commit 51e2791a4982e0c68a71f0988fb5dfd9b51c2df5 Author: superlishunqin <852326703@qq.com> Date: Thu Sep 25 05:52:22 2025 +0800 农产品直销平台完整项目 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..5a28c95 --- /dev/null +++ b/.gitignore @@ -0,0 +1,204 @@ +# Java编译文件 +*.class +*.jar +*.war +*.ear +*.zip +*.tar.gz +*.rar + +# Maven +target/ +pom.xml.tag +pom.xml.releaseBackup +pom.xml.versionsBackup +pom.xml.next +release.properties +dependency-reduced-pom.xml +buildNumber.properties +.mvn/timing.properties +.mvn/wrapper/maven-wrapper.jar + +# Gradle +.gradle +/build/ +gradle-app.setting +!gradle-wrapper.jar +!gradle-wrapper.properties +!gradle/wrapper/gradle-wrapper.jar +!**/src/main/**/build/ +!**/src/test/**/build/ + +# Spring Boot +spring-boot-starter-parent/ + +# Node.js和Vue +node_modules/ +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* +dist/ +dist-ssr/ +*.local +.npmrc + +# Vue相关 +.vuepress/dist +.nuxt +.nitro + +# Python虚拟环境 +venv/ +env/ +ENV/ +.venv/ +.env/ +.python-version +pyenv.cfg + +# Python编译文件 +__pycache__/ +*.py[cod] +*$py.class +*.so +*.egg +*.egg-info/ +dist/ +build/ +.eggs/ + +# Python工具 +.pytest_cache/ +.coverage +.tox/ +.nox/ +htmlcov/ +.mypy_cache/ +.dmypy.json +dmypy.json + +# Jupyter Notebook +.ipynb_checkpoints + +# IDE相关文件 +.idea/ +.vscode/ +*.swp +*.swo +*~ +.project +.classpath +.c9/ +*.launch +.settings/ +*.sublime-workspace +*.sublime-project +.spyderproject +.spyproject + +# 系统相关文件 +.DS_Store +.DS_Store? +._* +.Spotlight-V100 +.Trashes +ehthumbs.db +Thumbs.db +.fseventsd +.TemporaryItems +.DocumentRevisions-V100 + +# 日志文件 +logs/ +*.log +log/ +*.log.* +*.out + +# 缓存文件 +.cache/ +.temp/ +.tmp/ +*.cache +.eslintcache +.stylelintcache + +# 环境配置文件(包含敏感信息) +.env +.env.local +.env.development.local +.env.test.local +.env.production.local + +# 敏感配置文件(重要:包含密钥信息) +**/application-*.yml +!**/application.yml +!**/application-prod.yml.template +!**/application-dev.yml.template +*.key +*.pem +*.p12 +*.keystore +*.jks + +# 数据库文件 +*.db +*.sqlite +*.sqlite3 + +# 测试覆盖率报告 +coverage/ +*.lcov +.nyc_output + +# 依赖分析文件 +.pnp +.pnp.js + +# 运行时数据 +pids +*.pid +*.seed +*.pid.lock + +# 可选的npm缓存目录 +.npm + +# 可选的eslint缓存 +.eslintcache + +# 可选的stylelint缓存 +.stylelintcache + +# TypeScript缓存 +*.tsbuildinfo + +# 备份文件 +*.bak +*.backup +*.old + +# 压缩文件 +*.7z +*.dmg +*.gz +*.iso +*.tar + +# 项目特定文件 +export_code.sh +temp_* +*.txt.backup* +*.yml.backup* +*.yml.bak* +SunnyFarm_源代码.txt + +# Docker相关 +.dockerignore +docker-compose.override.yml + +# 其他 +.vite-inspect/ +.vite/ diff --git a/README.md b/README.md new file mode 100644 index 0000000..29b31b3 --- /dev/null +++ b/README.md @@ -0,0 +1,351 @@ +# 🌱 农产品直销平台 (SunnyFarm) + +一个基于 Spring Boot + Vue.js + MySQL 的现代化农产品电商平台,支持多角色管理(用户、商家、管理员),提供完整的电商功能包括商品管理、订单处理、支付集成、库存管理等。 + +## 📋 目录 + +- [功能特性](#功能特性) +- [技术栈](#技术栈) +- [环境要求](#环境要求) +- [快速开始](#快速开始) +- [配置说明](#配置说明) +- [部署指南](#部署指南) +- [API文档](#api文档) +- [贡献指南](#贡献指南) + +## ✨ 功能特性 + +### 🛒 核心电商功能 +- **商品管理**: 分类管理、商品发布、图片上传、库存管理 +- **购物车**: 添加商品、数量调整、批量操作 +- **订单系统**: 创建订单、支付集成、状态跟踪、物流管理 +- **用户系统**: 注册登录、个人中心、收货地址管理 +- **评价系统**: 商品评价、商家回复、评分统计 + +### 👥 多角色支持 +- **普通用户**: 浏览商品、下单购买、管理订单 +- **商家**: 商品管理、订单处理、销售统计、客服聊天 +- **平台管理员**: 用户管理、商家审核、系统配置、数据统计 + +### 🔧 技术亮点 +- **支付集成**: 支付宝沙箱支付,支持异步回调 +- **文件存储**: 腾讯云COS + CDN加速 +- **缓存优化**: Redis缓存用户会话和购物车数据 +- **邮件服务**: 注册验证码、订单通知 +- **实时聊天**: 用户与商家在线客服 +- **响应式设计**: 支持PC端和移动端访问 + +## 🛠 技术栈 + +### 后端技术 +- **框架**: Spring Boot 2.7.14 +- **安全**: Spring Security + JWT认证 +- **数据库**: MySQL 8.0 + MyBatis Plus +- **缓存**: Redis +- **文件存储**: 腾讯云COS +- **支付**: 支付宝开放平台 +- **邮件**: Spring Boot Mail + +### 前端技术 +- **框架**: Vue 3 + Vite +- **UI组件**: Element Plus +- **状态管理**: Pinia +- **路由**: Vue Router 4 +- **图表**: ECharts +- **HTTP客户端**: Axios + +### 运维部署 +- **容器化**: Docker + Docker Compose +- **云服务**: 支持各类云平台部署 +- **反向代理**: Nginx +- **进程管理**: PM2(可选) + +## 📋 环境要求 + +### 开发环境 +- **Java**: JDK 8 或以上 +- **Node.js**: 16.0 或以上 +- **Maven**: 3.6 或以上 +- **MySQL**: 8.0 或以上 +- **Redis**: 6.0 或以上 + +### 生产环境 +- **服务器**: Linux (推荐 Ubuntu 20.04+) +- **内存**: 建议 2GB 以上 +- **存储**: 建议 20GB 以上 +- **网络**: 需要公网IP或域名(用于支付回调) + +## 🚀 快速开始 + +### 1. 克隆项目 + +```bash +git clone https://git.sq0715.com/qin/SunnyFarm.git +cd SunnyFarm +``` + +### 2. 配置文件设置 + +#### 后端配置 + +复制配置模板并填入你的配置信息: + +```bash +# 开发环境配置 +cp backend/src/main/resources/application-dev.yml.example backend/src/main/resources/application-dev.yml + +# 生产环境配置 +cp backend/src/main/resources/application-prod.yml.example backend/src/main/resources/application-prod.yml +``` + +### 3. 数据库初始化 + +```bash +# 创建数据库 +mysql -u root -p -e "CREATE DATABASE sunnyfarm CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;" + +# 导入数据库结构和初始数据 +mysql -u your_username -p sunnyfarm < sql/sunnyfarm.sql +mysql -u your_username -p sunnyfarm < sql/init_inventory.sql +``` + +### 4. 启动后端服务 + +```bash +cd backend +mvn clean install +mvn spring-boot:run +``` + +服务启动后访问: http://localhost:8080/api/init/status + +### 5. 启动前端服务 + +```bash +cd frontend +npm install +npm run dev +``` + +前端启动后访问: http://localhost:3000 + +## ⚙️ 配置说明 + +### 数据库配置 +```yaml +spring: + datasource: + url: jdbc:mysql://your-mysql-host:3306/sunnyfarm + username: your-db-username + password: your-db-password +``` + +### Redis配置 +```yaml +spring: + redis: + host: your-redis-host + port: 6379 + password: your-redis-password +``` + +### 邮件服务配置 (QQ邮箱示例) +```yaml +spring: + mail: + host: smtp.qq.com + port: 465 + username: your-email@qq.com + password: your-email-auth-code # QQ邮箱授权码 +``` + +### 腾讯云COS配置 +```yaml +tencent: + cos: + secret-id: your-tencent-cos-secret-id + secret-key: your-tencent-cos-secret-key + region: ap-guangzhou + bucket: your-bucket-name +``` + +### 支付宝配置 +```yaml +alipay: + app-id: your-alipay-app-id + app-private-key: your-alipay-app-private-key + alipay-public-key: your-alipay-public-key + notify-url: http://your-domain:8080/api/payment/alipay/notify + return-url: http://your-domain:3000/orders?payment=success +``` + +**重要**: 支付宝回调地址必须是公网可访问的地址,不能使用localhost + +### JWT配置 +```yaml +jwt: + secret: your-jwt-secret-key + expiration: 86400000 # 24小时,单位毫秒 +``` + +## 🐳 Docker部署 + +### 使用Docker Compose部署 + +```bash +# 构建并启动服务 +docker-compose up -d + +# 查看服务状态 +docker-compose ps + +# 查看日志 +docker-compose logs -f +``` + +### 手动Docker部署 + +```bash +# 构建后端镜像 +cd backend +docker build -t sunnyfarm-backend . + +# 构建前端镜像 +cd ../frontend +docker build -t sunnyfarm-frontend . + +# 启动服务 +docker run -d -p 8080:8080 --name sunnyfarm-backend sunnyfarm-backend +docker run -d -p 3000:3000 --name sunnyfarm-frontend sunnyfarm-frontend +``` + +## 🌍 生产环境部署 + +### 1. 服务器准备 + +```bash +# 安装基础软件 +sudo apt update && sudo apt install -y git docker.io docker-compose nginx + +# 启动Docker服务 +sudo systemctl start docker +sudo systemctl enable docker +``` + +### 2. 克隆项目并配置 + +```bash +git clone https://git.sq0715.com/qin/SunnyFarm.git +cd SunnyFarm + +# 配置生产环境文件 +cp backend/src/main/resources/application-prod.yml.example backend/src/main/resources/application-prod.yml +# 编辑配置文件,填入生产环境配置信息 +vim backend/src/main/resources/application-prod.yml +``` + +### 3. 部署启动 + +```bash +# 构建并启动 +docker-compose up -d + +# 检查服务状态 +docker-compose ps +curl http://your-server-ip:8080/api/init/status +``` + +## 📚 API文档 + +### 认证接口 + +| 方法 | 路径 | 说明 | +|------|------|------| +| POST | `/api/user/login` | 用户登录 | +| POST | `/api/user/register` | 用户注册 | +| POST | `/api/merchant/login` | 商家登录 | +| POST | `/api/admin/login` | 管理员登录 | + +### 商品接口 + +| 方法 | 路径 | 说明 | +|------|------|------| +| GET | `/api/product/list` | 商品列表 | +| GET | `/api/product/{id}` | 商品详情 | +| POST | `/api/product/create` | 创建商品(商家) | +| PUT | `/api/product/{id}` | 更新商品(商家) | + +### 订单接口 + +| 方法 | 路径 | 说明 | +|------|------|------| +| POST | `/api/order/create` | 创建订单 | +| GET | `/api/order/list` | 订单列表 | +| GET | `/api/order/{id}` | 订单详情 | +| POST | `/api/order/{id}/pay` | 订单支付 | + +## 🎯 默认账号 + +### 管理员账号 +- 用户名: `admin` +- 密码: `admin123` + +### 测试商家账号 +- 用户名: `merchant` +- 密码: `merchant123` + +### 测试用户账号 +- 用户名: `user` +- 密码: `user123` + +## 📝 开发说明 + +### 项目结构 + +``` +SunnyFarm/ +├── backend/ # 后端Spring Boot项目 +│ ├── src/main/java/ # Java源码 +│ ├── src/main/resources/ # 配置文件 +│ └── pom.xml # Maven配置 +├── frontend/ # 前端Vue项目 +│ ├── src/ # Vue源码 +│ ├── public/ # 静态资源 +│ └── package.json # NPM配置 +├── sql/ # 数据库脚本 +├── docker-compose.yml # Docker编排 +└── README.md # 项目文档 +``` + +### 开发规范 + +- 代码格式化: 使用项目配置的格式化规则 +- 提交规范: 使用语义化提交信息 +- 分支管理: 使用Git Flow工作流 +- 代码审查: 重要功能需要代码审查 + +## 🤝 贡献指南 + +1. Fork本项目 +2. 创建特性分支 (`git checkout -b feature/AmazingFeature`) +3. 提交更改 (`git commit -m 'Add some AmazingFeature'`) +4. 推送分支 (`git push origin feature/AmazingFeature`) +5. 创建Pull Request + +## 📄 许可证 + +本项目采用 MIT 许可证 - 查看 [LICENSE](LICENSE) 文件了解详情 + +## 📞 联系方式 + +- 项目地址: https://git.sq0715.com/qin/SunnyFarm +- 问题反馈: 请在GitHub Issues中提交 +- 邮箱联系: 852326703@qq.com + +## 🙏 致谢 + +感谢所有为本项目做出贡献的开发者们! + +--- + +**注意**: 本项目仅供学习和研究使用,请勿用于商业用途。使用前请确保遵守相关法律法规。 \ No newline at end of file diff --git a/backend/README.md b/backend/README.md new file mode 100644 index 0000000..e69de29 diff --git a/backend/pom.xml b/backend/pom.xml new file mode 100644 index 0000000..37ebbe0 --- /dev/null +++ b/backend/pom.xml @@ -0,0 +1,133 @@ + + + 4.0.0 + + org.springframework.boot + spring-boot-starter-parent + 2.7.14 + + + com.sunnyfarm + sunny-farm + 0.0.1-SNAPSHOT + sunny-farm + Agricultural Product Direct Sales Platform + + 8 + + + + org.apache.commons + commons-pool2 + + + + org.springframework.boot + spring-boot-starter-web + + + + + org.springframework.boot + spring-boot-starter-security + + + + + org.springframework.boot + spring-boot-starter-mail + + + + + com.baomidou + mybatis-plus-boot-starter + 3.5.3 + + + + + mysql + mysql-connector-java + 8.0.33 + + + + + org.springframework.boot + spring-boot-starter-data-redis + + + + + org.projectlombok + lombok + true + + + + + io.jsonwebtoken + jjwt + 0.9.1 + + + + + com.fasterxml.jackson.core + jackson-databind + + + + + org.apache.commons + commons-lang3 + 3.12.0 + + + + + com.qcloud + cos_api + 5.6.155 + + + + + org.springframework.boot + spring-boot-starter-validation + + + + + org.springframework.boot + spring-boot-starter-test + test + + + + com.alipay.sdk + alipay-sdk-java + 4.38.10.ALL + + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + org.projectlombok + lombok + + + + + + + diff --git a/backend/pom.xml.backup2 b/backend/pom.xml.backup2 new file mode 100644 index 0000000..f61c109 --- /dev/null +++ b/backend/pom.xml.backup2 @@ -0,0 +1,206 @@ + + + 4.0.0 + + com.sunnyfarm + sunny-farm + 1.0.0 + jar + + SunnyFarm + 农产品直销平台 + + + + 17 + 17 + 17 + + 2.7.14 + 3.5.3 + 8.0.33 + 2.7.14 + 0.11.5 + 5.6.89 + 4.35.79.ALL + 5.8.20 + 2.0.31 + 1.18.32 + UTF-8 + + + + + + org.springframework.boot + spring-boot-dependencies + ${spring.boot.version} + pom + import + + + + + + + + org.springframework.boot + spring-boot-starter-web + + + + org.springframework.boot + spring-boot-starter-security + + + + org.springframework.boot + spring-boot-starter-data-redis + + + + org.springframework.boot + spring-boot-starter-validation + + + + org.springframework.boot + spring-boot-starter-mail + + + + + com.baomidou + mybatis-plus-boot-starter + ${mybatis.plus.version} + + + + com.mysql + mysql-connector-j + ${mysql.version} + + + + + io.jsonwebtoken + jjwt-api + ${jwt.version} + + + io.jsonwebtoken + jjwt-impl + ${jwt.version} + runtime + + + io.jsonwebtoken + jjwt-jackson + ${jwt.version} + runtime + + + + + com.qcloud + cos_api + ${cos.version} + + + + + com.alipay.sdk + alipay-sdk-java + ${alipay.version} + + + + + cn.hutool + hutool-all + ${hutool.version} + + + + com.alibaba.fastjson2 + fastjson2 + ${fastjson.version} + + + + org.apache.commons + commons-lang3 + + + + + org.projectlombok + lombok + ${lombok.version} + provided + + + + + org.springframework.boot + spring-boot-starter-test + test + + + + + org.springframework.boot + spring-boot-configuration-processor + true + + + + + + + org.springframework.boot + spring-boot-maven-plugin + ${spring.boot.version} + + + + org.springframework.boot + spring-boot-configuration-processor + + + org.projectlombok + lombok + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + + 3.11.0 + + + ${java.version} + ${java.version} + UTF-8 + + + org.projectlombok + lombok + ${lombok.version} + + + org.projectlombok + lombok-mapstruct-binding + 0.2.0 + + + + + + + diff --git a/backend/pom.xml.backup3 b/backend/pom.xml.backup3 new file mode 100644 index 0000000..43fc33c --- /dev/null +++ b/backend/pom.xml.backup3 @@ -0,0 +1,122 @@ + + + 4.0.0 + + org.springframework.boot + spring-boot-starter-parent + 2.7.14 + + + com.sunnyfarm + sunny-farm + 0.0.1-SNAPSHOT + sunny-farm + Agricultural Product Direct Sales Platform + + 8 + + + + + org.springframework.boot + spring-boot-starter-web + + + + + org.springframework.boot + spring-boot-starter-security + + + + + org.springframework.boot + spring-boot-starter-mail + + + + + com.baomidou + mybatis-plus-boot-starter + 3.5.3 + + + + + mysql + mysql-connector-java + 8.0.33 + + + + + org.springframework.boot + spring-boot-starter-data-redis + + + + + org.projectlombok + lombok + true + + + + + io.jsonwebtoken + jjwt + 0.9.1 + + + + + com.fasterxml.jackson.core + jackson-databind + + + + + org.apache.commons + commons-lang3 + 3.12.0 + + + + + com.qcloud + cos_api + 5.6.155 + + + + + org.springframework.boot + spring-boot-starter-validation + + + + + org.springframework.boot + spring-boot-starter-test + test + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + org.projectlombok + lombok + + + + + + + diff --git a/backend/src/main/java/com/sunnyfarm/SunnyFarmApplication.java b/backend/src/main/java/com/sunnyfarm/SunnyFarmApplication.java new file mode 100644 index 0000000..84a46cf --- /dev/null +++ b/backend/src/main/java/com/sunnyfarm/SunnyFarmApplication.java @@ -0,0 +1,12 @@ +package com.sunnyfarm; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration; + +@SpringBootApplication(exclude = {SecurityAutoConfiguration.class}) +public class SunnyFarmApplication { + public static void main(String[] args) { + SpringApplication.run(SunnyFarmApplication.class, args); + } +} diff --git a/backend/src/main/java/com/sunnyfarm/common/PageResult.java b/backend/src/main/java/com/sunnyfarm/common/PageResult.java new file mode 100644 index 0000000..a5f1244 --- /dev/null +++ b/backend/src/main/java/com/sunnyfarm/common/PageResult.java @@ -0,0 +1,22 @@ +package com.sunnyfarm.common; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.util.List; + +@Data +@NoArgsConstructor +@AllArgsConstructor +public class PageResult { + private Long total; + private Long pages; + private Long current; + private Long size; + private List records; + + public static PageResult of(Long total, Long pages, Long current, Long size, List records) { + return new PageResult<>(total, pages, current, size, records); + } +} diff --git a/backend/src/main/java/com/sunnyfarm/common/Result.java b/backend/src/main/java/com/sunnyfarm/common/Result.java new file mode 100644 index 0000000..6b68a60 --- /dev/null +++ b/backend/src/main/java/com/sunnyfarm/common/Result.java @@ -0,0 +1,42 @@ +package com.sunnyfarm.common; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@NoArgsConstructor +@AllArgsConstructor +public class Result { + private Integer code; + private String message; + private T data; + + public static Result success() { + return new Result<>(200, "操作成功", null); + } + + public static Result success(T data) { + return new Result<>(200, "操作成功", data); + } + + public static Result success(String message, T data) { + return new Result<>(200, message, data); + } + + public static Result error() { + return new Result<>(500, "操作失败", null); + } + + public static Result error(String message) { + return new Result<>(500, message, null); + } + + public static Result error(Integer code, String message) { + return new Result<>(code, message, null); + } + + public static Result error(String message, T data) { + return new Result<>(500, message, data); + } +} \ No newline at end of file diff --git a/backend/src/main/java/com/sunnyfarm/config/AlipayConfig.java b/backend/src/main/java/com/sunnyfarm/config/AlipayConfig.java new file mode 100644 index 0000000..952d2ab --- /dev/null +++ b/backend/src/main/java/com/sunnyfarm/config/AlipayConfig.java @@ -0,0 +1,28 @@ +package com.sunnyfarm.config; + +import com.alipay.api.AlipayClient; +import com.alipay.api.DefaultAlipayClient; +import com.sunnyfarm.config.properties.AlipayProperties; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration +public class AlipayConfig { + + @Autowired + private AlipayProperties alipayProperties; + + @Bean + public AlipayClient alipayClient() { + return new DefaultAlipayClient( + alipayProperties.getServerUrl(), + alipayProperties.getAppId(), + alipayProperties.getAppPrivateKey(), + alipayProperties.getFormat(), + alipayProperties.getCharset(), + alipayProperties.getAlipayPublicKey(), + alipayProperties.getSignType() + ); + } +} diff --git a/backend/src/main/java/com/sunnyfarm/config/AsyncConfig.java b/backend/src/main/java/com/sunnyfarm/config/AsyncConfig.java new file mode 100644 index 0000000..78ca784 --- /dev/null +++ b/backend/src/main/java/com/sunnyfarm/config/AsyncConfig.java @@ -0,0 +1,9 @@ +package com.sunnyfarm.config; + +import org.springframework.context.annotation.Configuration; +import org.springframework.scheduling.annotation.EnableAsync; + +@Configuration +@EnableAsync +public class AsyncConfig { +} diff --git a/backend/src/main/java/com/sunnyfarm/config/CosConfig.java b/backend/src/main/java/com/sunnyfarm/config/CosConfig.java new file mode 100644 index 0000000..20694de --- /dev/null +++ b/backend/src/main/java/com/sunnyfarm/config/CosConfig.java @@ -0,0 +1,33 @@ +package com.sunnyfarm.config; + +import com.qcloud.cos.COSClient; +import com.qcloud.cos.ClientConfig; +import com.qcloud.cos.auth.BasicCOSCredentials; +import com.qcloud.cos.auth.COSCredentials; +import com.qcloud.cos.region.Region; +import com.sunnyfarm.config.properties.CosProperties; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration +public class CosConfig { + + @Autowired + private CosProperties cosProperties; + + @Bean + public COSClient cosClient() { + // 初始化用户身份信息(secretId, secretKey) + COSCredentials cred = new BasicCOSCredentials( + cosProperties.getSecretId(), + cosProperties.getSecretKey() + ); + + // 设置bucket的区域 + ClientConfig clientConfig = new ClientConfig(new Region(cosProperties.getRegion())); + + // 生成cos客户端 + return new COSClient(cred, clientConfig); + } +} diff --git a/backend/src/main/java/com/sunnyfarm/config/MybatisPlusConfig.java b/backend/src/main/java/com/sunnyfarm/config/MybatisPlusConfig.java new file mode 100644 index 0000000..d21927d --- /dev/null +++ b/backend/src/main/java/com/sunnyfarm/config/MybatisPlusConfig.java @@ -0,0 +1,21 @@ +package com.sunnyfarm.config; + +import com.baomidou.mybatisplus.annotation.DbType; +import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor; +import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor; +import org.mybatis.spring.annotation.MapperScan; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration +@MapperScan("com.sunnyfarm.mapper") +public class MybatisPlusConfig { + + @Bean + public MybatisPlusInterceptor mybatisPlusInterceptor() { + MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor(); + // 分页插件 + interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL)); + return interceptor; + } +} diff --git a/backend/src/main/java/com/sunnyfarm/config/PasswordConfig.java b/backend/src/main/java/com/sunnyfarm/config/PasswordConfig.java new file mode 100644 index 0000000..e9de3b8 --- /dev/null +++ b/backend/src/main/java/com/sunnyfarm/config/PasswordConfig.java @@ -0,0 +1,15 @@ +package com.sunnyfarm.config; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; +import org.springframework.security.crypto.password.PasswordEncoder; + +@Configuration +public class PasswordConfig { + + @Bean + public PasswordEncoder passwordEncoder() { + return new BCryptPasswordEncoder(); + } +} diff --git a/backend/src/main/java/com/sunnyfarm/config/WebConfig.java b/backend/src/main/java/com/sunnyfarm/config/WebConfig.java new file mode 100644 index 0000000..d4f7ae3 --- /dev/null +++ b/backend/src/main/java/com/sunnyfarm/config/WebConfig.java @@ -0,0 +1,93 @@ +package com.sunnyfarm.config; + +import com.sunnyfarm.interceptor.JwtAuthInterceptor; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.web.cors.CorsConfiguration; +import org.springframework.web.cors.CorsConfigurationSource; +import org.springframework.web.cors.UrlBasedCorsConfigurationSource; +import org.springframework.web.servlet.config.annotation.CorsRegistry; +import org.springframework.web.servlet.config.annotation.InterceptorRegistry; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; + +import java.util.Arrays; + +@Configuration +public class WebConfig implements WebMvcConfigurer { + + @Autowired + private JwtAuthInterceptor jwtAuthInterceptor; + + @Override + public void addCorsMappings(CorsRegistry registry) { + registry.addMapping("/**") + .allowedOriginPatterns("*") + .allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS") + .allowedHeaders("*") + .allowCredentials(true) + .maxAge(3600); + } + + @Bean + public CorsConfigurationSource corsConfigurationSource() { + CorsConfiguration configuration = new CorsConfiguration(); + configuration.setAllowedOriginPatterns(Arrays.asList("*")); + configuration.setAllowedMethods(Arrays.asList("GET", "POST", "PUT", "DELETE", "OPTIONS")); + configuration.setAllowedHeaders(Arrays.asList("*")); + configuration.setAllowCredentials(true); + configuration.setMaxAge(3600L); + + UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); + source.registerCorsConfiguration("/**", configuration); + return source; + } + + @Override + public void addInterceptors(InterceptorRegistry registry) { + registry.addInterceptor(jwtAuthInterceptor) + .addPathPatterns("/**") + .excludePathPatterns( + // 用户认证相关 + "/api/user/login", + "/api/user/register", + "/api/user/send-code", + "/api/user/send-email-code", // 新增:邮箱验证码接口 + "/api/user/verify-code", + // 商家认证相关 + "/api/merchant/register", + "/api/merchant/login", + // 管理员认证相关 + "/api/admin/login", + // 公开接口 - 商品分类 + "/api/category/**", + // 公开接口 - 商品相关(用户端公共访问) + "/api/product/list", + "/api/product/*/detail", + "/api/product/hot", + "/api/product/search", + // 初始化和测试接口 + "/api/init/**", + // 公共接口 - 公告相关 + "/api/announcements/published", + "/api/announcements/latest", + "/api/announcements/*/detail", + "/api/test/**", + // 支付回调(无需认证) + "/api/payment/alipay/notify", + "/api/payment/alipay/return", + // 系统相关 + "/error", + "/favicon.ico", + // 静态资源 + "/**/*.css", + "/**/*.js", + "/**/*.png", + "/**/*.jpg", + "/**/*.jpeg", + "/**/*.gif", + "/**/*.ico", + "/**/*.html" + ); + } +} diff --git a/backend/src/main/java/com/sunnyfarm/config/properties/AlipayProperties.java b/backend/src/main/java/com/sunnyfarm/config/properties/AlipayProperties.java new file mode 100644 index 0000000..6c73d99 --- /dev/null +++ b/backend/src/main/java/com/sunnyfarm/config/properties/AlipayProperties.java @@ -0,0 +1,37 @@ +package com.sunnyfarm.config.properties; + +import lombok.Data; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; + +@Data +@Component +public class AlipayProperties { + + @Value("${alipay.app-id}") + private String appId; + + @Value("${alipay.app-private-key}") + private String appPrivateKey; + + @Value("${alipay.alipay-public-key}") + private String alipayPublicKey; + + @Value("${alipay.server-url}") + private String serverUrl; + + @Value("${alipay.format}") + private String format; + + @Value("${alipay.charset}") + private String charset; + + @Value("${alipay.sign-type}") + private String signType; + + @Value("${alipay.notify-url}") + private String notifyUrl; + + @Value("${alipay.return-url}") + private String returnUrl; +} diff --git a/backend/src/main/java/com/sunnyfarm/config/properties/CosProperties.java b/backend/src/main/java/com/sunnyfarm/config/properties/CosProperties.java new file mode 100644 index 0000000..e08ba80 --- /dev/null +++ b/backend/src/main/java/com/sunnyfarm/config/properties/CosProperties.java @@ -0,0 +1,25 @@ +package com.sunnyfarm.config.properties; + +import lombok.Data; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.stereotype.Component; + +@Data +@Component +@ConfigurationProperties(prefix = "tencent.cos") +public class CosProperties { + private String region; + private String bucket; + private String secretId; + private String secretKey; + private String baseUrl; + private String cdnUrl; + private Boolean cdnEnabled = false; + + /** + * 获取实际使用的URL(CDN优先) + */ + public String getActualUrl() { + return (cdnEnabled != null && cdnEnabled && cdnUrl != null) ? cdnUrl : baseUrl; + } +} diff --git a/backend/src/main/java/com/sunnyfarm/config/properties/EmailProperties.java b/backend/src/main/java/com/sunnyfarm/config/properties/EmailProperties.java new file mode 100644 index 0000000..0979003 --- /dev/null +++ b/backend/src/main/java/com/sunnyfarm/config/properties/EmailProperties.java @@ -0,0 +1,14 @@ +package com.sunnyfarm.config.properties; + +import lombok.Data; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.stereotype.Component; + +@Data +@Component +@ConfigurationProperties(prefix = "system.email") +public class EmailProperties { + private boolean enabled = true; + private String from; + private String fromName; +} diff --git a/backend/src/main/java/com/sunnyfarm/controller/AddressController.java b/backend/src/main/java/com/sunnyfarm/controller/AddressController.java new file mode 100644 index 0000000..0fcaa8b --- /dev/null +++ b/backend/src/main/java/com/sunnyfarm/controller/AddressController.java @@ -0,0 +1,129 @@ +package com.sunnyfarm.controller; + +import com.sunnyfarm.common.Result; +import com.sunnyfarm.entity.Address; +import com.sunnyfarm.service.AddressService; +import com.sunnyfarm.util.UserContext; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.*; + +import java.util.List; + +@RestController +@RequestMapping("/api/address") +@CrossOrigin +public class AddressController { + + @Autowired + private AddressService addressService; + + /** + * 获取用户地址列表 + */ + @GetMapping("/list") + public Result> getAddressList() { + Long userId = UserContext.getCurrentUserId(); + List
addresses = addressService.getUserAddresses(userId); + return Result.success(addresses); + } + + /** + * 获取用户默认地址 + */ + @GetMapping("/default") + public Result
getDefaultAddress() { + Long userId = UserContext.getCurrentUserId(); + Address address = addressService.getUserDefaultAddress(userId); + return Result.success(address); + } + + /** + * 获取地址详情 + */ + @GetMapping("/{id}") + public Result
getAddressDetail(@PathVariable Long id) { + Address address = addressService.getById(id); + if (address != null) { + return Result.success(address); + } else { + return Result.error("地址不存在"); + } + } + + /** + * 创建地址 + */ + @PostMapping("/create") + public Result
createAddress(@RequestBody Address address) { + try { + Long userId = UserContext.getCurrentUserId(); + address.setUserId(userId); + + Address createdAddress = addressService.createAddress(address); + return Result.success("地址创建成功", createdAddress); + } catch (Exception e) { + return Result.error(e.getMessage()); + } + } + + /** + * 更新地址 + */ + @PutMapping("/{id}") + public Result
updateAddress(@PathVariable Long id, @RequestBody Address address) { + try { + Long userId = UserContext.getCurrentUserId(); + address.setUserId(userId); + + Address updatedAddress = addressService.updateAddress(id, address); + return Result.success("地址更新成功", updatedAddress); + } catch (Exception e) { + return Result.error(e.getMessage()); + } + } + + /** + * 删除地址 + */ + @DeleteMapping("/{id}") + public Result deleteAddress(@PathVariable Long id) { + try { + boolean success = addressService.deleteAddress(id); + if (success) { + return Result.success("地址删除成功"); + } else { + return Result.error("地址删除失败"); + } + } catch (Exception e) { + return Result.error(e.getMessage()); + } + } + + /** + * 设置默认地址 + */ + @PutMapping("/{id}/default") + public Result setDefaultAddress(@PathVariable Long id) { + try { + Long userId = UserContext.getCurrentUserId(); + boolean success = addressService.setDefaultAddress(userId, id); + if (success) { + return Result.success("默认地址设置成功"); + } else { + return Result.error("默认地址设置失败"); + } + } catch (Exception e) { + return Result.error(e.getMessage()); + } + } + + /** + * 获取用户地址数量 + */ + @GetMapping("/count") + public Result getAddressCount() { + Long userId = UserContext.getCurrentUserId(); + Long count = addressService.getUserAddressCount(userId); + return Result.success(count); + } +} diff --git a/backend/src/main/java/com/sunnyfarm/controller/AdminController.java b/backend/src/main/java/com/sunnyfarm/controller/AdminController.java new file mode 100644 index 0000000..44c61aa --- /dev/null +++ b/backend/src/main/java/com/sunnyfarm/controller/AdminController.java @@ -0,0 +1,631 @@ +package com.sunnyfarm.controller; + +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.sunnyfarm.common.PageResult; +import com.sunnyfarm.common.Result; +import com.sunnyfarm.dto.AdminLoginRequest; +import com.sunnyfarm.dto.AdminUpdateRequest; +import com.sunnyfarm.dto.AdminPasswordRequest; +import com.sunnyfarm.entity.*; +import com.sunnyfarm.service.*; +import com.sunnyfarm.util.JwtUtil; +import com.sunnyfarm.util.UserContext; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.*; + +import javax.servlet.http.HttpServletRequest; +import javax.validation.Valid; +import java.math.BigDecimal; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; +import java.time.format.DateTimeFormatter; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +@RestController +@RequestMapping("/api/admin") +@CrossOrigin +public class AdminController { + + @Autowired + private AdminService adminService; + + @Autowired + private UserService userService; + + @Autowired + private MerchantService merchantService; + + @Autowired + private ProductService productService; + + @Autowired + private OrderService orderService; + + @Autowired + private StatisticsService statisticsService; + + @Autowired + private SystemLogService systemLogService; + + @Autowired + private SystemConfigService systemConfigService; + + @Autowired + private AnnouncementService announcementService; + + @Autowired + private JwtUtil jwtUtil; + + /** + * 管理员登录 + */ + @PostMapping("/login") + public Result> login(@Valid @RequestBody AdminLoginRequest request, HttpServletRequest httpRequest) { + try { + Admin admin = adminService.login(request); + if (admin != null) { + String token = jwtUtil.generateAdminToken(admin.getId(), admin.getUsername()); + + // 记录登录日志 + systemLogService.recordSuccessLog( + SystemLog.ADMIN_LOGIN, + "管理员登录成功", + admin.getId(), + admin.getUsername(), + SystemLog.OPERATOR_ADMIN + ); + + Map result = new HashMap<>(); + result.put("token", token); + result.put("admin", admin); + + return Result.success("登录成功", result); + } else { + return Result.error("登录失败"); + } + } catch (Exception e) { + // 记录登录失败日志 + systemLogService.recordFailLog( + SystemLog.ADMIN_LOGIN, + "管理员登录失败", + null, + request.getUsername(), + SystemLog.OPERATOR_ADMIN, + e.getMessage() + ); + + return Result.error(e.getMessage()); + } + } + + /** + * 获取管理员信息 + */ + @GetMapping("/profile") + public Result getProfile(@RequestParam Long adminId) { + Admin admin = adminService.getAdminWithRole(adminId); + return Result.success(admin); + } + + /** + * 管理员注销 + */ + @PostMapping("/logout") + public Result logout() { + return Result.success("注销成功"); + } + + /** + * 获取仪表盘统计数据 + */ + @GetMapping("/dashboard/stats") + public Result> getDashboardStats() { + Map stats = new HashMap<>(); + + // 获取当天的开始和结束时间 + LocalDateTime todayStart = LocalDateTime.of(LocalDate.now(), LocalTime.MIN); + LocalDateTime todayEnd = LocalDateTime.of(LocalDate.now(), LocalTime.MAX); + + // 用户统计 + long totalUsers = userService.count(); + long newUsersToday = userService.count( + new QueryWrapper() + .between("created_at", todayStart, todayEnd) + ); + + // 商家统计 + long totalMerchants = merchantService.count(); + long pendingMerchants = merchantService.count( + new QueryWrapper().eq("status", 0) + ); + + // 订单统计 + long totalOrders = orderService.count(); + long newOrdersToday = orderService.count( + new QueryWrapper() + .between("created_at", todayStart, todayEnd) + ); + + // 商品统计 + long totalProducts = productService.count(); + long pendingProducts = productService.count( + new QueryWrapper().eq("status", 0) + ); + + // 收入统计 - 获取真实的订单金额 + BigDecimal totalRevenue = orderService.getTotalRevenue(); + BigDecimal todayRevenue = orderService.getTodayRevenue(); + + // 投诉统计(暂时设为0,等后续实现投诉功能) + long pendingComplaints = 0; + + stats.put("totalUsers", totalUsers); + stats.put("newUsersToday", newUsersToday); + stats.put("totalMerchants", totalMerchants); + stats.put("pendingMerchants", pendingMerchants); + stats.put("totalOrders", totalOrders); + stats.put("newOrdersToday", newOrdersToday); + stats.put("totalProducts", totalProducts); + stats.put("pendingProducts", pendingProducts); + stats.put("totalRevenue", totalRevenue != null ? totalRevenue : BigDecimal.ZERO); + stats.put("todayRevenue", todayRevenue != null ? todayRevenue : BigDecimal.ZERO); + stats.put("pendingComplaints", pendingComplaints); + + return Result.success(stats); + } + + /** + * 获取用户列表 + */ + @GetMapping("/users") + public Result> getUsers( + @RequestParam(defaultValue = "1") int page, + @RequestParam(defaultValue = "10") int size, + @RequestParam(required = false) String keyword, + @RequestParam(required = false) Integer status) { + + Page pageParam = new Page<>(page, size); + QueryWrapper queryWrapper = new QueryWrapper<>(); + + if (keyword != null && !keyword.trim().isEmpty()) { + queryWrapper.like("username", keyword) + .or() + .like("nickname", keyword) + .or() + .like("phone", keyword); + } + + if (status != null) { + queryWrapper.eq("status", status); + } + + queryWrapper.orderByDesc("created_at"); + + Page result = userService.page(pageParam, queryWrapper); + + PageResult pageResult = new PageResult<>(); + pageResult.setRecords(result.getRecords()); + pageResult.setTotal(result.getTotal()); + pageResult.setCurrent(result.getCurrent()); + pageResult.setSize(result.getSize()); + + return Result.success(pageResult); + } + + /** + * 更新用户状态 + */ + @PutMapping("/users/{id}/status") + public Result updateUserStatus(@PathVariable Long id, @RequestParam Integer status) { + User user = new User(); + user.setId(id); + user.setStatus(status); + + boolean success = userService.updateById(user); + + if (success) { + User targetUser = userService.getById(id); + String statusText = status == 1 ? "启用" : "禁用"; + + systemLogService.recordSuccessLog( + "user_status_update", + statusText + "用户: " + (targetUser != null ? targetUser.getUsername() : "ID:" + id), + UserContext.getCurrentUserId(), + UserContext.getCurrentUsername(), + SystemLog.OPERATOR_ADMIN + ); + + return Result.success("用户状态更新成功"); + } else { + return Result.error("用户状态更新失败"); + } + } + + /** + * 获取商家列表 + */ + @GetMapping("/merchants") + public Result> getMerchants( + @RequestParam(defaultValue = "1") int page, + @RequestParam(defaultValue = "10") int size, + @RequestParam(required = false) String keyword, + @RequestParam(required = false) Integer status) { + + Page pageParam = new Page<>(page, size); + QueryWrapper queryWrapper = new QueryWrapper<>(); + + if (keyword != null && !keyword.trim().isEmpty()) { + queryWrapper.like("shop_name", keyword) + .or() + .like("legal_person", keyword) + .or() + .like("contact_phone", keyword); + } + + if (status != null) { + queryWrapper.eq("status", status); + } + + queryWrapper.orderByDesc("created_at"); + + Page result = merchantService.page(pageParam, queryWrapper); + + PageResult pageResult = new PageResult<>(); + pageResult.setRecords(result.getRecords()); + pageResult.setTotal(result.getTotal()); + pageResult.setCurrent(result.getCurrent()); + pageResult.setSize(result.getSize()); + + return Result.success(pageResult); + } + + /** + * 审核商家 + */ + @PutMapping("/merchants/{id}/audit") + public Result auditMerchant(@PathVariable Long id, @RequestParam Integer status) { + Merchant merchant = new Merchant(); + merchant.setId(id); + merchant.setStatus(status); + merchant.setVerifyTime(LocalDateTime.now()); + + boolean success = merchantService.updateById(merchant); + + if (success) { + Merchant targetMerchant = merchantService.getById(id); + String statusText = status == 1 ? "通过" : "拒绝"; + + systemLogService.recordSuccessLog( + SystemLog.MERCHANT_AUDIT, + "商家审核" + statusText + ": " + (targetMerchant != null ? targetMerchant.getShopName() : "ID:" + id), + UserContext.getCurrentUserId(), + UserContext.getCurrentUsername(), + SystemLog.OPERATOR_ADMIN + ); + + String message = status == 1 ? "商家审核通过" : "商家审核拒绝"; + return Result.success(message); + } else { + return Result.error("商家审核失败"); + } + } + + /** + * 获取商品列表 + */ + @GetMapping("/products") + public Result> getProducts( + @RequestParam(defaultValue = "1") int page, + @RequestParam(defaultValue = "10") int size, + @RequestParam(required = false) String keyword, + @RequestParam(required = false) Integer status) { + + Page pageParam = new Page<>(page, size); + QueryWrapper queryWrapper = new QueryWrapper<>(); + + if (keyword != null && !keyword.trim().isEmpty()) { + queryWrapper.like("name", keyword) + .or() + .like("origin", keyword); + } + + if (status != null) { + queryWrapper.eq("status", status); + } + + queryWrapper.orderByDesc("created_at"); + + Page result = productService.page(pageParam, queryWrapper); + + PageResult pageResult = new PageResult<>(); + pageResult.setRecords(result.getRecords()); + pageResult.setTotal(result.getTotal()); + pageResult.setCurrent(result.getCurrent()); + pageResult.setSize(result.getSize()); + + return Result.success(pageResult); + } + + /** + * 审核商品 + */ + @PutMapping("/products/{id}/audit") + public Result auditProduct(@PathVariable Long id, @RequestParam Integer status) { + Product product = new Product(); + product.setId(id); + product.setStatus(status); + + boolean success = productService.updateById(product); + + if (success) { + Product targetProduct = productService.getById(id); + String statusText = status == 1 ? "通过" : "拒绝"; + + systemLogService.recordSuccessLog( + SystemLog.PRODUCT_AUDIT, + "商品审核" + statusText + ": " + (targetProduct != null ? targetProduct.getName() : "ID:" + id), + UserContext.getCurrentUserId(), + UserContext.getCurrentUsername(), + SystemLog.OPERATOR_ADMIN + ); + + String message = status == 1 ? "商品审核通过" : "商品审核拒绝"; + return Result.success(message); + } else { + return Result.error("商品审核失败"); + } + } + + /** + * 获取订单列表 + */ + @GetMapping("/orders") + public Result> getOrders( + @RequestParam(defaultValue = "1") int page, + @RequestParam(defaultValue = "10") int size, + @RequestParam(required = false) String keyword, + @RequestParam(required = false) Integer status) { + + Page pageParam = new Page<>(page, size); + QueryWrapper queryWrapper = new QueryWrapper<>(); + + if (keyword != null && !keyword.trim().isEmpty()) { + queryWrapper.like("order_no", keyword) + .or() + .like("consignee", keyword) + .or() + .like("phone", keyword); + } + + if (status != null) { + queryWrapper.eq("status", status); + } + + queryWrapper.orderByDesc("created_at"); + + Page result = orderService.page(pageParam, queryWrapper); + + PageResult pageResult = new PageResult<>(); + pageResult.setRecords(result.getRecords()); + pageResult.setTotal(result.getTotal()); + pageResult.setCurrent(result.getCurrent()); + pageResult.setSize(result.getSize()); + + return Result.success(pageResult); + } + + /** + * 获取最新动态 + */ + @GetMapping("/activities") + public Result>> getRecentActivities() { + List> activities = systemLogService.getRecentActivities(10); + return Result.success(activities); + } + + /** + * 获取系统配置 + */ + @GetMapping("/config") + public Result> getSystemConfig() { + Map config = systemConfigService.getSystemConfig(); + return Result.success(config); + } + + /** + * 更新系统配置 + */ + @PutMapping("/config") + public Result updateSystemConfig(@RequestBody Map config) { + boolean success = systemConfigService.updateSystemConfig( + config, + UserContext.getCurrentUserId(), + UserContext.getCurrentUsername() + ); + + if (success) { + return Result.success("系统配置更新成功"); + } else { + return Result.error("系统配置更新失败"); + } + } + + /** + * 获取公告列表 + */ + @GetMapping("/announcements") + public Result> getAnnouncements( + @RequestParam(defaultValue = "1") int page, + @RequestParam(defaultValue = "10") int size, + @RequestParam(required = false) String keyword, + @RequestParam(required = false) Integer status) { + + Page pageParam = new Page<>(page, size); + QueryWrapper queryWrapper = new QueryWrapper<>(); + + if (keyword != null && !keyword.trim().isEmpty()) { + queryWrapper.like("title", keyword) + .or() + .like("content", keyword); + } + + if (status != null) { + queryWrapper.eq("status", status); + } + + queryWrapper.orderByDesc("created_at"); + + Page result = announcementService.page(pageParam, queryWrapper); + + PageResult pageResult = new PageResult<>(); + pageResult.setRecords(result.getRecords()); + pageResult.setTotal(result.getTotal()); + pageResult.setCurrent(result.getCurrent()); + pageResult.setSize(result.getSize()); + + return Result.success(pageResult); + } + + /** + * 创建公告 + */ + @PostMapping("/announcements") + public Result createAnnouncement(@RequestBody Announcement announcement) { + boolean success = announcementService.createAnnouncement( + announcement, + UserContext.getCurrentUserId(), + UserContext.getCurrentUsername() + ); + + if (success) { + return Result.success("公告创建成功"); + } else { + return Result.error("公告创建失败"); + } + } + + /** + * 更新公告 + */ + @PutMapping("/announcements/{id}") + public Result updateAnnouncement(@PathVariable Long id, @RequestBody Announcement announcement) { + announcement.setId(id); + boolean success = announcementService.updateAnnouncement( + announcement, + UserContext.getCurrentUserId(), + UserContext.getCurrentUsername() + ); + + if (success) { + return Result.success("公告更新成功"); + } else { + return Result.error("公告更新失败"); + } + } + + /** + * 删除公告 + */ + @DeleteMapping("/announcements/{id}") + public Result deleteAnnouncement(@PathVariable Long id) { + boolean success = announcementService.deleteAnnouncement( + id, + UserContext.getCurrentUserId(), + UserContext.getCurrentUsername() + ); + + if (success) { + return Result.success("公告删除成功"); + } else { + return Result.error("公告删除失败"); + } + } + + /** + * 获取系统日志 + */ + @GetMapping("/logs") + public Result> getSystemLogs( + @RequestParam(defaultValue = "1") int page, + @RequestParam(defaultValue = "10") int size, + @RequestParam(required = false) String keyword, + @RequestParam(required = false) String operationType) { + + Page pageParam = new Page<>(page, size); + QueryWrapper queryWrapper = new QueryWrapper<>(); + + if (keyword != null && !keyword.trim().isEmpty()) { + queryWrapper.like("operation_desc", keyword) + .or() + .like("operator_name", keyword); + } + + if (operationType != null && !operationType.trim().isEmpty()) { + queryWrapper.eq("operation_type", operationType); + } + + queryWrapper.orderByDesc("created_at"); + + Page result = systemLogService.page(pageParam, queryWrapper); + + PageResult pageResult = new PageResult<>(); + pageResult.setRecords(result.getRecords()); + pageResult.setTotal(result.getTotal()); + pageResult.setCurrent(result.getCurrent()); + pageResult.setSize(result.getSize()); + + return Result.success(pageResult); + } + + /** + * 更新管理员个人信息 + */ + @PutMapping("/profile/{id}") + public Result updateProfile(@PathVariable Long id, @Valid @RequestBody AdminUpdateRequest request) { + try { + boolean success = adminService.updateProfile(id, request); + if (success) { + systemLogService.recordSuccessLog( + "admin_profile_update", + "管理员更新个人信息", + id, + UserContext.getCurrentUsername(), + SystemLog.OPERATOR_ADMIN + ); + return Result.success("个人信息更新成功"); + } else { + return Result.error("个人信息更新失败"); + } + } catch (Exception e) { + return Result.error(e.getMessage()); + } + } + + /** + * 修改管理员密码 + */ + @PutMapping("/profile/{id}/password") + public Result changePassword(@PathVariable Long id, @Valid @RequestBody AdminPasswordRequest request) { + try { + boolean success = adminService.changePassword(id, request); + if (success) { + systemLogService.recordSuccessLog( + "admin_password_change", + "管理员修改密码", + id, + UserContext.getCurrentUsername(), + SystemLog.OPERATOR_ADMIN + ); + return Result.success("密码修改成功"); + } else { + return Result.error("密码修改失败"); + } + } catch (Exception e) { + return Result.error(e.getMessage()); + } + } +} diff --git a/backend/src/main/java/com/sunnyfarm/controller/AnnouncementController.java b/backend/src/main/java/com/sunnyfarm/controller/AnnouncementController.java new file mode 100644 index 0000000..f54045c --- /dev/null +++ b/backend/src/main/java/com/sunnyfarm/controller/AnnouncementController.java @@ -0,0 +1,99 @@ +package com.sunnyfarm.controller; + +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.sunnyfarm.common.PageResult; +import com.sunnyfarm.common.Result; +import com.sunnyfarm.entity.Announcement; +import com.sunnyfarm.service.AnnouncementService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.*; + +import java.util.List; + +/** + * 公告控制器 - 公共接口 + */ +@RestController +@RequestMapping("/api/announcements") +@CrossOrigin(originPatterns = "*") +public class AnnouncementController { + + @Autowired + private AnnouncementService announcementService; + + /** + * 获取已发布的公告列表 - 公共接口 + */ + @GetMapping("/published") + public Result> getPublishedAnnouncements( + @RequestParam(defaultValue = "1") int page, + @RequestParam(defaultValue = "10") int size, + @RequestParam(required = false) Integer type) { + + Page pageParam = new Page<>(page, size); + QueryWrapper queryWrapper = new QueryWrapper<>(); + + // 只查询已发布的公告 + queryWrapper.eq("status", 1); + + // 按类型筛选 + if (type != null) { + queryWrapper.eq("type", type); + } + + // 确保publish_time不为NULL,然后按发布时间倒序 + queryWrapper.isNotNull("publish_time") + .orderByDesc("publish_time"); + + Page result = announcementService.page(pageParam, queryWrapper); + + PageResult pageResult = new PageResult<>(); + pageResult.setRecords(result.getRecords()); + pageResult.setTotal(result.getTotal()); + pageResult.setCurrent(result.getCurrent()); + pageResult.setSize(result.getSize()); + + return Result.success(pageResult); + } + + /** + * 获取最新公告 - 用于首页展示 + */ + @GetMapping("/latest") + public Result> getLatestAnnouncements( + @RequestParam(defaultValue = "5") int limit) { + + QueryWrapper queryWrapper = new QueryWrapper<>(); + + // 只查询已发布的公告 + queryWrapper.eq("status", 1); + + // 确保publish_time不为NULL,然后按发布时间倒序,限制数量 + queryWrapper.isNotNull("publish_time") + .orderByDesc("publish_time") + .last("LIMIT " + limit); + + List announcements = announcementService.list(queryWrapper); + + return Result.success(announcements); + } + + /** + * 获取公告详情 + */ + @GetMapping("/{id}") + public Result getAnnouncementDetail(@PathVariable Long id) { + QueryWrapper queryWrapper = new QueryWrapper<>(); + queryWrapper.eq("id", id) + .eq("status", 1); // 只能查看已发布的公告 + + Announcement announcement = announcementService.getOne(queryWrapper); + + if (announcement == null) { + return Result.error("公告不存在或未发布"); + } + + return Result.success(announcement); + } +} diff --git a/backend/src/main/java/com/sunnyfarm/controller/CartController.java b/backend/src/main/java/com/sunnyfarm/controller/CartController.java new file mode 100644 index 0000000..585df5e --- /dev/null +++ b/backend/src/main/java/com/sunnyfarm/controller/CartController.java @@ -0,0 +1,188 @@ +package com.sunnyfarm.controller; + +import com.sunnyfarm.common.Result; +import com.sunnyfarm.dto.CartItemDTO; +import com.sunnyfarm.entity.Cart; +import com.sunnyfarm.service.CartService; +import com.sunnyfarm.util.UserContext; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.*; + +import java.util.List; +import java.util.Map; + +@RestController +@RequestMapping("/api/cart") +@CrossOrigin +public class CartController { + + @Autowired + private CartService cartService; + + /** + * 获取购物车列表 + */ + @GetMapping + public Result> getCartList() { + Long userId = UserContext.getCurrentUserId(); + List cartList = cartService.getUserCart(userId); + return Result.success(cartList); + } + + /** + * 获取购物车详细信息列表 + */ + @GetMapping("/items") + public Result> getCartItems() { + Long userId = UserContext.getCurrentUserId(); + List cartItems = cartService.getUserCartItems(userId); + return Result.success(cartItems); + } + + /** + * 添加到购物车 + */ + @PostMapping("/add") + public Result addToCart(@RequestBody Map cartData) { + try { + Long userId = UserContext.getCurrentUserId(); + Long productId = Long.valueOf(cartData.get("productId").toString()); + Integer quantity = Integer.valueOf(cartData.get("quantity").toString()); + + boolean success = cartService.addToCart(userId, productId, quantity); + if (success) { + return Result.success("添加到购物车成功"); + } else { + return Result.error("添加到购物车失败"); + } + } catch (Exception e) { + return Result.error(e.getMessage()); + } + } + + /** + * 更新购物车数量 + */ + @PutMapping("/{productId}/quantity") + public Result updateQuantity(@PathVariable Long productId, @RequestBody Map quantityData) { + try { + Long userId = UserContext.getCurrentUserId(); + Integer quantity = quantityData.get("quantity"); + + boolean success = cartService.updateCartQuantity(userId, productId, quantity); + if (success) { + return Result.success("购物车更新成功"); + } else { + return Result.error("购物车更新失败"); + } + } catch (Exception e) { + return Result.error(e.getMessage()); + } + } + + /** + * 从购物车删除商品 + */ + @DeleteMapping("/{productId}") + public Result removeFromCart(@PathVariable Long productId) { + try { + Long userId = UserContext.getCurrentUserId(); + boolean success = cartService.removeFromCart(userId, productId); + if (success) { + return Result.success("商品已从购物车删除"); + } else { + return Result.error("删除失败"); + } + } catch (Exception e) { + return Result.error(e.getMessage()); + } + } + + /** + * 清空购物车 + */ + @DeleteMapping("/clear") + public Result clearCart() { + try { + Long userId = UserContext.getCurrentUserId(); + boolean success = cartService.clearCart(userId); + if (success) { + return Result.success("购物车已清空"); + } else { + return Result.error("清空失败"); + } + } catch (Exception e) { + return Result.error(e.getMessage()); + } + } + + /** + * 获取购物车商品数量 + */ + @GetMapping("/count") + public Result getCartCount() { + Long userId = UserContext.getCurrentUserId(); + Long count = cartService.getCartCount(userId); + return Result.success(count); + } + + /** + * 批量删除购物车商品 + */ + @DeleteMapping("/batch") + public Result batchRemoveFromCart(@RequestBody Map> data) { + try { + Long userId = UserContext.getCurrentUserId(); + List productIds = data.get("productIds"); + + boolean success = cartService.batchRemoveFromCart(userId, productIds); + if (success) { + return Result.success("批量删除成功"); + } else { + return Result.error("批量删除失败"); + } + } catch (Exception e) { + return Result.error(e.getMessage()); + } + } + + /** + * 选择/取消选择购物车商品 + */ + @PutMapping("/{productId}/select") + public Result toggleSelect(@PathVariable Long productId, @RequestBody Map selectData) { + try { + Long userId = UserContext.getCurrentUserId(); + Boolean selected = selectData.get("selected"); + + boolean success = cartService.toggleCartSelect(userId, productId, selected); + if (success) { + return Result.success("选择状态更新成功"); + } else { + return Result.error("选择状态更新失败"); + } + } catch (Exception e) { + return Result.error(e.getMessage()); + } + } + + /** + * 全选/取消全选 + */ + @PutMapping("/select-all") + public Result toggleSelectAll(@RequestBody Map selectData) { + try { + Long userId = UserContext.getCurrentUserId(); + Boolean selected = selectData.get("selected"); + + boolean success = cartService.toggleAllCartSelection(userId, selected); + if (success) { + return Result.success("全选状态更新成功"); + } else { + return Result.error("全选状态更新失败"); + } + } catch (Exception e) { + return Result.error(e.getMessage()); + } + } +} diff --git a/backend/src/main/java/com/sunnyfarm/controller/CategoryController.java b/backend/src/main/java/com/sunnyfarm/controller/CategoryController.java new file mode 100644 index 0000000..38ada74 --- /dev/null +++ b/backend/src/main/java/com/sunnyfarm/controller/CategoryController.java @@ -0,0 +1,70 @@ +package com.sunnyfarm.controller; + +import com.sunnyfarm.common.Result; +import com.sunnyfarm.entity.Category; +import com.sunnyfarm.service.CategoryService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.*; + +import java.util.List; + +@RestController +@RequestMapping("/api/category") +@CrossOrigin +public class CategoryController { + + @Autowired + private CategoryService categoryService; + + /** + * 获取所有分类列表(用户端公共接口) + */ + @GetMapping("/list") + public Result> getCategoryList() { + try { + List categories = categoryService.getAllCategories(); + return Result.success(categories); + } catch (Exception e) { + return Result.error("获取分类列表失败: " + e.getMessage()); + } + } + + /** + * 获取主分类列表(用户端公共接口) + */ + @GetMapping("/main") + public Result> getMainCategories() { + try { + List categories = categoryService.getMainCategories(); + return Result.success(categories); + } catch (Exception e) { + return Result.error("获取主分类列表失败: " + e.getMessage()); + } + } + + /** + * 获取分类树形结构(用户端公共接口) + */ + @GetMapping("/tree") + public Result> getCategoryTree() { + try { + List categories = categoryService.getCategoryTree(); + return Result.success(categories); + } catch (Exception e) { + return Result.error("获取分类树失败: " + e.getMessage()); + } + } + + /** + * 根据父ID获取子分类(用户端公共接口) + */ + @GetMapping("/children/{parentId}") + public Result> getChildCategories(@PathVariable Long parentId) { + try { + List categories = categoryService.getChildCategories(parentId); + return Result.success(categories); + } catch (Exception e) { + return Result.error("获取子分类失败: " + e.getMessage()); + } + } +} diff --git a/backend/src/main/java/com/sunnyfarm/controller/ChatController.java b/backend/src/main/java/com/sunnyfarm/controller/ChatController.java new file mode 100644 index 0000000..e0b3473 --- /dev/null +++ b/backend/src/main/java/com/sunnyfarm/controller/ChatController.java @@ -0,0 +1,143 @@ +package com.sunnyfarm.controller; + +import com.sunnyfarm.common.Result; +import com.sunnyfarm.dto.SendMessageRequest; +import com.sunnyfarm.entity.ChatMessage; +import com.sunnyfarm.entity.ChatSession; +import com.sunnyfarm.service.ChatService; +import com.sunnyfarm.service.MerchantService; +import com.sunnyfarm.util.UserContext; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.*; + +import javax.validation.Valid; +import java.util.List; + +@Slf4j +@RestController +@RequestMapping("/api/chat") +@CrossOrigin +public class ChatController { + + @Autowired + private ChatService chatService; + + @Autowired + private MerchantService merchantService; + + /** + * 用户获取聊天会话列表 + */ + @GetMapping("/user/sessions") + public Result> getUserSessions() { + Long userId = UserContext.getCurrentUserId(); + List sessions = chatService.getUserSessions(userId); + return Result.success(sessions); + } + + /** + * 商家获取聊天会话列表 + */ + @GetMapping("/merchant/sessions") + public Result> getMerchantSessions() { + Long userId = UserContext.getCurrentUserId(); + Long merchantId = merchantService.getMerchantIdByUserId(userId); + if (merchantId == null) { + return Result.error("商家信息不存在"); + } + + List sessions = chatService.getMerchantSessions(merchantId); + return Result.success(sessions); + } + + /** + * 获取或创建聊天会话 + */ + @PostMapping("/session") + public Result createSession(@RequestParam Long merchantId) { + Long userId = UserContext.getCurrentUserId(); + ChatSession session = chatService.getOrCreateSession(userId, merchantId); + return Result.success(session); + } + + /** + * 获取会话消息列表 + */ + @GetMapping("/session/{sessionId}/messages") + public Result> getSessionMessages( + @PathVariable Long sessionId, + @RequestParam(defaultValue = "1") Integer page, + @RequestParam(defaultValue = "20") Integer size) { + + List messages = chatService.getSessionMessages(sessionId, page, size); + return Result.success(messages); + } + + /** + * 用户发送消息 + */ + @PostMapping("/user/send") + public Result sendUserMessage(@Valid @RequestBody SendMessageRequest request) { + Long userId = UserContext.getCurrentUserId(); + ChatMessage message = chatService.sendUserMessage(userId, request); + return Result.success(message); + } + + /** + * 商家发送消息 + */ + @PostMapping("/merchant/send") + public Result sendMerchantMessage(@RequestBody SendMessageRequest request) { + Long userId = UserContext.getCurrentUserId(); + Long merchantId = merchantService.getMerchantIdByUserId(userId); + if (merchantId == null) { + return Result.error("商家信息不存在"); + } + + ChatMessage message = chatService.sendMerchantMessage( + merchantId, + request.getMerchantId(), // 这里复用字段作为sessionId + request.getContent(), + request.getMessageType() + ); + return Result.success(message); + } + + /** + * 标记消息为已读 + */ + @PutMapping("/session/{sessionId}/read") + public Result markAsRead(@PathVariable Long sessionId) { + try { + Long userId = UserContext.getCurrentUserId(); + + // 检查是否是商家 + Long merchantId = merchantService.getMerchantIdByUserId(userId); + + boolean success; + if (merchantId != null) { + // 商家标记消息为已读 - 标记用户发送的消息(senderType=1) + success = chatService.markMerchantMessagesAsRead(sessionId, merchantId); + } else { + // 普通用户标记消息为已读 - 标记商家发送的消息(senderType=2) + success = chatService.markUserMessagesAsRead(sessionId, userId); + } + + return Result.success("标记成功"); + } catch (Exception e) { + log.error("标记消息已读失败", e); + return Result.success("标记成功"); // 返回成功避免前端错误提示 + } + } + + /** + * 获取未读消息数量 + */ + @GetMapping("/session/{sessionId}/unread") + public Result getUnreadCount(@PathVariable Long sessionId) { + Long userId = UserContext.getCurrentUserId(); + Integer count = chatService.getUnreadCount(sessionId, userId); + return Result.success(count); + } +} diff --git a/backend/src/main/java/com/sunnyfarm/controller/CommentController.java b/backend/src/main/java/com/sunnyfarm/controller/CommentController.java new file mode 100644 index 0000000..116367c --- /dev/null +++ b/backend/src/main/java/com/sunnyfarm/controller/CommentController.java @@ -0,0 +1,204 @@ +package com.sunnyfarm.controller; + +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.sunnyfarm.common.PageResult; +import com.sunnyfarm.common.Result; +import com.sunnyfarm.entity.Comment; +import com.sunnyfarm.service.CommentService; +import com.sunnyfarm.util.UserContext; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.*; + +import java.math.BigDecimal; +import java.util.Map; + +@RestController +@RequestMapping("/api/comment") +@CrossOrigin +public class CommentController { + + @Autowired + private CommentService commentService; + + /** + * 创建评论 + */ + @PostMapping("/create") + public Result createComment(@RequestBody Comment comment) { + try { + Long userId = UserContext.getCurrentUserId(); + comment.setUserId(userId); + + // 验证必填字段 + if (comment.getMerchantId() == null) { + return Result.error("商家ID不能为空"); + } + if (comment.getProductId() == null) { + return Result.error("商品ID不能为空"); + } + if (comment.getOrderId() == null) { + return Result.error("订单ID不能为空"); + } + + Comment createdComment = commentService.createComment(comment); + return Result.success("评论提交成功", createdComment); + } catch (Exception e) { + return Result.error(e.getMessage()); + } + } + + /** + * 获取产品评论列表 + */ + @GetMapping("/product/{productId}") + public Result> getProductComments( + @PathVariable Long productId, + @RequestParam(defaultValue = "1") Integer page, + @RequestParam(defaultValue = "10") Integer size) { + + Page pageObj = new Page<>(page, size); + IPage result = commentService.getProductComments(pageObj, productId); + + PageResult pageResult = PageResult.of( + result.getTotal(), + result.getPages(), + result.getCurrent(), + result.getSize(), + result.getRecords() + ); + + return Result.success(pageResult); + } + + /** + * 获取用户评论列表 + */ + @GetMapping("/user") + public Result> getUserComments( + @RequestParam(defaultValue = "1") Integer page, + @RequestParam(defaultValue = "10") Integer size) { + + Long userId = UserContext.getCurrentUserId(); + Page pageObj = new Page<>(page, size); + IPage result = commentService.getUserComments(pageObj, userId); + + PageResult pageResult = PageResult.of( + result.getTotal(), + result.getPages(), + result.getCurrent(), + result.getSize(), + result.getRecords() + ); + + return Result.success(pageResult); + } + + /** + * 获取产品平均评分 + */ + @GetMapping("/product/{productId}/rating") + public Result getProductRating(@PathVariable Long productId) { + BigDecimal rating = commentService.getProductAverageRating(productId); + return Result.success(rating); + } + + /** + * 获取产品评论数量 + */ + @GetMapping("/product/{productId}/count") + public Result getProductCommentCount(@PathVariable Long productId) { + Long count = commentService.getProductCommentCount(productId); + return Result.success(count); + } + + /** + * 检查用户是否已评论 + */ + @GetMapping("/check") + public Result checkUserComment( + @RequestParam Long productId, + @RequestParam Long orderId) { + + Long userId = UserContext.getCurrentUserId(); + boolean hasCommented = commentService.hasUserCommented(userId, productId, orderId); + return Result.success(hasCommented); + } + + /** + * 回复评论(管理员) + */ + @PutMapping("/{id}/reply") + public Result replyComment(@PathVariable Long id, @RequestBody Map replyData) { + try { + String reply = replyData.get("reply"); + boolean success = commentService.replyComment(id, reply); + if (success) { + return Result.success("回复成功"); + } else { + return Result.error("回复失败"); + } + } catch (Exception e) { + return Result.error(e.getMessage()); + } + } + + /** + * 审核评论(管理员) + */ + @PutMapping("/{id}/audit") + public Result auditComment(@PathVariable Long id, @RequestBody Map auditData) { + try { + Integer status = auditData.get("status"); + boolean success = commentService.auditComment(id, status); + if (success) { + return Result.success("审核成功"); + } else { + return Result.error("审核失败"); + } + } catch (Exception e) { + return Result.error(e.getMessage()); + } + } + + /** + * 删除评论(管理员) + */ + @DeleteMapping("/{id}") + public Result deleteComment(@PathVariable Long id) { + try { + boolean success = commentService.deleteComment(id); + if (success) { + return Result.success("评论删除成功"); + } else { + return Result.error("评论删除失败"); + } + } catch (Exception e) { + return Result.error(e.getMessage()); + } + } + + /** + * 获取所有评论(管理员) + */ + @GetMapping("/admin/list") + public Result> getAllComments( + @RequestParam(defaultValue = "1") Integer page, + @RequestParam(defaultValue = "10") Integer size, + @RequestParam(required = false) String keyword, + @RequestParam(required = false) Integer status) { + + Page pageObj = new Page<>(page, size); + IPage result = commentService.getAllComments(pageObj, keyword, status); + + PageResult pageResult = PageResult.of( + result.getTotal(), + result.getPages(), + result.getCurrent(), + result.getSize(), + result.getRecords() + ); + + return Result.success(pageResult); + } +} diff --git a/backend/src/main/java/com/sunnyfarm/controller/ConfigTestController.java b/backend/src/main/java/com/sunnyfarm/controller/ConfigTestController.java new file mode 100644 index 0000000..85e03fc --- /dev/null +++ b/backend/src/main/java/com/sunnyfarm/controller/ConfigTestController.java @@ -0,0 +1,34 @@ +package com.sunnyfarm.controller; + +import com.sunnyfarm.config.properties.AlipayProperties; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; +import org.springframework.util.StringUtils; + +import java.util.HashMap; +import java.util.Map; + +@RestController +@RequestMapping("/api/test") +public class ConfigTestController { + + @Autowired + private AlipayProperties alipayProperties; + + @GetMapping("/alipay-config") + public Map testAlipayConfig() { + Map result = new HashMap<>(); + + result.put("appId", alipayProperties.getAppId()); + result.put("hasAppPrivateKey", StringUtils.hasText(alipayProperties.getAppPrivateKey())); + result.put("appPrivateKeyLength", alipayProperties.getAppPrivateKey() != null ? alipayProperties.getAppPrivateKey().length() : 0); + result.put("hasAlipayPublicKey", StringUtils.hasText(alipayProperties.getAlipayPublicKey())); + result.put("serverUrl", alipayProperties.getServerUrl()); + result.put("notifyUrl", alipayProperties.getNotifyUrl()); + result.put("returnUrl", alipayProperties.getReturnUrl()); + + return result; + } +} diff --git a/backend/src/main/java/com/sunnyfarm/controller/FavoriteController.java b/backend/src/main/java/com/sunnyfarm/controller/FavoriteController.java new file mode 100644 index 0000000..6afaee2 --- /dev/null +++ b/backend/src/main/java/com/sunnyfarm/controller/FavoriteController.java @@ -0,0 +1,126 @@ +package com.sunnyfarm.controller; + +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.sunnyfarm.common.PageResult; +import com.sunnyfarm.common.Result; +import com.sunnyfarm.entity.Favorite; +import com.sunnyfarm.service.FavoriteService; +import com.sunnyfarm.util.UserContext; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.*; + +import java.util.List; +import java.util.Map; + +@RestController +@RequestMapping("/api/favorite") +@CrossOrigin +public class FavoriteController { + + @Autowired + private FavoriteService favoriteService; + + /** + * 添加收藏 + */ + @PostMapping("/add") + public Result addFavorite(@RequestBody Map favoriteData) { + try { + Long userId = UserContext.getCurrentUserId(); + Long productId = favoriteData.get("productId"); + + boolean success = favoriteService.addFavorite(userId, productId); + if (success) { + return Result.success("收藏成功"); + } else { + return Result.error("商品已收藏"); + } + } catch (Exception e) { + return Result.error(e.getMessage()); + } + } + + /** + * 取消收藏 + */ + @DeleteMapping("/remove") + public Result removeFavorite(@RequestBody Map favoriteData) { + try { + Long userId = UserContext.getCurrentUserId(); + Long productId = favoriteData.get("productId"); + + boolean success = favoriteService.removeFavorite(userId, productId); + if (success) { + return Result.success("取消收藏成功"); + } else { + return Result.error("取消收藏失败"); + } + } catch (Exception e) { + return Result.error(e.getMessage()); + } + } + + /** + * 检查是否已收藏 + */ + @GetMapping("/check/{productId}") + public Result checkFavorite(@PathVariable Long productId) { + Long userId = UserContext.getCurrentUserId(); + boolean isFavorited = favoriteService.isFavorited(userId, productId); + return Result.success(isFavorited); + } + + /** + * 获取用户收藏列表 + */ + @GetMapping("/list") + public Result> getFavoriteList( + @RequestParam(defaultValue = "1") Integer page, + @RequestParam(defaultValue = "10") Integer size) { + + Long userId = UserContext.getCurrentUserId(); + Page pageObj = new Page<>(page, size); + IPage result = favoriteService.getUserFavorites(pageObj, userId); + + PageResult pageResult = PageResult.of( + result.getTotal(), + result.getPages(), + result.getCurrent(), + result.getSize(), + result.getRecords() + ); + + return Result.success(pageResult); + } + + /** + * 获取用户收藏数量 + */ + @GetMapping("/count") + public Result getFavoriteCount() { + Long userId = UserContext.getCurrentUserId(); + Long count = favoriteService.getUserFavoriteCount(userId); + return Result.success(count); + } + + /** + * 批量取消收藏 + */ + @DeleteMapping("/batch") + public Result batchRemoveFavorite(@RequestBody Map> data) { + try { + Long userId = UserContext.getCurrentUserId(); + List productIds = data.get("productIds"); + + boolean success = favoriteService.batchRemoveFavorite(userId, productIds); + if (success) { + return Result.success("批量取消收藏成功"); + } else { + return Result.error("批量取消收藏失败"); + } + } catch (Exception e) { + return Result.error(e.getMessage()); + } + } +} diff --git a/backend/src/main/java/com/sunnyfarm/controller/FileController.java b/backend/src/main/java/com/sunnyfarm/controller/FileController.java new file mode 100644 index 0000000..805ccb6 --- /dev/null +++ b/backend/src/main/java/com/sunnyfarm/controller/FileController.java @@ -0,0 +1,66 @@ +package com.sunnyfarm.controller; + +import com.sunnyfarm.common.Result; +import com.sunnyfarm.util.FileUploadUtil; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.*; +import org.springframework.web.multipart.MultipartFile; + +import java.util.ArrayList; +import java.util.List; + +@RestController +@RequestMapping("/api/file") +@CrossOrigin +public class FileController { + + @Autowired + private FileUploadUtil fileUploadUtil; + + /** + * 单文件上传 + */ + @PostMapping("/upload") + public Result uploadFile(@RequestParam("file") MultipartFile file) { + try { + String fileUrl = fileUploadUtil.uploadFile(file); + return Result.success("文件上传成功", fileUrl); + } catch (Exception e) { + return Result.error("文件上传失败: " + e.getMessage()); + } + } + + /** + * 多文件上传 + */ + @PostMapping("/upload/batch") + public Result> uploadFiles(@RequestParam("files") MultipartFile[] files) { + try { + List fileUrls = new ArrayList<>(); + for (MultipartFile file : files) { + String fileUrl = fileUploadUtil.uploadFile(file); + fileUrls.add(fileUrl); + } + return Result.success("文件上传成功", fileUrls); + } catch (Exception e) { + return Result.error("文件上传失败: " + e.getMessage()); + } + } + + /** + * 删除文件 + */ + @DeleteMapping("/delete") + public Result deleteFile(@RequestParam String fileUrl) { + try { + boolean success = fileUploadUtil.deleteFile(fileUrl); + if (success) { + return Result.success("文件删除成功"); + } else { + return Result.error("文件删除失败"); + } + } catch (Exception e) { + return Result.error("文件删除失败: " + e.getMessage()); + } + } +} diff --git a/backend/src/main/java/com/sunnyfarm/controller/InitController.java b/backend/src/main/java/com/sunnyfarm/controller/InitController.java new file mode 100644 index 0000000..edd5cf8 --- /dev/null +++ b/backend/src/main/java/com/sunnyfarm/controller/InitController.java @@ -0,0 +1,82 @@ +package com.sunnyfarm.controller; + +import com.sunnyfarm.common.Result; +import com.sunnyfarm.entity.Admin; +import com.sunnyfarm.entity.Role; +import com.sunnyfarm.service.AdminService; +import com.sunnyfarm.service.RoleService; +import com.sunnyfarm.util.PasswordUtil; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.*; + +@RestController +@RequestMapping("/api/init") +@CrossOrigin +public class InitController { + + @Autowired + private AdminService adminService; + + @Autowired + private RoleService roleService; + + /** + * 初始化管理员账号 + */ + @PostMapping("/admin") + public Result initAdmin() { + try { + // 检查是否已经存在管理员 + Admin existingAdmin = adminService.getByUsername("admin"); + if (existingAdmin != null) { + return Result.error("管理员账号已存在"); + } + + // 创建管理员账号 + Admin admin = new Admin(); + admin.setUsername("admin"); + admin.setPassword(PasswordUtil.encode("admin123")); // 使用BCrypt加密 + admin.setEmail("admin@sunnyfarm.com"); + admin.setPhone("18888888888"); + admin.setRealName("系统管理员"); + admin.setRoleId(1L); + admin.setStatus(1); + + adminService.save(admin); + + return Result.success("管理员账号初始化成功!用户名: admin, 密码: admin123"); + } catch (Exception e) { + return Result.error("初始化失败: " + e.getMessage()); + } + } + + /** + * 重置管理员密码 + */ + @PostMapping("/reset-admin-password") + public Result resetAdminPassword() { + try { + Admin admin = adminService.getByUsername("admin"); + if (admin == null) { + return Result.error("管理员账号不存在"); + } + + // 重置密码为admin123 + admin.setPassword(PasswordUtil.encode("admin123")); + adminService.updateById(admin); + + return Result.success("管理员密码重置成功!新密码: admin123"); + } catch (Exception e) { + return Result.error("重置失败: " + e.getMessage()); + } + } + + /** + * 生成密码hash(用于测试) + */ + @GetMapping("/generate-password/{password}") + public Result generatePassword(@PathVariable String password) { + String encodedPassword = PasswordUtil.encode(password); + return Result.success("加密后的密码: " + encodedPassword); + } +} diff --git a/backend/src/main/java/com/sunnyfarm/controller/InventoryController.java b/backend/src/main/java/com/sunnyfarm/controller/InventoryController.java new file mode 100644 index 0000000..04e7908 --- /dev/null +++ b/backend/src/main/java/com/sunnyfarm/controller/InventoryController.java @@ -0,0 +1,133 @@ +package com.sunnyfarm.controller; + +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.sunnyfarm.common.PageResult; +import com.sunnyfarm.common.Result; +import com.sunnyfarm.dto.InventoryWithProduct; +import com.sunnyfarm.entity.Inventory; +import com.sunnyfarm.service.InventoryService; +import com.sunnyfarm.service.MerchantService; +import com.sunnyfarm.util.UserContext; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.*; + +import java.util.List; +import java.util.Map; + +@RestController +@RequestMapping("/api/inventory") +@CrossOrigin +public class InventoryController { + + @Autowired + private InventoryService inventoryService; + + @Autowired + private MerchantService merchantService; + + /** + * 获取商家库存统计 + */ + @GetMapping("/stats") + public Result getInventoryStats() { + Long userId = UserContext.getCurrentUserId(); + Long merchantId = merchantService.getMerchantIdByUserId(userId); + if (merchantId == null) { + return Result.error("商家信息不存在"); + } + + InventoryService.InventoryStatistics stats = inventoryService.getMerchantInventoryStats(merchantId); + return Result.success(stats); + } + + /** + * 分页获取商家库存列表 + */ + @GetMapping("/page") + public Result> getInventoryPage( + @RequestParam(defaultValue = "1") Integer page, + @RequestParam(defaultValue = "10") Integer size, + @RequestParam(required = false) String keyword) { + + Long userId = UserContext.getCurrentUserId(); + Long merchantId = merchantService.getMerchantIdByUserId(userId); + if (merchantId == null) { + return Result.error("商家信息不存在"); + } + + Page pageObj = new Page<>(page, size); + IPage result = inventoryService.getMerchantInventoryPage(pageObj, merchantId, keyword); + + PageResult pageResult = PageResult.of( + result.getTotal(), + result.getPages(), + result.getCurrent(), + result.getSize(), + result.getRecords() + ); + + return Result.success(pageResult); + } + + /** + * 更新库存数量 + */ + @PutMapping("/{productId}/stock") + public Result updateStock(@PathVariable Long productId, @RequestBody Map data) { + try { + Integer quantity = data.get("quantity"); + boolean success = inventoryService.updateStockQuantity(productId, quantity); + + if (success) { + return Result.success("库存更新成功"); + } else { + return Result.error("库存更新失败"); + } + } catch (Exception e) { + return Result.error(e.getMessage()); + } + } + + /** + * 设置预警数量 + */ + @PutMapping("/{productId}/warning") + public Result setWarningQuantity(@PathVariable Long productId, @RequestBody Map data) { + try { + Integer warningQuantity = data.get("warningQuantity"); + boolean success = inventoryService.setWarningQuantity(productId, warningQuantity); + + if (success) { + return Result.success("预警数量设置成功"); + } else { + return Result.error("预警数量设置失败"); + } + } catch (Exception e) { + return Result.error(e.getMessage()); + } + } + + /** + * 批量更新库存 + */ + @PutMapping("/batch") + public Result batchUpdateStock(@RequestBody Map data) { + try { + @SuppressWarnings("unchecked") + List productIds = (List) data.get("productIds"); + @SuppressWarnings("unchecked") + List quantities = (List) data.get("quantities"); + + boolean success = inventoryService.batchUpdateStock(productIds, quantities); + + if (success) { + return Result.success("批量更新成功"); + } else { + return Result.error("批量更新失败"); + } + } catch (Exception e) { + return Result.error(e.getMessage()); + } + } +} diff --git a/backend/src/main/java/com/sunnyfarm/controller/MerchantController.java b/backend/src/main/java/com/sunnyfarm/controller/MerchantController.java new file mode 100644 index 0000000..e740090 --- /dev/null +++ b/backend/src/main/java/com/sunnyfarm/controller/MerchantController.java @@ -0,0 +1,431 @@ +package com.sunnyfarm.controller; + +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.sunnyfarm.common.PageResult; +import com.sunnyfarm.common.Result; +import com.sunnyfarm.dto.MerchantRegisterRequest; +import com.sunnyfarm.dto.MerchantDashboardDTO; +import com.sunnyfarm.entity.Merchant; +import com.sunnyfarm.entity.Product; +import com.sunnyfarm.service.MerchantService; +import com.sunnyfarm.service.ProductService; +import com.sunnyfarm.util.JwtUtil; +import com.sunnyfarm.util.UserContext; +import com.sunnyfarm.util.FileUploadUtil; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.*; +import org.springframework.web.multipart.MultipartFile; + +import javax.validation.Valid; +import java.util.HashMap; +import java.util.Map; + +@RestController +@RequestMapping("/api/merchant") +@CrossOrigin +public class MerchantController { + + @Autowired + private MerchantService merchantService; + + @Autowired + private ProductService productService; + + @Autowired + private JwtUtil jwtUtil; + + @Autowired + private FileUploadUtil fileUploadUtil; + + /** + * 商家注册 + */ + @PostMapping("/register") + public Result register(@Valid @RequestBody MerchantRegisterRequest request) { + try { + boolean success = merchantService.register(request); + if (success) { + return Result.success("商家注册成功,请等待审核"); + } else { + return Result.error("商家注册失败"); + } + } catch (Exception e) { + return Result.error(e.getMessage()); + } + } + + /** + * 商家登录 + */ + @PostMapping("/login") + public Result> login(@RequestBody Map loginData) { + try { + String username = loginData.get("username"); + String password = loginData.get("password"); + + Merchant merchant = merchantService.login(username, password); + if (merchant != null) { + String token = jwtUtil.generateMerchantToken( + merchant.getUser().getId(), + merchant.getUser().getUsername(), + merchant.getId() + ); + + Map result = new HashMap<>(); + result.put("token", token); + result.put("user", merchant.getUser()); + result.put("merchant", merchant); + + return Result.success("登录成功", result); + } else { + return Result.error("登录失败"); + } + } catch (Exception e) { + return Result.error(e.getMessage()); + } + } + + /** + * 获取当前商家信息 + */ + @GetMapping("/profile") + public Result getProfile() { + Long userId = UserContext.getCurrentUserId(); + Merchant merchant = merchantService.getByUserId(userId); + return Result.success(merchant); + } + + /** + * 更新商家信息 + */ + @PutMapping("/profile") + public Result updateProfile(@RequestBody Merchant merchant) { + try { + Long userId = UserContext.getCurrentUserId(); + Merchant currentMerchant = merchantService.getByUserId(userId); + if (currentMerchant == null) { + return Result.error("商家信息不存在"); + } + + merchant.setId(currentMerchant.getId()); + merchant.setUserId(userId); + + boolean success = merchantService.updateMerchantInfo(merchant); + if (success) { + // 重新查询更新后的商家信息 + Merchant updatedMerchant = merchantService.getByUserId(userId); + return Result.success("更新成功", updatedMerchant); + } else { + return Result.error("更新失败"); + } + } catch (Exception e) { + return Result.error(e.getMessage()); + } + } + + /** + * 获取商家列表(管理员) + */ + @GetMapping("/list") + public Result> getMerchantList( + @RequestParam(defaultValue = "1") Integer page, + @RequestParam(defaultValue = "10") Integer size, + @RequestParam(required = false) String keyword, + @RequestParam(required = false) Integer status) { + + Page pageObj = new Page<>(page, size); + IPage result = merchantService.getMerchantPage(pageObj, keyword, status); + + PageResult pageResult = PageResult.of( + result.getTotal(), + result.getPages(), + result.getCurrent(), + result.getSize(), + result.getRecords() + ); + + return Result.success(pageResult); + } + + /** + * 审核商家(管理员) + */ + @PutMapping("/{id}/verify") + public Result verifyMerchant(@PathVariable Long id, @RequestBody Map verifyData) { + try { + Integer status = (Integer) verifyData.get("status"); + String remark = (String) verifyData.get("remark"); + + boolean success = merchantService.verifyMerchant(id, status, remark); + if (success) { + return Result.success("审核完成"); + } else { + return Result.error("审核失败"); + } + } catch (Exception e) { + return Result.error(e.getMessage()); + } + } + + /** + * 获取商家Dashboard数据 + */ + @GetMapping("/dashboard") + public Result getDashboard() { + try { + Long userId = UserContext.getCurrentUserId(); + Merchant merchant = merchantService.getByUserId(userId); + if (merchant == null) { + return Result.error("商家信息不存在"); + } + + MerchantDashboardDTO dashboard = merchantService.getDashboardData(merchant.getId()); + return Result.success(dashboard); + } catch (Exception e) { + return Result.error(e.getMessage()); + } + } + + // ==================== 商品管理相关接口 ==================== + + /** + * 获取商家商品列表 + */ + @GetMapping("/products") + public Result> getMerchantProducts( + @RequestParam(defaultValue = "1") Integer page, + @RequestParam(defaultValue = "20") Integer size, + @RequestParam(required = false) String keyword, + @RequestParam(required = false) Integer status) { + try { + Long userId = UserContext.getCurrentUserId(); + Merchant merchant = merchantService.getByUserId(userId); + if (merchant == null) { + return Result.error("商家信息不存在"); + } + + Page pageObj = new Page<>(page, size); + IPage result = productService.getMerchantProductPage(pageObj, merchant.getId(), keyword, status); + + PageResult pageResult = PageResult.of( + result.getTotal(), + result.getPages(), + result.getCurrent(), + result.getSize(), + result.getRecords() + ); + + return Result.success(pageResult); + } catch (Exception e) { + return Result.error(e.getMessage()); + } + } + + /** + * 创建商品 + */ + @PostMapping("/products") + public Result createMerchantProduct(@RequestBody Product product) { + try { + Long userId = UserContext.getCurrentUserId(); + Merchant merchant = merchantService.getByUserId(userId); + if (merchant == null) { + return Result.error("商家信息不存在"); + } + + product.setMerchantId(merchant.getId()); + Product createdProduct = productService.createMerchantProduct(product); + return Result.success("商品创建成功", createdProduct); + } catch (Exception e) { + return Result.error(e.getMessage()); + } + } + + /** + * 更新商品 + */ + @PutMapping("/products/{id}") + public Result updateMerchantProduct(@PathVariable Long id, @RequestBody Product product) { + try { + Long userId = UserContext.getCurrentUserId(); + Merchant merchant = merchantService.getByUserId(userId); + if (merchant == null) { + return Result.error("商家信息不存在"); + } + + // 检查商品是否属于当前商家 + Product existingProduct = productService.getById(id); + if (existingProduct == null || !existingProduct.getMerchantId().equals(merchant.getId())) { + return Result.error("商品不存在或无权限操作"); + } + + product.setId(id); + product.setMerchantId(merchant.getId()); + Product updatedProduct = productService.updateMerchantProduct(product); + return Result.success("商品更新成功", updatedProduct); + } catch (Exception e) { + return Result.error(e.getMessage()); + } + } + + /** + * 删除商品 + */ + @DeleteMapping("/products/{id}") + public Result deleteMerchantProduct(@PathVariable Long id) { + try { + Long userId = UserContext.getCurrentUserId(); + Merchant merchant = merchantService.getByUserId(userId); + if (merchant == null) { + return Result.error("商家信息不存在"); + } + + // 检查商品是否属于当前商家 + Product existingProduct = productService.getById(id); + if (existingProduct == null || !existingProduct.getMerchantId().equals(merchant.getId())) { + return Result.error("商品不存在或无权限操作"); + } + + boolean success = productService.deleteMerchantProduct(id); + if (success) { + return Result.success("商品删除成功"); + } else { + return Result.error("商品删除失败"); + } + } catch (Exception e) { + return Result.error(e.getMessage()); + } + } + + /** + * 上架/下架商品 + */ + @PutMapping("/products/{id}/status") + public Result updateMerchantProductStatus(@PathVariable Long id, @RequestBody Map statusData) { + try { + Long userId = UserContext.getCurrentUserId(); + Merchant merchant = merchantService.getByUserId(userId); + if (merchant == null) { + return Result.error("商家信息不存在"); + } + + // 检查商品是否属于当前商家 + Product existingProduct = productService.getById(id); + if (existingProduct == null || !existingProduct.getMerchantId().equals(merchant.getId())) { + return Result.error("商品不存在或无权限操作"); + } + + Integer status = statusData.get("status"); + boolean success = productService.updateMerchantProductStatus(id, status); + if (success) { + String action = status == 1 ? "上架" : "下架"; + return Result.success("商品" + action + "成功"); + } else { + return Result.error("操作失败"); + } + } catch (Exception e) { + return Result.error(e.getMessage()); + } + } + + /** + * 获取商品详情 + */ + @GetMapping("/products/{id}") + public Result getMerchantProductDetail(@PathVariable Long id) { + try { + Long userId = UserContext.getCurrentUserId(); + Merchant merchant = merchantService.getByUserId(userId); + if (merchant == null) { + return Result.error("商家信息不存在"); + } + + Product product = productService.getMerchantProductDetail(id, merchant.getId()); + if (product != null) { + return Result.success(product); + } else { + return Result.error("商品不存在或无权限查看"); + } + } catch (Exception e) { + return Result.error(e.getMessage()); + } + } + + /** + * 获取商家信息(用于设置页面) + */ + @GetMapping("/info") + public Result getMerchantInfo() { + try { + Long userId = UserContext.getCurrentUserId(); + Merchant merchant = merchantService.getByUserId(userId); + if (merchant == null) { + return Result.error("商家信息不存在"); + } + return Result.success(merchant); + } catch (Exception e) { + return Result.error(e.getMessage()); + } + } + + /** + * 更新商家信息(用于设置页面) + */ + @PutMapping("/info") + public Result updateMerchantInfo(@RequestBody Merchant merchant) { + try { + Long userId = UserContext.getCurrentUserId(); + Merchant currentMerchant = merchantService.getByUserId(userId); + if (currentMerchant == null) { + return Result.error("商家信息不存在"); + } + + merchant.setId(currentMerchant.getId()); + merchant.setUserId(userId); + + boolean success = merchantService.updateMerchantInfo(merchant); + if (success) { + return Result.success("更新成功"); + } else { + return Result.error("更新失败"); + } + } catch (Exception e) { + return Result.error(e.getMessage()); + } + } + + /** + * 修改密码 + */ + @PutMapping("/password") + public Result changePassword(@RequestBody Map passwordData) { + try { + Long userId = UserContext.getCurrentUserId(); + String oldPassword = passwordData.get("oldPassword"); + String newPassword = passwordData.get("newPassword"); + + boolean success = merchantService.changePassword(userId, oldPassword, newPassword); + if (success) { + return Result.success("密码修改成功"); + } else { + return Result.error("原密码错误"); + } + } catch (Exception e) { + return Result.error(e.getMessage()); + } + } + + /** + * 上传商品图片 + */ + @PostMapping("/products/upload/image") + public Result uploadProductImage(@RequestParam("file") MultipartFile file) { + try { + String imageUrl = fileUploadUtil.uploadFile(file); + return Result.success("图片上传成功", imageUrl); + } catch (Exception e) { + return Result.error("图片上传失败: " + e.getMessage()); + } + } +} diff --git a/backend/src/main/java/com/sunnyfarm/controller/OrderController.java b/backend/src/main/java/com/sunnyfarm/controller/OrderController.java new file mode 100644 index 0000000..6c3bd53 --- /dev/null +++ b/backend/src/main/java/com/sunnyfarm/controller/OrderController.java @@ -0,0 +1,212 @@ +package com.sunnyfarm.controller; + +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.sunnyfarm.common.PageResult; +import com.sunnyfarm.common.Result; +import com.sunnyfarm.dto.OrderCreateRequest; +import com.sunnyfarm.entity.Order; +import com.sunnyfarm.service.OrderService; +import com.sunnyfarm.util.UserContext; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.*; + +import javax.validation.Valid; +import java.util.Map; + +@RestController +@RequestMapping("/api/order") +@CrossOrigin +public class OrderController { + + @Autowired + private OrderService orderService; + + /** + * 创建订单(支持购物车结算和立即购买) + */ + @PostMapping("/create") + public Result createOrder(@Valid @RequestBody OrderCreateRequest request) { + try { + Long userId = UserContext.getCurrentUserId(); + Order order = orderService.createOrder(userId, request); + return Result.success("订单创建成功", order); + } catch (Exception e) { + return Result.error(e.getMessage()); + } + } + + /** + * 直接购买创建订单(保持向后兼容) + */ + @PostMapping("/buy-now") + public Result buyNow(@RequestBody Map orderData) { + try { + Long userId = UserContext.getCurrentUserId(); + Long addressId = Long.valueOf(orderData.get("addressId").toString()); + String remark = (String) orderData.get("remark"); + + Order order = orderService.createOrder(userId, addressId, remark); + return Result.success("订单创建成功", order); + } catch (Exception e) { + return Result.error(e.getMessage()); + } + } + + /** + * 获取用户订单列表 + */ + @GetMapping("/list") + public Result> getOrderList( + @RequestParam(defaultValue = "1") Integer page, + @RequestParam(defaultValue = "10") Integer size, + @RequestParam(required = false) Integer status) { + + Long userId = UserContext.getCurrentUserId(); + Page pageObj = new Page<>(page, size); + IPage result = orderService.getUserOrders(pageObj, userId, status); + + PageResult pageResult = PageResult.of( + result.getTotal(), + result.getPages(), + result.getCurrent(), + result.getSize(), + result.getRecords() + ); + + return Result.success(pageResult); + } + + /** + * 获取订单详情 + */ + @GetMapping("/{orderId}") + public Result getOrderDetail(@PathVariable Long orderId) { + try { + Long userId = UserContext.getCurrentUserId(); + Order order = orderService.getOrderDetail(orderId, userId); + if (order == null) { + return Result.error("订单不存在"); + } + return Result.success(order); + } catch (Exception e) { + return Result.error(e.getMessage()); + } + } + + /** + * 支付订单 + */ + @PostMapping("/{orderId}/pay") + public Result payOrder(@PathVariable Long orderId, @RequestBody Map payData) { + try { + String payTransactionId = payData.get("payTransactionId"); + boolean success = orderService.payOrder(orderId, payTransactionId); + if (success) { + return Result.success("支付成功"); + } else { + return Result.error("支付失败"); + } + } catch (Exception e) { + return Result.error(e.getMessage()); + } + } + + /** + * 取消订单 + */ + @PostMapping("/{orderId}/cancel") + public Result cancelOrder(@PathVariable Long orderId, @RequestBody Map cancelData) { + try { + Long userId = UserContext.getCurrentUserId(); + String reason = cancelData.get("reason"); + boolean success = orderService.cancelOrder(orderId, userId); + if (success) { + return Result.success("订单已取消"); + } else { + return Result.error("取消失败"); + } + } catch (Exception e) { + return Result.error(e.getMessage()); + } + } + + /** + * 确认收货 + */ + @PostMapping("/{orderId}/confirm") + public Result confirmOrder(@PathVariable Long orderId) { + try { + Long userId = UserContext.getCurrentUserId(); + boolean success = orderService.confirmOrder(orderId, userId); + if (success) { + return Result.success("确认收货成功"); + } else { + return Result.error("确认收货失败"); + } + } catch (Exception e) { + return Result.error(e.getMessage()); + } + } + + /** + * 删除订单 + */ + @DeleteMapping("/{orderId}") + public Result deleteOrder(@PathVariable Long orderId) { + try { + Long userId = UserContext.getCurrentUserId(); + boolean success = orderService.deleteOrder(orderId, userId); + if (success) { + return Result.success("订单已删除"); + } else { + return Result.error("删除失败"); + } + } catch (Exception e) { + return Result.error(e.getMessage()); + } + } + + /** + * 商家获取订单列表 + */ + @GetMapping("/merchant/list") + public Result> getMerchantOrderList( + @RequestParam(defaultValue = "1") Integer page, + @RequestParam(defaultValue = "10") Integer size, + @RequestParam(required = false) Integer status) { + + Long merchantId = UserContext.getMerchantId(); + Page pageObj = new Page<>(page, size); + IPage result = orderService.getMerchantOrders(pageObj, merchantId, status); + + PageResult pageResult = PageResult.of( + result.getTotal(), + result.getPages(), + result.getCurrent(), + result.getSize(), + result.getRecords() + ); + + return Result.success(pageResult); + } + + /** + * 商家发货 + */ + @PostMapping("/{orderId}/ship") + public Result shipOrder(@PathVariable Long orderId, @RequestBody Map shipData) { + try { + String shipCompany = shipData.get("shipCompany"); + String shipNo = shipData.get("shipNo"); + boolean success = orderService.shipOrder(orderId, shipCompany, shipNo); + if (success) { + return Result.success("发货成功"); + } else { + return Result.error("发货失败"); + } + } catch (Exception e) { + return Result.error(e.getMessage()); + } + } +} diff --git a/backend/src/main/java/com/sunnyfarm/controller/PaymentController.java b/backend/src/main/java/com/sunnyfarm/controller/PaymentController.java new file mode 100644 index 0000000..b0d92ca --- /dev/null +++ b/backend/src/main/java/com/sunnyfarm/controller/PaymentController.java @@ -0,0 +1,192 @@ +package com.sunnyfarm.controller; + +import com.sunnyfarm.common.Result; +import com.sunnyfarm.entity.Order; +import com.sunnyfarm.entity.Payment; +import com.sunnyfarm.service.AlipayService; +import com.sunnyfarm.service.OrderService; +import com.sunnyfarm.service.PaymentService; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.*; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.math.BigDecimal; +import java.util.HashMap; +import java.util.Map; + +@Slf4j +@RestController +@RequestMapping("/api/payment") +@CrossOrigin +public class PaymentController { + + @Autowired + private AlipayService alipayService; + + @Autowired + private OrderService orderService; + + @Autowired + private PaymentService paymentService; + + /** + * 创建支付宝支付表单 + */ + @PostMapping("/alipay/create") + public Result createAlipay(@RequestBody Map payData) { + try { + Long orderId = Long.valueOf(payData.get("orderId").toString()); + + // 获取订单信息 + Order order = orderService.getById(orderId); + if (order == null) { + return Result.error("订单不存在"); + } + + if (order.getStatus() != 1) { + return Result.error("订单状态不正确,无法支付"); + } + + String subject = "农产品订单-" + order.getOrderNo(); + BigDecimal amount = order.getActualAmount(); + + String payForm = alipayService.createPayForm(orderId, subject, amount); + + return Result.success("创建支付成功", payForm); + + } catch (Exception e) { + log.error("创建支付失败", e); + return Result.error("创建支付失败: " + e.getMessage()); + } + } + + /** + * 支付宝异步回调 + */ + @PostMapping("/alipay/notify") + public String alipayNotify(HttpServletRequest request) { + try { + Map params = new HashMap<>(); + Map requestParams = request.getParameterMap(); + + for (String name : requestParams.keySet()) { + String[] values = requestParams.get(name); + String valueStr = ""; + for (int i = 0; i < values.length; i++) { + valueStr = (i == values.length - 1) ? valueStr + values[i] : valueStr + values[i] + ","; + } + params.put(name, valueStr); + } + + log.info("收到支付宝回调: {}", params); + + boolean success = alipayService.handlePayNotify(params); + + if (success) { + return "success"; + } else { + return "failure"; + } + + } catch (Exception e) { + log.error("处理支付回调异常", e); + return "failure"; + } + } + + /** + * 支付宝同步返回 + */ + @GetMapping("/alipay/return") + public void alipayReturn(HttpServletRequest request, HttpServletResponse response) throws IOException { + try { + Map params = new HashMap<>(); + Map requestParams = request.getParameterMap(); + + for (String name : requestParams.keySet()) { + String[] values = requestParams.get(name); + String valueStr = ""; + for (int i = 0; i < values.length; i++) { + valueStr = (i == values.length - 1) ? valueStr + values[i] : valueStr + values[i] + ","; + } + params.put(name, valueStr); + } + + boolean verifyResult = alipayService.verifySign(params); + + if (verifyResult) { + String outTradeNo = params.get("out_trade_no"); + String tradeStatus = params.get("trade_status"); + + if ("TRADE_SUCCESS".equals(tradeStatus)) { + // 支付成功,跳转到成功页面 + response.sendRedirect("http://localhost:3000/orders?status=2&payment=success"); + } else { + // 支付失败,跳转到失败页面 + response.sendRedirect("http://localhost:3000/orders?payment=failed"); + } + } else { + response.sendRedirect("http://localhost:3000/orders?payment=error"); + } + + } catch (Exception e) { + log.error("处理支付返回异常", e); + response.sendRedirect("http://localhost:3000/orders?payment=error"); + } + } + + /** + * 查询支付结果 + */ + @GetMapping("/query/{paymentNo}") + public Result> queryPayResult(@PathVariable String paymentNo) { + try { + Map result = alipayService.queryPayResult(paymentNo); + return Result.success("查询成功", result); + } catch (Exception e) { + log.error("查询支付结果异常", e); + return Result.error("查询失败: " + e.getMessage()); + } + } + + /** + * 申请退款 + */ + @PostMapping("/refund") + public Result> refund(@RequestBody Map refundData) { + try { + String paymentNo = (String) refundData.get("paymentNo"); + BigDecimal refundAmount = new BigDecimal(refundData.get("refundAmount").toString()); + String refundReason = (String) refundData.get("refundReason"); + + Map result = alipayService.refund(paymentNo, refundAmount, refundReason); + + if ((Boolean) result.get("success")) { + return Result.success("退款成功", result); + } else { + return Result.error("退款失败: " + result.get("msg")); + } + + } catch (Exception e) { + log.error("退款异常", e); + return Result.error("退款失败: " + e.getMessage()); + } + } + + /** + * 获取订单支付记录 + */ + @GetMapping("/order/{orderId}") + public Result getOrderPayment(@PathVariable Long orderId) { + try { + Payment payment = paymentService.getByOrderId(orderId); + return Result.success("获取成功", payment); + } catch (Exception e) { + log.error("获取支付记录异常", e); + return Result.error("获取失败: " + e.getMessage()); + } + } +} diff --git a/backend/src/main/java/com/sunnyfarm/controller/PaymentStatusController.java b/backend/src/main/java/com/sunnyfarm/controller/PaymentStatusController.java new file mode 100644 index 0000000..ddface9 --- /dev/null +++ b/backend/src/main/java/com/sunnyfarm/controller/PaymentStatusController.java @@ -0,0 +1,116 @@ +package com.sunnyfarm.controller; + +import com.sunnyfarm.common.Result; +import com.sunnyfarm.entity.Order; +import com.sunnyfarm.entity.Payment; +import com.sunnyfarm.service.AlipayService; +import com.sunnyfarm.service.OrderService; +import com.sunnyfarm.service.PaymentService; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.*; + +import java.util.HashMap; +import java.util.Map; + +@Slf4j +@RestController +@RequestMapping("/api/payment/status") +@CrossOrigin +public class PaymentStatusController { + + @Autowired + private PaymentService paymentService; + + @Autowired + private OrderService orderService; + + @Autowired + private AlipayService alipayService; + + /** + * 检查订单支付状态 + */ + @GetMapping("/check/{orderId}") + public Result> checkPaymentStatus(@PathVariable Long orderId) { + try { + Map result = new HashMap<>(); + + // 获取订单信息 + Order order = orderService.getById(orderId); + if (order == null) { + return Result.error("订单不存在"); + } + + result.put("orderStatus", order.getStatus()); + result.put("orderStatusText", getOrderStatusText(order.getStatus())); + + // 获取支付记录 + Payment payment = paymentService.getByOrderId(orderId); + if (payment != null) { + result.put("paymentStatus", payment.getStatus()); + result.put("paymentStatusText", getPaymentStatusText(payment.getStatus())); + result.put("paymentNo", payment.getPaymentNo()); + + // 如果支付记录状态为待支付,查询支付宝最新状态 + if (payment.getStatus() == 1 && order.getStatus() == 1) { + log.info("🔍 订单{}支付状态待确认,查询支付宝最新状态", orderId); + Map alipayResult = alipayService.queryPayResult(payment.getPaymentNo()); + + if ((Boolean) alipayResult.get("success")) { + String tradeStatus = (String) alipayResult.get("tradeStatus"); + if ("TRADE_SUCCESS".equals(tradeStatus)) { + log.info("🔄 发现支付成功但状态未更新,手动同步状态"); + // 手动同步支付状态 + Map syncParams = new HashMap<>(); + syncParams.put("out_trade_no", payment.getPaymentNo()); + syncParams.put("trade_status", "TRADE_SUCCESS"); + syncParams.put("trade_no", (String) alipayResult.get("tradeNo")); + syncParams.put("buyer_pay_amount", (String) alipayResult.get("buyerPayAmount")); + syncParams.put("buyer_logon_id", (String) alipayResult.get("buyerLogonId")); + + alipayService.handlePayNotify(syncParams); + + // 重新获取最新状态 + order = orderService.getById(orderId); + payment = paymentService.getByOrderId(orderId); + + result.put("orderStatus", order.getStatus()); + result.put("orderStatusText", getOrderStatusText(order.getStatus())); + result.put("paymentStatus", payment.getStatus()); + result.put("paymentStatusText", getPaymentStatusText(payment.getStatus())); + } + } + } + } + + return Result.success("查询成功", result); + + } catch (Exception e) { + log.error("查询支付状态异常", e); + return Result.error("查询失败: " + e.getMessage()); + } + } + + private String getOrderStatusText(Integer status) { + switch (status) { + case 1: return "待支付"; + case 2: return "已支付"; + case 3: return "已发货"; + case 4: return "已完成"; + case 5: return "已取消"; + case 6: return "已退款"; + default: return "未知状态"; + } + } + + private String getPaymentStatusText(Integer status) { + switch (status) { + case 1: return "待支付"; + case 2: return "已支付"; + case 3: return "支付失败"; + case 4: return "已退款"; + default: return "未知状态"; + } + } +} diff --git a/backend/src/main/java/com/sunnyfarm/controller/ProductController.java b/backend/src/main/java/com/sunnyfarm/controller/ProductController.java new file mode 100644 index 0000000..c635ee0 --- /dev/null +++ b/backend/src/main/java/com/sunnyfarm/controller/ProductController.java @@ -0,0 +1,227 @@ +package com.sunnyfarm.controller; + +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.sunnyfarm.common.PageResult; +import com.sunnyfarm.common.Result; +import com.sunnyfarm.entity.Product; +import com.sunnyfarm.service.ProductService; +import com.sunnyfarm.util.FileUploadUtil; +import com.sunnyfarm.util.UserContext; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.*; +import org.springframework.web.multipart.MultipartFile; + +import java.math.BigDecimal; +import java.util.List; +import java.util.Map; + +@RestController +@RequestMapping("/api/product") +@CrossOrigin +public class ProductController { + + @Autowired + private ProductService productService; + + @Autowired + private FileUploadUtil fileUploadUtil; + + /** + * 获取产品列表 + */ + @GetMapping("/list") + public Result> getProductList( + @RequestParam(defaultValue = "1") Integer page, + @RequestParam(defaultValue = "12") Integer size, + @RequestParam(required = false) String keyword, + @RequestParam(required = false) Long categoryId, + @RequestParam(required = false) BigDecimal minPrice, + @RequestParam(required = false) BigDecimal maxPrice, + @RequestParam(required = false) String sort) { + + Page pageObj = new Page<>(page, size); + IPage result = productService.getProductList(pageObj, keyword, categoryId, minPrice, maxPrice, sort); + + PageResult pageResult = PageResult.of( + result.getTotal(), + result.getPages(), + result.getCurrent(), + result.getSize(), + result.getRecords() + ); + + return Result.success(pageResult); + } + + /** + * 获取产品详情 + */ + @GetMapping("/{id}") + public Result getProductDetail(@PathVariable Long id) { + Product product = productService.getProductDetail(id); + if (product != null) { + return Result.success(product); + } else { + return Result.error("产品不存在"); + } + } + + /** + * 获取热门产品 + */ + @GetMapping("/hot") + public Result> getHotProducts(@RequestParam(defaultValue = "8") Integer limit) { + List products = productService.getHotProducts(limit); + return Result.success(products); + } + + /** + * 获取新品推荐 + */ + @GetMapping("/new") + public Result> getNewProducts(@RequestParam(defaultValue = "8") Integer limit) { + List products = productService.getNewProducts(limit); + return Result.success(products); + } + + /** + * 获取分类产品 + */ + @GetMapping("/category/{categoryId}") + public Result> getCategoryProducts( + @PathVariable Long categoryId, + @RequestParam(defaultValue = "1") Integer page, + @RequestParam(defaultValue = "12") Integer size, + @RequestParam(required = false) String sort) { + + Page pageObj = new Page<>(page, size); + IPage result = productService.getCategoryProducts(pageObj, categoryId, sort); + + PageResult pageResult = PageResult.of( + result.getTotal(), + result.getPages(), + result.getCurrent(), + result.getSize(), + result.getRecords() + ); + + return Result.success(pageResult); + } + + /** + * 搜索产品 + */ + @GetMapping("/search") + public Result> searchProducts( + @RequestParam String keyword, + @RequestParam(defaultValue = "1") Integer page, + @RequestParam(defaultValue = "12") Integer size, + @RequestParam(required = false) String sort) { + + Page pageObj = new Page<>(page, size); + IPage result = productService.searchProducts(pageObj, keyword, sort); + + PageResult pageResult = PageResult.of( + result.getTotal(), + result.getPages(), + result.getCurrent(), + result.getSize(), + result.getRecords() + ); + + return Result.success(pageResult); + } + + /** + * 创建产品(管理员) + */ + @PostMapping + public Result createProduct(@RequestBody Product product) { + try { + Product createdProduct = productService.createProduct(product); + return Result.success("产品创建成功", createdProduct); + } catch (Exception e) { + return Result.error(e.getMessage()); + } + } + + /** + * 更新产品(管理员) + */ + @PutMapping("/{id}") + public Result updateProduct(@PathVariable Long id, @RequestBody Product product) { + try { + Product updatedProduct = productService.updateProduct(id, product); + return Result.success("产品更新成功", updatedProduct); + } catch (Exception e) { + return Result.error(e.getMessage()); + } + } + + /** + * 删除产品(管理员) + */ + @DeleteMapping("/{id}") + public Result deleteProduct(@PathVariable Long id) { + try { + boolean success = productService.deleteProduct(id); + if (success) { + return Result.success("产品删除成功"); + } else { + return Result.error("产品删除失败"); + } + } catch (Exception e) { + return Result.error(e.getMessage()); + } + } + + /** + * 上传产品图片 + */ + @PostMapping("/upload") + public Result uploadImage(@RequestParam("file") MultipartFile file) { + try { + String imageUrl = fileUploadUtil.uploadFile(file); + return Result.success("图片上传成功", imageUrl); + } catch (Exception e) { + return Result.error("图片上传失败: " + e.getMessage()); + } + } + + /** + * 更新产品库存(管理员) + */ + @PutMapping("/{id}/stock") + public Result updateStock(@PathVariable Long id, @RequestBody Map stockData) { + try { + Integer stock = stockData.get("stock"); + boolean success = productService.updateProductStock(id, stock); + if (success) { + return Result.success("库存更新成功"); + } else { + return Result.error("库存更新失败"); + } + } catch (Exception e) { + return Result.error(e.getMessage()); + } + } + + /** + * 更新产品状态(管理员) + */ + @PutMapping("/{id}/status") + public Result updateStatus(@PathVariable Long id, @RequestBody Map statusData) { + try { + Integer status = statusData.get("status"); + boolean success = productService.updateProductStatus(id, status); + if (success) { + return Result.success("状态更新成功"); + } else { + return Result.error("状态更新失败"); + } + } catch (Exception e) { + return Result.error(e.getMessage()); + } + } +} diff --git a/backend/src/main/java/com/sunnyfarm/controller/StatisticsController.java b/backend/src/main/java/com/sunnyfarm/controller/StatisticsController.java new file mode 100644 index 0000000..6b2505e --- /dev/null +++ b/backend/src/main/java/com/sunnyfarm/controller/StatisticsController.java @@ -0,0 +1,55 @@ +package com.sunnyfarm.controller; + +import com.sunnyfarm.common.Result; +import com.sunnyfarm.service.StatisticsService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.*; + +import java.util.Map; + +@RestController +@RequestMapping("/api/statistics") +@CrossOrigin +public class StatisticsController { + + @Autowired + private StatisticsService statisticsService; + + /** + * 获取系统统计数据(管理员) + */ + @GetMapping("/overview") + public Result> getOverviewStatistics() { + Map statistics = statisticsService.getOverviewStatistics(); + return Result.success(statistics); + } + + /** + * 获取销售统计数据(管理员) + */ + @GetMapping("/sales") + public Result> getSalesStatistics( + @RequestParam(required = false) String startDate, + @RequestParam(required = false) String endDate) { + Map statistics = statisticsService.getSalesStatistics(startDate, endDate); + return Result.success(statistics); + } + + /** + * 获取产品统计数据(管理员) + */ + @GetMapping("/product") + public Result> getProductStatistics() { + Map statistics = statisticsService.getProductStatistics(); + return Result.success(statistics); + } + + /** + * 获取用户统计数据(管理员) + */ + @GetMapping("/user") + public Result> getUserStatistics() { + Map statistics = statisticsService.getUserStatistics(); + return Result.success(statistics); + } +} diff --git a/backend/src/main/java/com/sunnyfarm/controller/UserController.java b/backend/src/main/java/com/sunnyfarm/controller/UserController.java new file mode 100644 index 0000000..558be7d --- /dev/null +++ b/backend/src/main/java/com/sunnyfarm/controller/UserController.java @@ -0,0 +1,206 @@ +package com.sunnyfarm.controller; + +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.sunnyfarm.common.PageResult; +import com.sunnyfarm.common.Result; +import com.sunnyfarm.entity.User; +import com.sunnyfarm.service.UserService; +import com.sunnyfarm.util.JwtUtil; +import com.sunnyfarm.util.UserContext; +import com.sunnyfarm.dto.EmailCodeRequest; +import com.sunnyfarm.dto.VerifyCodeRequest; +import com.sunnyfarm.dto.RegisterRequest; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.*; + +import javax.servlet.http.HttpServletRequest; +import javax.validation.Valid; +import java.util.HashMap; +import java.util.Map; + +@RestController +@RequestMapping("/api/user") +@CrossOrigin +public class UserController { + + @Autowired + private UserService userService; + + @Autowired + private JwtUtil jwtUtil; + + /** + * 用户注册 + */ + @PostMapping("/register") + public Result register(@Valid @RequestBody RegisterRequest request) { + try { + // 验证邮箱验证码 + if (!userService.verifyEmailCode(request.getEmail(), request.getEmailCode())) { + return Result.error("邮箱验证码错误或已过期"); + } + + // 创建用户对象 + User user = new User(); + user.setUsername(request.getUsername()); + user.setPhone(request.getPhone()); + user.setEmail(request.getEmail()); + user.setPassword(request.getPassword()); + + User registeredUser = userService.register(user); + return Result.success("注册成功"); + } catch (Exception e) { + return Result.error(e.getMessage()); + } + } + + /** + * 用户登录 + */ + @PostMapping("/login") + public Result> login(@RequestBody Map loginData) { + try { + String username = loginData.get("username"); + String password = loginData.get("password"); + + User user = userService.login(username, password); + if (user != null) { + String token = jwtUtil.generateToken(user.getId(), user.getUsername()); + + Map result = new HashMap<>(); + result.put("token", token); + result.put("user", user); + + return Result.success("登录成功", result); + } else { + return Result.error("用户名或密码错误"); + } + } catch (Exception e) { + return Result.error(e.getMessage()); + } + } + + /** + * 获取当前用户信息 + */ + @GetMapping("/profile") + public Result getProfile() { + Long userId = UserContext.getCurrentUserId(); + User user = userService.getById(userId); + return Result.success(user); + } + + /** + * 更新用户信息 + */ + @PutMapping("/profile") + public Result updateProfile(@RequestBody User user) { + Long userId = UserContext.getCurrentUserId(); + user.setId(userId); + User updatedUser = userService.updateProfile(user); + return Result.success("更新成功", updatedUser); + } + + /** + * 修改密码 + */ + @PutMapping("/password") + public Result changePassword(@RequestBody Map passwordData) { + try { + Long userId = UserContext.getCurrentUserId(); + String oldPassword = passwordData.get("oldPassword"); + String newPassword = passwordData.get("newPassword"); + + boolean success = userService.changePassword(userId, oldPassword, newPassword); + if (success) { + return Result.success("密码修改成功"); + } else { + return Result.error("原密码错误"); + } + } catch (Exception e) { + return Result.error(e.getMessage()); + } + } + + /** + * 用户注销 + */ + @PostMapping("/logout") + public Result logout() { + // JWT是无状态的,客户端删除token即可 + return Result.success("注销成功"); + } + + /** + * 发送邮箱验证码 + */ + @PostMapping("/send-email-code") + public Result sendEmailCode(@Valid @RequestBody EmailCodeRequest request) { + try { + boolean success = userService.sendEmailCode(request.getEmail()); + if (success) { + return Result.success("验证码发送成功"); + } else { + return Result.error("验证码发送失败"); + } + } catch (Exception e) { + return Result.error(e.getMessage()); + } + } + + /** + * 验证邮箱验证码 + */ + @PostMapping("/verify-email-code") + public Result verifyEmailCode(@Valid @RequestBody VerifyCodeRequest request) { + try { + boolean valid = userService.verifyEmailCode(request.getEmail(), request.getCode()); + if (valid) { + return Result.success("验证码验证成功"); + } else { + return Result.error("验证码错误或已过期"); + } + } catch (Exception e) { + return Result.error(e.getMessage()); + } + } + + /** + * 获取用户列表(管理员) + */ + @GetMapping("/list") + public Result> getUserList( + @RequestParam(defaultValue = "1") Integer page, + @RequestParam(defaultValue = "10") Integer size, + @RequestParam(required = false) String keyword, + @RequestParam(required = false) Integer status) { + + Page pageObj = new Page<>(page, size); + IPage result = userService.getUserList(pageObj, keyword, status); + + PageResult pageResult = PageResult.of( + result.getTotal(), + result.getPages(), + result.getCurrent(), + result.getSize(), + result.getRecords() + ); + + return Result.success(pageResult); + } + + /** + * 更新用户状态(管理员) + */ + @PutMapping("/{id}/status") + public Result updateUserStatus(@PathVariable Long id, @RequestBody Map statusData) { + Integer status = statusData.get("status"); + boolean success = userService.updateUserStatus(id, status); + if (success) { + return Result.success("状态更新成功"); + } else { + return Result.error("状态更新失败"); + } + } +} diff --git a/backend/src/main/java/com/sunnyfarm/dto/AdminDashboardDTO.java b/backend/src/main/java/com/sunnyfarm/dto/AdminDashboardDTO.java new file mode 100644 index 0000000..0539f58 --- /dev/null +++ b/backend/src/main/java/com/sunnyfarm/dto/AdminDashboardDTO.java @@ -0,0 +1,35 @@ +package com.sunnyfarm.dto; + +import lombok.Data; +import java.time.LocalDateTime; + +@Data +public class AdminDashboardDTO { + + // 用户统计 + private Long totalUsers; + private Long newUsersToday; + + // 商家统计 + private Long totalMerchants; + private Long pendingMerchants; + + // 订单统计 + private Long totalOrders; + private Long newOrdersToday; + + // 商品统计 + private Long totalProducts; + private Long pendingProducts; + + // 收入统计 + private Double totalRevenue; + private Double todayRevenue; + + // 待处理事项 + private Long pendingComplaints; + + // 系统信息 + private String systemVersion; + private LocalDateTime lastUpdateTime; +} diff --git a/backend/src/main/java/com/sunnyfarm/dto/AdminLoginRequest.java b/backend/src/main/java/com/sunnyfarm/dto/AdminLoginRequest.java new file mode 100644 index 0000000..81d13b2 --- /dev/null +++ b/backend/src/main/java/com/sunnyfarm/dto/AdminLoginRequest.java @@ -0,0 +1,15 @@ +package com.sunnyfarm.dto; + +import lombok.Data; + +import javax.validation.constraints.NotBlank; + +@Data +public class AdminLoginRequest { + + @NotBlank(message = "用户名不能为空") + private String username; + + @NotBlank(message = "密码不能为空") + private String password; +} diff --git a/backend/src/main/java/com/sunnyfarm/dto/AdminMerchantPageDTO.java b/backend/src/main/java/com/sunnyfarm/dto/AdminMerchantPageDTO.java new file mode 100644 index 0000000..6c9316f --- /dev/null +++ b/backend/src/main/java/com/sunnyfarm/dto/AdminMerchantPageDTO.java @@ -0,0 +1,11 @@ +package com.sunnyfarm.dto; + +import lombok.Data; + +@Data +public class AdminMerchantPageDTO { + private Integer pageNum = 1; + private Integer pageSize = 10; + private String keyword; + private Integer status; +} diff --git a/backend/src/main/java/com/sunnyfarm/dto/AdminOrderPageDTO.java b/backend/src/main/java/com/sunnyfarm/dto/AdminOrderPageDTO.java new file mode 100644 index 0000000..46c6fa1 --- /dev/null +++ b/backend/src/main/java/com/sunnyfarm/dto/AdminOrderPageDTO.java @@ -0,0 +1,14 @@ +package com.sunnyfarm.dto; + +import lombok.Data; +import java.time.LocalDateTime; + +@Data +public class AdminOrderPageDTO { + private Integer pageNum = 1; + private Integer pageSize = 10; + private String keyword; + private Integer status; + private LocalDateTime startTime; + private LocalDateTime endTime; +} diff --git a/backend/src/main/java/com/sunnyfarm/dto/AdminPasswordRequest.java b/backend/src/main/java/com/sunnyfarm/dto/AdminPasswordRequest.java new file mode 100644 index 0000000..c5dc1ce --- /dev/null +++ b/backend/src/main/java/com/sunnyfarm/dto/AdminPasswordRequest.java @@ -0,0 +1,20 @@ +package com.sunnyfarm.dto; + +import lombok.Data; + +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.Size; + +@Data +public class AdminPasswordRequest { + + @NotBlank(message = "原密码不能为空") + private String oldPassword; + + @NotBlank(message = "新密码不能为空") + @Size(min = 6, max = 20, message = "新密码长度必须在6-20个字符之间") + private String newPassword; + + @NotBlank(message = "确认密码不能为空") + private String confirmPassword; +} diff --git a/backend/src/main/java/com/sunnyfarm/dto/AdminProductPageDTO.java b/backend/src/main/java/com/sunnyfarm/dto/AdminProductPageDTO.java new file mode 100644 index 0000000..b22523f --- /dev/null +++ b/backend/src/main/java/com/sunnyfarm/dto/AdminProductPageDTO.java @@ -0,0 +1,12 @@ +package com.sunnyfarm.dto; + +import lombok.Data; + +@Data +public class AdminProductPageDTO { + private Integer pageNum = 1; + private Integer pageSize = 10; + private String keyword; + private Integer status; + private Long categoryId; +} diff --git a/backend/src/main/java/com/sunnyfarm/dto/AdminUpdateRequest.java b/backend/src/main/java/com/sunnyfarm/dto/AdminUpdateRequest.java new file mode 100644 index 0000000..616145d --- /dev/null +++ b/backend/src/main/java/com/sunnyfarm/dto/AdminUpdateRequest.java @@ -0,0 +1,20 @@ +package com.sunnyfarm.dto; + +import lombok.Data; + +import javax.validation.constraints.Email; +import javax.validation.constraints.Size; + +@Data +public class AdminUpdateRequest { + + @Size(max = 100, message = "邮箱长度不能超过100个字符") + @Email(message = "邮箱格式不正确") + private String email; + + @Size(max = 20, message = "手机号长度不能超过20个字符") + private String phone; + + @Size(max = 50, message = "真实姓名不能超过50个字符") + private String realName; +} diff --git a/backend/src/main/java/com/sunnyfarm/dto/AdminUserPageDTO.java b/backend/src/main/java/com/sunnyfarm/dto/AdminUserPageDTO.java new file mode 100644 index 0000000..460ede3 --- /dev/null +++ b/backend/src/main/java/com/sunnyfarm/dto/AdminUserPageDTO.java @@ -0,0 +1,12 @@ +package com.sunnyfarm.dto; + +import lombok.Data; + +@Data +public class AdminUserPageDTO { + private Integer pageNum = 1; + private Integer pageSize = 10; + private String keyword; + private Integer status; + private Integer isVerified; +} diff --git a/backend/src/main/java/com/sunnyfarm/dto/CartItemDTO.java b/backend/src/main/java/com/sunnyfarm/dto/CartItemDTO.java new file mode 100644 index 0000000..09240a4 --- /dev/null +++ b/backend/src/main/java/com/sunnyfarm/dto/CartItemDTO.java @@ -0,0 +1,32 @@ +package com.sunnyfarm.dto; + +import lombok.Data; + +import java.math.BigDecimal; +import java.time.LocalDateTime; + +@Data +public class CartItemDTO { + + private Long id; + private Long userId; + private Long productId; + private Integer quantity; + private Boolean selected; + private LocalDateTime createdAt; + private LocalDateTime updatedAt; + + // 商品信息 + private String productName; + private String productImage; + private BigDecimal price; + private BigDecimal originPrice; + private String origin; + private String unit; + private Integer status; + private Long merchantId; + private String merchantName; + + // 库存信息 + private Integer stockQuantity; +} diff --git a/backend/src/main/java/com/sunnyfarm/dto/ChatSessionDTO.java b/backend/src/main/java/com/sunnyfarm/dto/ChatSessionDTO.java new file mode 100644 index 0000000..e9eb281 --- /dev/null +++ b/backend/src/main/java/com/sunnyfarm/dto/ChatSessionDTO.java @@ -0,0 +1,20 @@ +package com.sunnyfarm.dto; + +import lombok.Data; + +import java.time.LocalDateTime; + +@Data +public class ChatSessionDTO { + private Long id; + private Long userId; + private Long merchantId; + private String lastMessage; + private LocalDateTime lastMessageTime; + private Integer status; + private String userName; + private String merchantName; + private Integer unreadCount; + private String userAvatar; + private String merchantAvatar; +} diff --git a/backend/src/main/java/com/sunnyfarm/dto/EmailCodeRequest.java b/backend/src/main/java/com/sunnyfarm/dto/EmailCodeRequest.java new file mode 100644 index 0000000..b8c81c5 --- /dev/null +++ b/backend/src/main/java/com/sunnyfarm/dto/EmailCodeRequest.java @@ -0,0 +1,25 @@ +package com.sunnyfarm.dto; + +import javax.validation.constraints.Email; +import javax.validation.constraints.NotBlank; + +public class EmailCodeRequest { + + @NotBlank(message = "邮箱不能为空") + @Email(message = "邮箱格式不正确") + private String email; + + public EmailCodeRequest() {} + + public EmailCodeRequest(String email) { + this.email = email; + } + + public String getEmail() { + return email; + } + + public void setEmail(String email) { + this.email = email; + } +} diff --git a/backend/src/main/java/com/sunnyfarm/dto/InventoryWithProduct.java b/backend/src/main/java/com/sunnyfarm/dto/InventoryWithProduct.java new file mode 100644 index 0000000..6ac3998 --- /dev/null +++ b/backend/src/main/java/com/sunnyfarm/dto/InventoryWithProduct.java @@ -0,0 +1,40 @@ +package com.sunnyfarm.dto; + +import lombok.Data; +import java.math.BigDecimal; +import java.time.LocalDateTime; + +@Data +public class InventoryWithProduct { + + // 库存信息 + private Long id; + private Long productId; + private Integer stockQuantity; + private Integer warningQuantity; + private LocalDateTime updatedAt; + + // 商品基本信息 + private String productName; + private BigDecimal price; + private Integer productStatus; + private String unit; + private String description; + private String origin; + private Integer salesCount; + private Integer viewCount; + + // 商品图片信息 + private String mainImage; + + // 分类信息 + private Long categoryId; + private String categoryName; + + // 商家信息 + private Long merchantId; + private String merchantName; + + // 创建时间 + private LocalDateTime createdAt; +} \ No newline at end of file diff --git a/backend/src/main/java/com/sunnyfarm/dto/MerchantDashboardDTO.java b/backend/src/main/java/com/sunnyfarm/dto/MerchantDashboardDTO.java new file mode 100644 index 0000000..a94f918 --- /dev/null +++ b/backend/src/main/java/com/sunnyfarm/dto/MerchantDashboardDTO.java @@ -0,0 +1,87 @@ +package com.sunnyfarm.dto; + +import lombok.Data; +import java.math.BigDecimal; +import java.util.List; + +@Data +public class MerchantDashboardDTO { + + /** + * 总订单数 + */ + private Integer totalOrders; + + /** + * 今日订单数 + */ + private Integer todayOrders; + + /** + * 总收入 + */ + private BigDecimal totalRevenue; + + /** + * 今日收入 + */ + private BigDecimal todayRevenue; + + /** + * 商品总数 + */ + private Long totalProducts; + + /** + * 在售商品数 + */ + private Integer activeProducts; + + /** + * 低库存商品数 + */ + private Integer lowStockProducts; + + /** + * 客户总数 + */ + private Long totalCustomers; + + /** + * 最近订单列表 + */ + private List recentOrders; + + /** + * 销售趋势数据 + */ + private List salesTrend; + + /** + * 商品销量排行 + */ + private List topProducts; + + @Data + public static class RecentOrderDTO { + private String orderNo; + private String customerName; + private BigDecimal totalAmount; + private Integer status; + private String createTime; + } + + @Data + public static class SalesTrendDTO { + private String date; + private BigDecimal sales; + private Long orders; + } + + @Data + public static class ProductSalesDTO { + private String productName; + private Long salesCount; + private BigDecimal salesAmount; + } +} diff --git a/backend/src/main/java/com/sunnyfarm/dto/MerchantRegisterRequest.java b/backend/src/main/java/com/sunnyfarm/dto/MerchantRegisterRequest.java new file mode 100644 index 0000000..833b6b3 --- /dev/null +++ b/backend/src/main/java/com/sunnyfarm/dto/MerchantRegisterRequest.java @@ -0,0 +1,47 @@ +package com.sunnyfarm.dto; + +import lombok.Data; + +import javax.validation.constraints.Email; +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.Pattern; +import javax.validation.constraints.Size; + +@Data +public class MerchantRegisterRequest { + + @NotBlank(message = "用户名不能为空") + @Size(min = 3, max = 20, message = "用户名长度在3到20个字符") + private String username; + + @Pattern(regexp = "^1[3-9]\\d{9}$", message = "请输入正确的手机号") + private String phone; + + @Email(message = "请输入正确的邮箱格式") + private String email; + + @NotBlank(message = "邮箱验证码不能为空") + @Size(min = 6, max = 6, message = "验证码为6位数字") + private String emailCode; + + @NotBlank(message = "密码不能为空") + @Size(min = 6, max = 20, message = "密码长度在6到20个字符") + private String password; + + @NotBlank(message = "店铺名称不能为空") + @Size(max = 100, message = "店铺名称不能超过100个字符") + private String shopName; + + @NotBlank(message = "法人姓名不能为空") + @Size(max = 50, message = "法人姓名不能超过50个字符") + private String legalPerson; + + @Pattern(regexp = "^1[3-9]\\d{9}$", message = "请输入正确的联系电话") + private String contactPhone; + + @Size(max = 500, message = "经营范围不能超过500个字符") + private String businessScope; + + @Size(max = 200, message = "经营地址不能超过200个字符") + private String address; +} diff --git a/backend/src/main/java/com/sunnyfarm/dto/OrderCreateRequest.java b/backend/src/main/java/com/sunnyfarm/dto/OrderCreateRequest.java new file mode 100644 index 0000000..d1c8412 --- /dev/null +++ b/backend/src/main/java/com/sunnyfarm/dto/OrderCreateRequest.java @@ -0,0 +1,61 @@ +package com.sunnyfarm.dto; + +import lombok.Data; +import java.math.BigDecimal; +import java.util.List; + +@Data +public class OrderCreateRequest { + + /** + * 订单类型:1-立即购买,2-购物车结算 + */ + private Integer type; + + /** + * 收货地址ID + */ + private Long addressId; + + /** + * 订单备注 + */ + private String remark; + + /** + * 购物车ID列表(购物车结算时使用) + */ + private List cartIds; + + /** + * 商品信息列表(立即购买时使用) + */ + private List items; + + @Data + public static class OrderItemRequest { + /** + * 商品ID + */ + private Long productId; + + /** + * 购买数量 + */ + private Integer quantity; + + /** + * 商品价格 + */ + private BigDecimal price; + } + + /** + * 验证订单创建请求是否有效 + * 至少要有购物车ID列表或商品列表中的一种 + */ + public boolean isValid() { + return (cartIds != null && !cartIds.isEmpty()) || + (items != null && !items.isEmpty()); + } +} \ No newline at end of file diff --git a/backend/src/main/java/com/sunnyfarm/dto/OrderDetailDTO.java b/backend/src/main/java/com/sunnyfarm/dto/OrderDetailDTO.java new file mode 100644 index 0000000..d66eb23 --- /dev/null +++ b/backend/src/main/java/com/sunnyfarm/dto/OrderDetailDTO.java @@ -0,0 +1,37 @@ +package com.sunnyfarm.dto; + +import lombok.Data; + +import java.math.BigDecimal; +import java.time.LocalDateTime; +import java.util.List; + +@Data +public class OrderDetailDTO { + + private Long id; + private String orderNo; + private Long userId; + private Long merchantId; + private String merchantName; + private BigDecimal totalAmount; + private BigDecimal discountAmount; + private BigDecimal actualAmount; + private Integer status; + private String statusName; + private String consignee; + private String phone; + private String address; + private String remark; + private LocalDateTime createdAt; + private LocalDateTime updatedAt; + + // 订单项列表 + private List orderItems; + + // 物流信息 + private String logisticsCompany; + private String trackingNumber; + private Integer logisticsStatus; + private String logisticsStatusName; +} diff --git a/backend/src/main/java/com/sunnyfarm/dto/OrderItemDTO.java b/backend/src/main/java/com/sunnyfarm/dto/OrderItemDTO.java new file mode 100644 index 0000000..94b10cc --- /dev/null +++ b/backend/src/main/java/com/sunnyfarm/dto/OrderItemDTO.java @@ -0,0 +1,18 @@ +package com.sunnyfarm.dto; + +import lombok.Data; + +import java.math.BigDecimal; + +@Data +public class OrderItemDTO { + + private Long id; + private Long orderId; + private Long productId; + private String productName; + private String productImage; + private BigDecimal price; + private Integer quantity; + private BigDecimal totalAmount; +} diff --git a/backend/src/main/java/com/sunnyfarm/dto/RegisterRequest.java b/backend/src/main/java/com/sunnyfarm/dto/RegisterRequest.java new file mode 100644 index 0000000..2043230 --- /dev/null +++ b/backend/src/main/java/com/sunnyfarm/dto/RegisterRequest.java @@ -0,0 +1,73 @@ +package com.sunnyfarm.dto; + +import javax.validation.constraints.Email; +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.Pattern; +import javax.validation.constraints.Size; + +public class RegisterRequest { + + @NotBlank(message = "用户名不能为空") + @Size(min = 3, max = 20, message = "用户名长度在3到20个字符") + private String username; + + @NotBlank(message = "手机号不能为空") + @Pattern(regexp = "^1[3-9]\\d{9}$", message = "手机号格式不正确") + private String phone; + + @NotBlank(message = "邮箱不能为空") + @Email(message = "邮箱格式不正确") + private String email; + + @NotBlank(message = "验证码不能为空") + @Pattern(regexp = "^\\d{6}$", message = "验证码必须为6位数字") + private String emailCode; + + @NotBlank(message = "密码不能为空") + @Size(min = 6, max = 20, message = "密码长度在6到20个字符") + private String password; + + // 构造函数 + public RegisterRequest() {} + + // Getters and Setters + public String getUsername() { + return username; + } + + public void setUsername(String username) { + this.username = username; + } + + public String getPhone() { + return phone; + } + + public void setPhone(String phone) { + this.phone = phone; + } + + public String getEmail() { + return email; + } + + public void setEmail(String email) { + this.email = email; + } + + public String getEmailCode() { + return emailCode; + } + + public void setEmailCode(String emailCode) { + this.emailCode = emailCode; + } + + public String getPassword() { + return password; + } + + public void setPassword(String password) { + this.password = password; + } +} diff --git a/backend/src/main/java/com/sunnyfarm/dto/SendMessageRequest.java b/backend/src/main/java/com/sunnyfarm/dto/SendMessageRequest.java new file mode 100644 index 0000000..ed6287b --- /dev/null +++ b/backend/src/main/java/com/sunnyfarm/dto/SendMessageRequest.java @@ -0,0 +1,19 @@ +package com.sunnyfarm.dto; + +import lombok.Data; + +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.NotNull; + +@Data +public class SendMessageRequest { + + @NotNull(message = "商家ID不能为空") + private Long merchantId; + + @NotNull(message = "消息类型不能为空") + private Integer messageType; // 1文本,2图片,3文件 + + @NotBlank(message = "消息内容不能为空") + private String content; +} diff --git a/backend/src/main/java/com/sunnyfarm/dto/VerifyCodeRequest.java b/backend/src/main/java/com/sunnyfarm/dto/VerifyCodeRequest.java new file mode 100644 index 0000000..fc84e9f --- /dev/null +++ b/backend/src/main/java/com/sunnyfarm/dto/VerifyCodeRequest.java @@ -0,0 +1,39 @@ +package com.sunnyfarm.dto; + +import javax.validation.constraints.Email; +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.Pattern; + +public class VerifyCodeRequest { + + @NotBlank(message = "邮箱不能为空") + @Email(message = "邮箱格式不正确") + private String email; + + @NotBlank(message = "验证码不能为空") + @Pattern(regexp = "^\\d{6}$", message = "验证码必须为6位数字") + private String code; + + public VerifyCodeRequest() {} + + public VerifyCodeRequest(String email, String code) { + this.email = email; + this.code = code; + } + + public String getEmail() { + return email; + } + + public void setEmail(String email) { + this.email = email; + } + + public String getCode() { + return code; + } + + public void setCode(String code) { + this.code = code; + } +} diff --git a/backend/src/main/java/com/sunnyfarm/entity/Address.java b/backend/src/main/java/com/sunnyfarm/entity/Address.java new file mode 100644 index 0000000..49d31d7 --- /dev/null +++ b/backend/src/main/java/com/sunnyfarm/entity/Address.java @@ -0,0 +1,45 @@ +package com.sunnyfarm.entity; + +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.NotNull; +import javax.validation.constraints.Pattern; +import javax.validation.constraints.Size; + +@Data +@EqualsAndHashCode(callSuper = true) +@TableName("user_addresses") +public class Address extends BaseEntity { + + @NotNull(message = "用户ID不能为空") + private Long userId; + + @NotBlank(message = "收货人姓名不能为空") + @Size(max = 50, message = "收货人姓名长度不能超过50个字符") + private String consignee; + + @NotBlank(message = "收货人电话不能为空") + @Pattern(regexp = "^1[3-9]\\d{9}$", message = "手机号格式不正确") + private String phone; + + @NotBlank(message = "省份不能为空") + @Size(max = 50, message = "省份长度不能超过50个字符") + private String province; + + @NotBlank(message = "城市不能为空") + @Size(max = 50, message = "城市长度不能超过50个字符") + private String city; + + @NotBlank(message = "区县不能为空") + @Size(max = 50, message = "区县长度不能超过50个字符") + private String district; + + @NotBlank(message = "详细地址不能为空") + @Size(max = 200, message = "详细地址长度不能超过200个字符") + private String address; + + private Boolean isDefault = false; +} diff --git a/backend/src/main/java/com/sunnyfarm/entity/Admin.java b/backend/src/main/java/com/sunnyfarm/entity/Admin.java new file mode 100644 index 0000000..aae23fa --- /dev/null +++ b/backend/src/main/java/com/sunnyfarm/entity/Admin.java @@ -0,0 +1,49 @@ +package com.sunnyfarm.entity; + +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableName; +import com.fasterxml.jackson.annotation.JsonIgnore; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import javax.validation.constraints.Email; +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.Size; +import java.time.LocalDateTime; + +@Data +@EqualsAndHashCode(callSuper = true) +@TableName("admins") +public class Admin extends BaseEntity { + + @NotBlank(message = "用户名不能为空") + @Size(min = 2, max = 50, message = "用户名长度必须在2-50个字符之间") + private String username; + + @JsonIgnore + @NotBlank(message = "密码不能为空") + @Size(min = 6, max = 128, message = "密码长度必须在6-128个字符之间") + private String password; + + @Email(message = "邮箱格式不正确") + @Size(max = 100, message = "邮箱长度不能超过100个字符") + private String email; + + private String phone; + + @Size(max = 50, message = "真实姓名不能超过50个字符") + private String realName; + + private Long roleId; + + private Integer status; // 状态:0禁用,1正常 + + private LocalDateTime lastLoginTime; + + // 非数据库字段 + @TableField(exist = false) + private String token; + + @TableField(exist = false) + private Role role; +} diff --git a/backend/src/main/java/com/sunnyfarm/entity/Announcement.java b/backend/src/main/java/com/sunnyfarm/entity/Announcement.java new file mode 100644 index 0000000..ecc30b2 --- /dev/null +++ b/backend/src/main/java/com/sunnyfarm/entity/Announcement.java @@ -0,0 +1,57 @@ +package com.sunnyfarm.entity; + +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.time.LocalDateTime; + +@Data +@EqualsAndHashCode(callSuper = true) +@TableName("announcements") +public class Announcement extends BaseEntity { + + /** + * 标题 + */ + private String title; + + /** + * 内容 + */ + private String content; + + /** + * 类型:1系统公告,2活动公告,3维护公告 + */ + private Integer type; + + /** + * 状态:0草稿,1发布 + */ + private Integer status; + + /** + * 发布时间 + */ + private LocalDateTime publishTime; + + /** + * 创建者ID + */ + private Long creatorId; + + /** + * 创建者名称 + */ + private String creatorName; + + // 类型常量 + public static final int TYPE_SYSTEM = 1; + public static final int TYPE_ACTIVITY = 2; + public static final int TYPE_MAINTENANCE = 3; + + // 状态常量 + public static final int STATUS_DRAFT = 0; + public static final int STATUS_PUBLISHED = 1; +} diff --git a/backend/src/main/java/com/sunnyfarm/entity/BaseEntity.java b/backend/src/main/java/com/sunnyfarm/entity/BaseEntity.java new file mode 100644 index 0000000..14a62c1 --- /dev/null +++ b/backend/src/main/java/com/sunnyfarm/entity/BaseEntity.java @@ -0,0 +1,23 @@ +package com.sunnyfarm.entity; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import com.fasterxml.jackson.annotation.JsonFormat; +import lombok.Data; + +import java.time.LocalDateTime; + +@Data +public class BaseEntity { + @TableId(value = "id", type = IdType.AUTO) + private Long id; + + @TableField("created_at") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime createTime; + + @TableField("updated_at") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime updateTime; +} diff --git a/backend/src/main/java/com/sunnyfarm/entity/Cart.java b/backend/src/main/java/com/sunnyfarm/entity/Cart.java new file mode 100644 index 0000000..196aa29 --- /dev/null +++ b/backend/src/main/java/com/sunnyfarm/entity/Cart.java @@ -0,0 +1,31 @@ +package com.sunnyfarm.entity; + +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import javax.validation.constraints.Min; +import javax.validation.constraints.NotNull; + +@Data +@EqualsAndHashCode(callSuper = true) +@TableName("shopping_cart") +public class Cart extends BaseEntity { + + @NotNull(message = "用户ID不能为空") + private Long userId; + + @NotNull(message = "产品ID不能为空") + private Long productId; + + @NotNull(message = "数量不能为空") + @Min(value = 1, message = "数量必须大于0") + private Integer quantity; + + private Boolean selected = false; + + // 非数据库字段 + @TableField(exist = false) + private Product product; +} diff --git a/backend/src/main/java/com/sunnyfarm/entity/Category.java b/backend/src/main/java/com/sunnyfarm/entity/Category.java new file mode 100644 index 0000000..3f2679f --- /dev/null +++ b/backend/src/main/java/com/sunnyfarm/entity/Category.java @@ -0,0 +1,76 @@ +package com.sunnyfarm.entity; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import com.fasterxml.jackson.annotation.JsonFormat; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.time.LocalDateTime; +import java.util.List; + +@Data +@EqualsAndHashCode(callSuper = false) +@TableName("categories") +public class Category { + + @TableId(type = IdType.AUTO) + private Long id; + + private String name; + + private String description; + + private String icon; + + private Long parentId; + + private Integer sortOrder; + + private Integer status; + + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime createdAt; + + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime updatedAt; + + // 子分类列表,用于构建树形结构 + @TableField(exist = false) + private List children; + + // 父分类名称 + @TableField(exist = false) + private String parentName; + + // 产品数量 + @TableField(exist = false) + private Long productCount; + + // 兼容旧方法名 + public LocalDateTime getCreateTime() { + return this.createdAt; + } + + public void setCreateTime(LocalDateTime createTime) { + this.createdAt = createTime; + } + + public LocalDateTime getUpdateTime() { + return this.updatedAt; + } + + public void setUpdateTime(LocalDateTime updateTime) { + this.updatedAt = updateTime; + } + + public Integer getSort() { + return this.sortOrder; + } + + public void setSort(Integer sort) { + this.sortOrder = sort; + } +} diff --git a/backend/src/main/java/com/sunnyfarm/entity/ChatMessage.java b/backend/src/main/java/com/sunnyfarm/entity/ChatMessage.java new file mode 100644 index 0000000..f46849d --- /dev/null +++ b/backend/src/main/java/com/sunnyfarm/entity/ChatMessage.java @@ -0,0 +1,47 @@ +package com.sunnyfarm.entity; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import com.fasterxml.jackson.annotation.JsonFormat; +import lombok.Data; + +import java.time.LocalDateTime; + +@Data +@TableName("chat_messages") +public class ChatMessage { + + @TableId(value = "id", type = IdType.AUTO) + private Long id; + + @TableField("session_id") + private Long sessionId; + + @TableField("sender_id") + private Long senderId; + + @TableField("sender_type") + private Integer senderType; // 1用户,2商家 + + @TableField("message_type") + private Integer messageType; // 1文本,2图片,3文件 + + @TableField("content") + private String content; + + @TableField("is_read") + private Integer isRead; // 0未读,1已读 + + @TableField("created_at") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime createdAt; + + // 非数据库字段 + @TableField(exist = false) + private String senderName; + + @TableField(exist = false) + private String senderAvatar; +} diff --git a/backend/src/main/java/com/sunnyfarm/entity/ChatSession.java b/backend/src/main/java/com/sunnyfarm/entity/ChatSession.java new file mode 100644 index 0000000..daec5cd --- /dev/null +++ b/backend/src/main/java/com/sunnyfarm/entity/ChatSession.java @@ -0,0 +1,46 @@ +package com.sunnyfarm.entity; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import com.fasterxml.jackson.annotation.JsonFormat; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.time.LocalDateTime; + +@Data +@EqualsAndHashCode(callSuper = true) +@TableName("chat_sessions") +public class ChatSession extends BaseEntity { + + @TableId(value = "id", type = IdType.AUTO) + private Long id; + + @TableField("user_id") + private Long userId; + + @TableField("merchant_id") + private Long merchantId; + + @TableField("last_message") + private String lastMessage; + + @TableField("last_message_time") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime lastMessageTime; + + @TableField("status") + private Integer status; // 0关闭,1活跃 + + // 非数据库字段 + @TableField(exist = false) + private String userName; + + @TableField(exist = false) + private String merchantName; + + @TableField(exist = false) + private Integer unreadCount; +} diff --git a/backend/src/main/java/com/sunnyfarm/entity/Comment.java b/backend/src/main/java/com/sunnyfarm/entity/Comment.java new file mode 100644 index 0000000..7c65c1a --- /dev/null +++ b/backend/src/main/java/com/sunnyfarm/entity/Comment.java @@ -0,0 +1,50 @@ +package com.sunnyfarm.entity; + +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import javax.validation.constraints.NotNull; +import javax.validation.constraints.Size; +import java.math.BigDecimal; +import java.time.LocalDateTime; + +@Data +@EqualsAndHashCode(callSuper = true) +@TableName("reviews") +public class Comment extends BaseEntity { + + @NotNull(message = "用户ID不能为空") + private Long userId; + @NotNull(message = "商家ID不能为空") + private Long merchantId; + + @NotNull(message = "订单ID不能为空") + private Long orderId; + + @NotNull(message = "产品ID不能为空") + private Long productId; + + @Size(max = 1000, message = "评论内容不能超过1000个字符") + private String content; + + private BigDecimal rating; + + @TableField(exist = false) + private String images; + + private String reply; + + private LocalDateTime replyTime; + + private Integer status; + + // 关联用户信息 + @TableField(exist = false) + private User user; + + // 关联产品信息 + @TableField(exist = false) + private Product product; +} diff --git a/backend/src/main/java/com/sunnyfarm/entity/Favorite.java b/backend/src/main/java/com/sunnyfarm/entity/Favorite.java new file mode 100644 index 0000000..c234dca --- /dev/null +++ b/backend/src/main/java/com/sunnyfarm/entity/Favorite.java @@ -0,0 +1,22 @@ +package com.sunnyfarm.entity; + +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import javax.validation.constraints.NotNull; + +@Data +@EqualsAndHashCode(callSuper = true) +@TableName("favorites") +public class Favorite extends BaseEntity { + + @NotNull(message = "用户ID不能为空") + private Long userId; + @NotNull(message = "产品ID不能为空") + private Long productId; + // 非数据库字段 + @TableField(exist = false) + private Product product; +} diff --git a/backend/src/main/java/com/sunnyfarm/entity/Inventory.java b/backend/src/main/java/com/sunnyfarm/entity/Inventory.java new file mode 100644 index 0000000..80f0dc6 --- /dev/null +++ b/backend/src/main/java/com/sunnyfarm/entity/Inventory.java @@ -0,0 +1,25 @@ +package com.sunnyfarm.entity; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; + +import java.time.LocalDateTime; + +@Data +@TableName("inventory") +public class Inventory { + + @TableId(type = IdType.AUTO) + private Long id; + + private Long productId; + + private Integer stockQuantity; + + private Integer warningQuantity; + + private LocalDateTime updatedAt; +} diff --git a/backend/src/main/java/com/sunnyfarm/entity/Merchant.java b/backend/src/main/java/com/sunnyfarm/entity/Merchant.java new file mode 100644 index 0000000..33d61df --- /dev/null +++ b/backend/src/main/java/com/sunnyfarm/entity/Merchant.java @@ -0,0 +1,43 @@ +package com.sunnyfarm.entity; + +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.Size; +import java.time.LocalDateTime; + +@Data +@EqualsAndHashCode(callSuper = true) +@TableName("merchants") +public class Merchant extends BaseEntity { + + private Long userId; // 关联用户ID + + @NotBlank(message = "店铺名称不能为空") + @Size(max = 100, message = "店铺名称不能超过100个字符") + private String shopName; + + private String businessLicense; // 营业执照 + + @Size(max = 50, message = "法人姓名不能超过50个字符") + private String legalPerson; + + private String contactPhone; // 联系电话 + + @Size(max = 500, message = "经营范围不能超过500个字符") + private String businessScope; + + @Size(max = 200, message = "经营地址不能超过200个字符") + private String address; + + private Integer status; // 状态:0待审核,1通过,2拒绝 + + private LocalDateTime verifyTime; // 审核时间 + + // 关联用户信息(非数据库字段) + @TableField(exist = false) + private User user; +} diff --git a/backend/src/main/java/com/sunnyfarm/entity/Order.java b/backend/src/main/java/com/sunnyfarm/entity/Order.java new file mode 100644 index 0000000..e0d3e8b --- /dev/null +++ b/backend/src/main/java/com/sunnyfarm/entity/Order.java @@ -0,0 +1,91 @@ +package com.sunnyfarm.entity; + +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.NotNull; +import javax.validation.constraints.Size; +import java.math.BigDecimal; +import java.time.LocalDateTime; +import java.util.List; + +@Data +@EqualsAndHashCode(callSuper = true) +@TableName("orders") +public class Order extends BaseEntity { + + @NotBlank(message = "订单号不能为空") + @Size(max = 50, message = "订单号长度不能超过50个字符") + private String orderNo; + + @NotNull(message = "用户ID不能为空") + private Long userId; + + @NotNull(message = "商家ID不能为空") + private Long merchantId; + + @NotNull(message = "总金额不能为空") + private BigDecimal totalAmount; + + private BigDecimal discountAmount; + + @NotNull(message = "实付金额不能为空") + private BigDecimal actualAmount; + + @NotNull(message = "订单状态不能为空") + private Integer status; + + @NotBlank(message = "收货人不能为空") + @Size(max = 50, message = "收货人姓名长度不能超过50个字符") + private String consignee; + + @NotBlank(message = "收货人电话不能为空") + @Size(max = 20, message = "收货人电话长度不能超过20个字符") + private String phone; + + @NotBlank(message = "收货地址不能为空") + @Size(max = 200, message = "收货地址长度不能超过200个字符") + private String address; + + private String remark; + + private LocalDateTime payTime; + + private String payTransactionId; + + private LocalDateTime shipTime; + + private String shipCompany; + + private String shipNo; + + private LocalDateTime receiveTime; + + private LocalDateTime finishTime; + + private LocalDateTime cancelTime; + + private String cancelReason; + + private String refundReason; + + private BigDecimal refundAmount; + + private LocalDateTime refundTime; + + // 非数据库字段 + @TableField(exist = false) + private String statusName; + + @TableField(exist = false) + private List orderDetails; + + @TableField(exist = false) + private String userName; + + @TableField(exist = false) + private String merchantName; +} diff --git a/backend/src/main/java/com/sunnyfarm/entity/OrderDetail.java b/backend/src/main/java/com/sunnyfarm/entity/OrderDetail.java new file mode 100644 index 0000000..c62a333 --- /dev/null +++ b/backend/src/main/java/com/sunnyfarm/entity/OrderDetail.java @@ -0,0 +1,58 @@ +package com.sunnyfarm.entity; + +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; + +import javax.validation.constraints.Min; +import javax.validation.constraints.NotNull; +import java.math.BigDecimal; +import java.time.LocalDateTime; + +@Data +@TableName("order_items") +public class OrderDetail { + + private Long id; + + @NotNull(message = "订单ID不能为空") + private Long orderId; + + @NotNull(message = "产品ID不能为空") + private Long productId; + // 商家ID(从products表关联获取) + @TableField(exist = false) + private Long merchantId; + + + @NotNull(message = "数量不能为空") + @Min(value = 1, message = "数量必须大于0") + private Integer quantity; + + @NotNull(message = "单价不能为空") + private BigDecimal price; + + @NotNull(message = "小计不能为空") + @TableField("total_amount") // 映射到数据库的 total_amount 字段 + private BigDecimal subtotal; + + // 冗余字段,避免产品信息变更影响历史订单 + private String productName; + + private String productImage; + + // 数据库中没有这些字段,标记为非数据库字段 + @TableField(exist = false) + private String productSpecs; + + @TableField(exist = false) + private Product product; + + // 时间字段 + @TableField("created_at") + private LocalDateTime createTime; + + // order_items表没有updated_at字段,标记为非数据库字段 + @TableField(exist = false) + private LocalDateTime updateTime; +} diff --git a/backend/src/main/java/com/sunnyfarm/entity/Payment.java b/backend/src/main/java/com/sunnyfarm/entity/Payment.java new file mode 100644 index 0000000..3e3f691 --- /dev/null +++ b/backend/src/main/java/com/sunnyfarm/entity/Payment.java @@ -0,0 +1,45 @@ +package com.sunnyfarm.entity; + +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.NotNull; +import java.math.BigDecimal; +import java.time.LocalDateTime; + +@Data +@EqualsAndHashCode(callSuper = true) +@TableName("payments") +public class Payment extends BaseEntity { + + @NotNull(message = "订单ID不能为空") + private Long orderId; + + @NotBlank(message = "支付单号不能为空") + private String paymentNo; + + @NotBlank(message = "支付方式不能为空") + private String paymentMethod; + + @NotNull(message = "支付金额不能为空") + private BigDecimal amount; + + @NotNull(message = "支付状态不能为空") + private Integer status; // 1:待支付 2:已支付 3:支付失败 4:已退款 + + private String tradeNo; // 第三方交易号 + + private LocalDateTime paidAt; + + // 支付宝相关字段 + private String alipayTradeNo; // 支付宝交易号 + private String buyerPayAmount; // 买家实付金额 + private String buyerLogonId; // 买家支付宝账号 + + // 退款相关 + private BigDecimal refundAmount; + private LocalDateTime refundTime; + private String refundReason; +} diff --git a/backend/src/main/java/com/sunnyfarm/entity/Permission.java b/backend/src/main/java/com/sunnyfarm/entity/Permission.java new file mode 100644 index 0000000..b7a7a59 --- /dev/null +++ b/backend/src/main/java/com/sunnyfarm/entity/Permission.java @@ -0,0 +1,32 @@ +package com.sunnyfarm.entity; + +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.Size; + +@Data +@EqualsAndHashCode(callSuper = true) +@TableName("permissions") +public class Permission extends BaseEntity { + + @NotBlank(message = "权限名称不能为空") + @Size(max = 50, message = "权限名称不能超过50个字符") + private String name; + + @NotBlank(message = "权限代码不能为空") + @Size(max = 50, message = "权限代码不能超过50个字符") + private String code; + + private Integer type; // 权限类型:1菜单,2按钮,3接口 + + private Long parentId; // 父权限ID + + @Size(max = 100, message = "路径不能超过100个字符") + private String path; + + @Size(max = 200, message = "描述不能超过200个字符") + private String description; +} diff --git a/backend/src/main/java/com/sunnyfarm/entity/Product.java b/backend/src/main/java/com/sunnyfarm/entity/Product.java new file mode 100644 index 0000000..e37598a --- /dev/null +++ b/backend/src/main/java/com/sunnyfarm/entity/Product.java @@ -0,0 +1,90 @@ +package com.sunnyfarm.entity; + +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import javax.validation.constraints.DecimalMin; +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.NotNull; +import javax.validation.constraints.Size; +import java.math.BigDecimal; +import java.util.List; + +@Data +@EqualsAndHashCode(callSuper = true) +@TableName("products") +public class Product extends BaseEntity { + + @NotNull(message = "商家不能为空") + private Long merchantId; + + @NotNull(message = "分类不能为空") + private Long categoryId; + + @NotBlank(message = "产品名称不能为空") + @Size(max = 200, message = "产品名称长度不能超过200个字符") + private String name; + + @Size(max = 1000, message = "产品描述长度不能超过1000个字符") + private String description; + + @NotNull(message = "价格不能为空") + @DecimalMin(value = "0.01", message = "价格必须大于0") + private BigDecimal price; + + private BigDecimal originPrice; + + private String origin; + + private String unit; + + private String weight; + + private String shelfLife; + + private String storageMethod; + + private Integer status; // 0审核中,1上架,2下架 + + private Integer sortOrder; + + private Integer salesCount; + + private Integer viewCount; + + // 非数据库字段 - 来自inventory表 + @TableField(exist = false) + private Integer stock; + + // 非数据库字段 + @TableField(exist = false) + private String categoryName; + + @TableField(exist = false) + private List imageList; + + @TableField(exist = false) + private Boolean collected; + + @TableField(exist = false) + private Integer cartCount; + + // 兼容性方法 + public Long getSellerId() { + return this.merchantId; + } + + public void setSellerId(Long sellerId) { + this.merchantId = sellerId; + } + + public Integer getSales() { + return this.salesCount; + } + + public void setSales(Integer sales) { + this.salesCount = sales; + } +} diff --git a/backend/src/main/java/com/sunnyfarm/entity/ProductImage.java b/backend/src/main/java/com/sunnyfarm/entity/ProductImage.java new file mode 100644 index 0000000..96c80bb --- /dev/null +++ b/backend/src/main/java/com/sunnyfarm/entity/ProductImage.java @@ -0,0 +1,24 @@ +package com.sunnyfarm.entity; + +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.NotNull; + +@Data +@EqualsAndHashCode(callSuper = true) +@TableName("product_images") +public class ProductImage extends BaseEntity { + + @NotNull(message = "商品ID不能为空") + private Long productId; + + @NotBlank(message = "图片URL不能为空") + private String imageUrl; + + private Boolean isMain = false; // 是否主图 + + private Integer sortOrder = 0; // 排序 +} diff --git a/backend/src/main/java/com/sunnyfarm/entity/Role.java b/backend/src/main/java/com/sunnyfarm/entity/Role.java new file mode 100644 index 0000000..2b781ee --- /dev/null +++ b/backend/src/main/java/com/sunnyfarm/entity/Role.java @@ -0,0 +1,33 @@ +package com.sunnyfarm.entity; + +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.Size; +import java.util.List; + +@Data +@EqualsAndHashCode(callSuper = true) +@TableName("roles") +public class Role extends BaseEntity { + + @NotBlank(message = "角色名称不能为空") + @Size(max = 50, message = "角色名称不能超过50个字符") + private String name; + + @NotBlank(message = "角色代码不能为空") + @Size(max = 50, message = "角色代码不能超过50个字符") + private String code; + + @Size(max = 200, message = "描述不能超过200个字符") + private String description; + + private Integer status; // 状态:0禁用,1启用 + + // 非数据库字段 + @TableField(exist = false) + private List permissions; +} diff --git a/backend/src/main/java/com/sunnyfarm/entity/SystemConfig.java b/backend/src/main/java/com/sunnyfarm/entity/SystemConfig.java new file mode 100644 index 0000000..9e83bcc --- /dev/null +++ b/backend/src/main/java/com/sunnyfarm/entity/SystemConfig.java @@ -0,0 +1,40 @@ +package com.sunnyfarm.entity; + +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; +import lombok.EqualsAndHashCode; + +@Data +@EqualsAndHashCode(callSuper = true) +@TableName("system_configs") +public class SystemConfig extends BaseEntity { + + /** + * 配置键 + */ + private String configKey; + + /** + * 配置值 + */ + private String configValue; + + /** + * 配置类型 + */ + private String configType; + + /** + * 描述 + */ + private String description; + + // 配置键常量 + public static final String SITE_NAME = "site_name"; + public static final String SITE_LOGO = "site_logo"; + public static final String SITE_DESCRIPTION = "site_description"; + public static final String CONTACT_PHONE = "contact_phone"; + public static final String CONTACT_EMAIL = "contact_email"; + public static final String AUTO_CONFIRM_DAYS = "auto_confirm_days"; + public static final String MAX_CART_ITEMS = "max_cart_items"; +} diff --git a/backend/src/main/java/com/sunnyfarm/entity/SystemLog.java b/backend/src/main/java/com/sunnyfarm/entity/SystemLog.java new file mode 100644 index 0000000..a8fe103 --- /dev/null +++ b/backend/src/main/java/com/sunnyfarm/entity/SystemLog.java @@ -0,0 +1,91 @@ +package com.sunnyfarm.entity; + +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; +import lombok.EqualsAndHashCode; + +@Data +@EqualsAndHashCode(callSuper = true) +@TableName("system_logs") +public class SystemLog extends BaseEntity { + + /** + * 操作类型 + */ + private String operationType; + + /** + * 操作描述 + */ + private String operationDesc; + + /** + * 操作者ID + */ + private Long operatorId; + + /** + * 操作者名称 + */ + private String operatorName; + + /** + * 操作者类型:1普通用户,2商家,3管理员,4系统 + */ + private Integer operatorType; + + /** + * 操作目标类型 + */ + private String targetType; + + /** + * 操作目标ID + */ + private Long targetId; + + /** + * IP地址 + */ + private String ipAddress; + + /** + * 用户代理 + */ + private String userAgent; + + /** + * 操作结果:0失败,1成功 + */ + private Integer result; + + /** + * 错误信息 + */ + private String errorMsg; + + // 操作类型常量 + public static final String USER_REGISTER = "user_register"; + public static final String USER_LOGIN = "user_login"; + public static final String MERCHANT_REGISTER = "merchant_register"; + public static final String MERCHANT_AUDIT = "merchant_audit"; + public static final String PRODUCT_CREATE = "product_create"; + public static final String PRODUCT_AUDIT = "product_audit"; + public static final String ORDER_CREATE = "order_create"; + public static final String ORDER_AUDIT = "order_audit"; + public static final String ADMIN_LOGIN = "admin_login"; + public static final String SYSTEM_CONFIG = "system_config"; + public static final String ANNOUNCEMENT_CREATE = "announcement_create"; + public static final String ANNOUNCEMENT_UPDATE = "announcement_update"; + public static final String ANNOUNCEMENT_DELETE = "announcement_delete"; + + // 操作者类型常量 + public static final int OPERATOR_USER = 1; + public static final int OPERATOR_MERCHANT = 2; + public static final int OPERATOR_ADMIN = 3; + public static final int OPERATOR_SYSTEM = 4; + + // 操作结果常量 + public static final int RESULT_FAIL = 0; + public static final int RESULT_SUCCESS = 1; +} diff --git a/backend/src/main/java/com/sunnyfarm/entity/User.java b/backend/src/main/java/com/sunnyfarm/entity/User.java new file mode 100644 index 0000000..3362df9 --- /dev/null +++ b/backend/src/main/java/com/sunnyfarm/entity/User.java @@ -0,0 +1,62 @@ +package com.sunnyfarm.entity; + +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableName; +import com.fasterxml.jackson.annotation.JsonIgnore; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import javax.validation.constraints.Email; +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.Pattern; +import javax.validation.constraints.Size; +import java.time.LocalDate; +import java.time.LocalDateTime; + +@Data +@EqualsAndHashCode(callSuper = true) +@TableName("users") +public class User extends BaseEntity { + + @NotBlank(message = "用户名不能为空") + @Size(min = 2, max = 50, message = "用户名长度必须在2-50个字符之间") + private String username; + + @JsonIgnore + @NotBlank(message = "密码不能为空") + @Size(min = 6, max = 100, message = "密码长度必须在6-100个字符之间") + private String password; + + @Size(max = 50, message = "昵称长度不能超过50个字符") + private String nickname; + + @Email(message = "邮箱格式不正确") + @Size(max = 100, message = "邮箱长度不能超过100个字符") + private String email; + + @Pattern(regexp = "^1[3-9]\\d{9}$", message = "手机号格式不正确") + private String phone; + + private String avatar; + + private Integer gender; // 0未知,1男,2女 + + private LocalDate birthday; + + private String realName; // 真实姓名 + + private String idCard; // 身份证号 + + private Boolean isVerified; // 是否实名认证 + + private Integer status; // 0禁用,1正常 + + private LocalDateTime lastLoginTime; + + // 非数据库字段 + @TableField(exist = false) + private String token; + + @TableField(exist = false) + private String confirmPassword; +} diff --git a/backend/src/main/java/com/sunnyfarm/exception/BusinessException.java b/backend/src/main/java/com/sunnyfarm/exception/BusinessException.java new file mode 100644 index 0000000..c4c66dd --- /dev/null +++ b/backend/src/main/java/com/sunnyfarm/exception/BusinessException.java @@ -0,0 +1,20 @@ +package com.sunnyfarm.exception; + +public class BusinessException extends RuntimeException { + + private Integer code; + + public BusinessException(String message) { + super(message); + this.code = 500; + } + + public BusinessException(Integer code, String message) { + super(message); + this.code = code; + } + + public Integer getCode() { + return code; + } +} diff --git a/backend/src/main/java/com/sunnyfarm/exception/GlobalExceptionHandler.java b/backend/src/main/java/com/sunnyfarm/exception/GlobalExceptionHandler.java new file mode 100644 index 0000000..79157da --- /dev/null +++ b/backend/src/main/java/com/sunnyfarm/exception/GlobalExceptionHandler.java @@ -0,0 +1,96 @@ +package com.sunnyfarm.exception; + +import com.sunnyfarm.common.Result; +import lombok.extern.slf4j.Slf4j; +import org.springframework.http.HttpStatus; +import org.springframework.validation.BindException; +import org.springframework.validation.FieldError; +import org.springframework.web.bind.MethodArgumentNotValidException; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.bind.annotation.ResponseStatus; +import org.springframework.web.bind.annotation.RestControllerAdvice; + +import javax.validation.ConstraintViolation; +import javax.validation.ConstraintViolationException; +import java.util.List; +import java.util.Set; + +@RestControllerAdvice +@Slf4j +public class GlobalExceptionHandler { + + /** + * 处理业务异常 + */ + @ExceptionHandler(BusinessException.class) + @ResponseStatus(HttpStatus.BAD_REQUEST) + public Result handleBusinessException(BusinessException e) { + log.error("业务异常: {}", e.getMessage()); + return Result.error(e.getMessage()); + } + + /** + * 处理参数校验异常 + */ + @ExceptionHandler(MethodArgumentNotValidException.class) + @ResponseStatus(HttpStatus.BAD_REQUEST) + public Result handleValidException(MethodArgumentNotValidException e) { + List fieldErrors = e.getBindingResult().getFieldErrors(); + StringBuilder sb = new StringBuilder(); + for (FieldError error : fieldErrors) { + sb.append(error.getField()).append(": ").append(error.getDefaultMessage()).append("; "); + } + log.error("参数校验异常: {}", sb.toString()); + return Result.error("参数校验失败: " + sb.toString()); + } + + /** + * 处理绑定异常 + */ + @ExceptionHandler(BindException.class) + @ResponseStatus(HttpStatus.BAD_REQUEST) + public Result handleBindException(BindException e) { + List fieldErrors = e.getBindingResult().getFieldErrors(); + StringBuilder sb = new StringBuilder(); + for (FieldError error : fieldErrors) { + sb.append(error.getField()).append(": ").append(error.getDefaultMessage()).append("; "); + } + log.error("绑定异常: {}", sb.toString()); + return Result.error("参数绑定失败: " + sb.toString()); + } + + /** + * 处理约束违反异常 + */ + @ExceptionHandler(ConstraintViolationException.class) + @ResponseStatus(HttpStatus.BAD_REQUEST) + public Result handleConstraintViolationException(ConstraintViolationException e) { + Set> violations = e.getConstraintViolations(); + StringBuilder sb = new StringBuilder(); + for (ConstraintViolation violation : violations) { + sb.append(violation.getMessage()).append("; "); + } + log.error("约束违反异常: {}", sb.toString()); + return Result.error("参数约束违反: " + sb.toString()); + } + + /** + * 处理运行时异常 + */ + @ExceptionHandler(RuntimeException.class) + @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR) + public Result handleRuntimeException(RuntimeException e) { + log.error("运行时异常", e); + return Result.error("系统异常,请稍后重试"); + } + + /** + * 处理其他异常 + */ + @ExceptionHandler(Exception.class) + @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR) + public Result handleException(Exception e) { + log.error("系统异常", e); + return Result.error("系统异常,请稍后重试"); + } +} diff --git a/backend/src/main/java/com/sunnyfarm/interceptor/JwtAuthInterceptor.java b/backend/src/main/java/com/sunnyfarm/interceptor/JwtAuthInterceptor.java new file mode 100644 index 0000000..bea451a --- /dev/null +++ b/backend/src/main/java/com/sunnyfarm/interceptor/JwtAuthInterceptor.java @@ -0,0 +1,105 @@ +package com.sunnyfarm.interceptor; + +import com.sunnyfarm.util.JwtUtil; +import com.sunnyfarm.util.UserContext; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; +import org.springframework.util.StringUtils; +import org.springframework.web.servlet.HandlerInterceptor; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +@Component +@Slf4j +public class JwtAuthInterceptor implements HandlerInterceptor { + + @Autowired + private JwtUtil jwtUtil; + + @Override + public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { + String requestURI = request.getRequestURI(); + String method = request.getMethod(); + + log.debug("拦截器处理请求: {} {}", method, requestURI); + + // 处理OPTIONS预检请求 + if ("OPTIONS".equals(method)) { + response.setStatus(HttpServletResponse.SC_OK); + return true; + } + + // 获取token + String token = getTokenFromRequest(request); + + if (!StringUtils.hasText(token)) { + log.warn("请求 {} 缺少Authorization token", requestURI); + response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); + response.setContentType("application/json;charset=UTF-8"); + response.getWriter().write("{\"code\": 401, \"message\": \"未登录或登录已过期\"}"); + return false; + } + + try { + // 验证token + String username = jwtUtil.getUsernameFromToken(token); + if (username == null || jwtUtil.isTokenExpired(token)) { + log.warn("Token验证失败或已过期: {}", requestURI); + response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); + response.setContentType("application/json;charset=UTF-8"); + response.getWriter().write("{\"code\": 401, \"message\": \"登录已过期,请重新登录\"}"); + return false; + } + + // 获取用户信息并存储到上下文 + String userType = jwtUtil.getUserTypeFromToken(token); + + if ("admin".equals(userType)) { + Long adminId = jwtUtil.getAdminIdFromToken(token); + UserContext.setCurrentUserId(adminId); + UserContext.setCurrentUsername(username); + UserContext.setUserType("admin"); + } else if ("merchant".equals(userType)) { + Long userId = jwtUtil.getUserIdFromToken(token); + Long merchantId = jwtUtil.getMerchantIdFromToken(token); + UserContext.setCurrentUserId(userId); + UserContext.setCurrentUsername(username); + UserContext.setUserType("merchant"); + UserContext.setMerchantId(merchantId); + } else { + Long userId = jwtUtil.getUserIdFromToken(token); + UserContext.setCurrentUserId(userId); + UserContext.setCurrentUsername(username); + UserContext.setUserType("user"); + } + + log.debug("用户认证成功: {} ({})", username, userType); + return true; + } catch (Exception e) { + log.error("JWT验证失败: " + requestURI, e); + response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); + response.setContentType("application/json;charset=UTF-8"); + response.getWriter().write("{\"code\": 401, \"message\": \"Token验证失败\"}"); + return false; + } + } + + @Override + public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { + // 清除用户上下文 + UserContext.clear(); + } + + /** + * 从请求中获取token + */ + private String getTokenFromRequest(HttpServletRequest request) { + String authHeader = request.getHeader("Authorization"); + if (StringUtils.hasText(authHeader) && authHeader.startsWith("Bearer ")) { + return authHeader.substring(7); + } + return null; + } +} diff --git a/backend/src/main/java/com/sunnyfarm/mapper/AddressMapper.java b/backend/src/main/java/com/sunnyfarm/mapper/AddressMapper.java new file mode 100644 index 0000000..aa6c3a4 --- /dev/null +++ b/backend/src/main/java/com/sunnyfarm/mapper/AddressMapper.java @@ -0,0 +1,26 @@ +package com.sunnyfarm.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.sunnyfarm.entity.Address; +import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Param; +import org.apache.ibatis.annotations.Select; +import org.apache.ibatis.annotations.Update; + +import java.util.List; + +@Mapper +public interface AddressMapper extends BaseMapper
{ + + @Select("SELECT * FROM addresses WHERE user_id = #{userId} AND deleted = 0 ORDER BY is_default DESC, create_time DESC") + List
findByUserId(@Param("userId") Long userId); + + @Select("SELECT * FROM addresses WHERE user_id = #{userId} AND is_default = 1 AND deleted = 0") + Address findDefaultByUserId(@Param("userId") Long userId); + + @Update("UPDATE addresses SET is_default = 0 WHERE user_id = #{userId}") + int clearDefaultByUserId(@Param("userId") Long userId); + + @Select("SELECT COUNT(*) FROM addresses WHERE user_id = #{userId} AND deleted = 0") + Long countByUser(@Param("userId") Long userId); +} diff --git a/backend/src/main/java/com/sunnyfarm/mapper/AdminMapper.java b/backend/src/main/java/com/sunnyfarm/mapper/AdminMapper.java new file mode 100644 index 0000000..96276e6 --- /dev/null +++ b/backend/src/main/java/com/sunnyfarm/mapper/AdminMapper.java @@ -0,0 +1,22 @@ +package com.sunnyfarm.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.sunnyfarm.entity.Admin; +import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Param; +import org.apache.ibatis.annotations.Select; + +@Mapper +public interface AdminMapper extends BaseMapper { + + /** + * 根据用户名查询管理员 + */ + @Select("SELECT * FROM admins WHERE username = #{username}") + Admin selectByUsername(@Param("username") String username); + + /** + * 查询管理员详情(包含角色信息) + */ + Admin selectAdminWithRole(@Param("id") Long id); +} diff --git a/backend/src/main/java/com/sunnyfarm/mapper/AnnouncementMapper.java b/backend/src/main/java/com/sunnyfarm/mapper/AnnouncementMapper.java new file mode 100644 index 0000000..e529554 --- /dev/null +++ b/backend/src/main/java/com/sunnyfarm/mapper/AnnouncementMapper.java @@ -0,0 +1,9 @@ +package com.sunnyfarm.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.sunnyfarm.entity.Announcement; +import org.apache.ibatis.annotations.Mapper; + +@Mapper +public interface AnnouncementMapper extends BaseMapper { +} diff --git a/backend/src/main/java/com/sunnyfarm/mapper/CartMapper.java b/backend/src/main/java/com/sunnyfarm/mapper/CartMapper.java new file mode 100644 index 0000000..7625417 --- /dev/null +++ b/backend/src/main/java/com/sunnyfarm/mapper/CartMapper.java @@ -0,0 +1,18 @@ +package com.sunnyfarm.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.sunnyfarm.dto.CartItemDTO; +import com.sunnyfarm.entity.Cart; +import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Param; + +import java.util.List; + +@Mapper +public interface CartMapper extends BaseMapper { + + /** + * 获取用户购物车详情(包含商品和商家信息) + */ + List getUserCartItems(@Param("userId") Long userId); +} diff --git a/backend/src/main/java/com/sunnyfarm/mapper/CategoryMapper.java b/backend/src/main/java/com/sunnyfarm/mapper/CategoryMapper.java new file mode 100644 index 0000000..9537483 --- /dev/null +++ b/backend/src/main/java/com/sunnyfarm/mapper/CategoryMapper.java @@ -0,0 +1,22 @@ +package com.sunnyfarm.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.sunnyfarm.entity.Category; +import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Param; +import org.apache.ibatis.annotations.Select; + +import java.util.List; + +@Mapper +public interface CategoryMapper extends BaseMapper { + + @Select("SELECT * FROM categories WHERE parent_id = #{parentId} AND deleted = 0 ORDER BY sort ASC") + List findByParentId(@Param("parentId") Long parentId); + + @Select("SELECT * FROM categories WHERE status = 1 AND deleted = 0 ORDER BY sort ASC") + List findActiveCategories(); + + @Select("SELECT COUNT(*) FROM categories WHERE deleted = 0") + Long countCategories(); +} diff --git a/backend/src/main/java/com/sunnyfarm/mapper/ChatMessageMapper.java b/backend/src/main/java/com/sunnyfarm/mapper/ChatMessageMapper.java new file mode 100644 index 0000000..e32dafa --- /dev/null +++ b/backend/src/main/java/com/sunnyfarm/mapper/ChatMessageMapper.java @@ -0,0 +1,34 @@ +package com.sunnyfarm.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.sunnyfarm.entity.ChatMessage; +import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Param; + +import java.util.List; + +@Mapper +public interface ChatMessageMapper extends BaseMapper { + + /** + * 获取会话消息列表 + */ + List getSessionMessages(@Param("sessionId") Long sessionId, + @Param("offset") Integer offset, + @Param("limit") Integer limit); + + /** + * 标记消息为已读 + */ + int markMessagesAsRead(@Param("sessionId") Long sessionId, @Param("readerId") Long readerId); + + /** + * 根据发送者类型标记消息为已读 + */ + int markMessagesByTypeAsRead(@Param("sessionId") Long sessionId, @Param("senderType") Integer senderType); + + /** + * 获取未读消息数量 + */ + Integer getUnreadCount(@Param("sessionId") Long sessionId, @Param("receiverId") Long receiverId); +} diff --git a/backend/src/main/java/com/sunnyfarm/mapper/ChatSessionMapper.java b/backend/src/main/java/com/sunnyfarm/mapper/ChatSessionMapper.java new file mode 100644 index 0000000..9e49953 --- /dev/null +++ b/backend/src/main/java/com/sunnyfarm/mapper/ChatSessionMapper.java @@ -0,0 +1,27 @@ +package com.sunnyfarm.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.sunnyfarm.entity.ChatSession; +import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Param; + +import java.util.List; + +@Mapper +public interface ChatSessionMapper extends BaseMapper { + + /** + * 获取用户的聊天会话列表 + */ + List getUserSessions(@Param("userId") Long userId); + + /** + * 获取商家的聊天会话列表 + */ + List getMerchantSessions(@Param("merchantId") Long merchantId); + + /** + * 获取或创建聊天会话 + */ + ChatSession getOrCreateSession(@Param("userId") Long userId, @Param("merchantId") Long merchantId); +} diff --git a/backend/src/main/java/com/sunnyfarm/mapper/CommentMapper.java b/backend/src/main/java/com/sunnyfarm/mapper/CommentMapper.java new file mode 100644 index 0000000..a525b62 --- /dev/null +++ b/backend/src/main/java/com/sunnyfarm/mapper/CommentMapper.java @@ -0,0 +1,45 @@ +package com.sunnyfarm.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.sunnyfarm.entity.Comment; +import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Param; +import org.apache.ibatis.annotations.Select; + +import java.math.BigDecimal; + +@Mapper +public interface CommentMapper extends BaseMapper { + + @Select("SELECT c.*, u.nickname as user_name, u.avatar as user_avatar, " + + "p.name as product_name " + + "FROM reviews c " + + "LEFT JOIN users u ON c.user_id = u.id " + + "LEFT JOIN products p ON c.product_id = p.id " + + "WHERE c.product_id = #{productId} AND c.status = 1 " + + "ORDER BY c.created_at DESC") + IPage findCommentsByProduct(Page page, @Param("productId") Long productId); + + @Select("SELECT c.*, u.nickname as user_name, u.avatar as user_avatar, " + + "p.name as product_name " + + "FROM reviews c " + + "LEFT JOIN users u ON c.user_id = u.id " + + "LEFT JOIN products p ON c.product_id = p.id " + + "WHERE c.user_id = #{userId} " + + "ORDER BY c.created_at DESC") + IPage findCommentsByUser(Page page, @Param("userId") Long userId); + + @Select("SELECT AVG(rating) FROM reviews WHERE product_id = #{productId} AND status = 1 AND deleted = 0") + BigDecimal getAverageRating(@Param("productId") Long productId); + + @Select("SELECT COUNT(*) FROM reviews WHERE product_id = #{productId} AND status = 1 AND deleted = 0") + Long countByProduct(@Param("productId") Long productId); + + @Select("SELECT COUNT(*) FROM reviews WHERE user_id = #{userId} AND deleted = 0") + Long countByUser(@Param("userId") Long userId); + + @Select("SELECT * FROM reviews WHERE user_id = #{userId} AND product_id = #{productId} AND order_id = #{orderId} AND deleted = 0") + Comment findByUserAndProductAndOrder(@Param("userId") Long userId, @Param("productId") Long productId, @Param("orderId") Long orderId); +} diff --git a/backend/src/main/java/com/sunnyfarm/mapper/FavoriteMapper.java b/backend/src/main/java/com/sunnyfarm/mapper/FavoriteMapper.java new file mode 100644 index 0000000..3d12eaa --- /dev/null +++ b/backend/src/main/java/com/sunnyfarm/mapper/FavoriteMapper.java @@ -0,0 +1,39 @@ +package com.sunnyfarm.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.sunnyfarm.entity.Favorite; +import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Param; +import org.apache.ibatis.annotations.Select; + +@Mapper +public interface FavoriteMapper extends BaseMapper { + + /** + * 查询用户收藏列表(包含商品信息) + */ + IPage getUserFavoritesWithProduct(Page page, @Param("userId") Long userId); + + /** + * 检查用户是否收藏了某商品 + */ + Integer checkUserFavorite(@Param("userId") Long userId, @Param("productId") Long productId); + + @Select("SELECT f.*, p.name as product_name, p.price, p.main_image " + + "FROM favorites f " + + "LEFT JOIN products p ON f.product_id = p.id " + + "WHERE f.user_id = #{userId} AND f.deleted = 0 " + + "ORDER BY f.create_time DESC") + IPage findFavoritesByUser(Page page, @Param("userId") Long userId); + + @Select("SELECT * FROM favorites WHERE user_id = #{userId} AND product_id = #{productId} AND deleted = 0") + Favorite findByUserAndProduct(@Param("userId") Long userId, @Param("productId") Long productId); + + @Select("SELECT COUNT(*) FROM favorites WHERE user_id = #{userId} AND deleted = 0") + Long countByUser(@Param("userId") Long userId); + + @Select("SELECT COUNT(*) FROM favorites WHERE product_id = #{productId} AND deleted = 0") + Long countByProduct(@Param("productId") Long productId); +} diff --git a/backend/src/main/java/com/sunnyfarm/mapper/InventoryMapper.java b/backend/src/main/java/com/sunnyfarm/mapper/InventoryMapper.java new file mode 100644 index 0000000..0589065 --- /dev/null +++ b/backend/src/main/java/com/sunnyfarm/mapper/InventoryMapper.java @@ -0,0 +1,50 @@ +package com.sunnyfarm.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.sunnyfarm.dto.InventoryWithProduct; +import com.sunnyfarm.entity.Inventory; +import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Param; + +import java.util.List; + +@Mapper +public interface InventoryMapper extends BaseMapper { + + /** + * 获取商家库存分页数据(包含商品信息) + */ + IPage getMerchantInventoryPage(Page page, + @Param("merchantId") Long merchantId, + @Param("keyword") String keyword); + + /** + * 获取商家库存列表(包含商品信息) + */ + List getMerchantInventoryList(@Param("merchantId") Long merchantId); + + /** + * 获取商家库存列表(不分页) + */ + /** + * 获取库存不足商品 + */ + List getLowStockProducts(@Param("merchantId") Long merchantId); + + /** + * 获取缺货商品 + */ + List getOutOfStockProducts(@Param("merchantId") Long merchantId); + + /** + * 按商品ID删除库存 + */ + int deleteByProductId(@Param("productId") Long productId); + + /** + * 批量插入库存记录 + */ + int batchInsert(@Param("list") List inventoryList); +} diff --git a/backend/src/main/java/com/sunnyfarm/mapper/MerchantMapper.java b/backend/src/main/java/com/sunnyfarm/mapper/MerchantMapper.java new file mode 100644 index 0000000..02c0797 --- /dev/null +++ b/backend/src/main/java/com/sunnyfarm/mapper/MerchantMapper.java @@ -0,0 +1,48 @@ +package com.sunnyfarm.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.sunnyfarm.dto.MerchantDashboardDTO; +import com.sunnyfarm.entity.Merchant; +import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Param; +import org.apache.ibatis.annotations.Select; + +import java.math.BigDecimal; +import java.util.List; + +@Mapper +public interface MerchantMapper extends BaseMapper { + + /** + * 分页查询商家列表(包含用户信息) + */ + IPage selectMerchantPage(Page page, + @Param("keyword") String keyword, + @Param("status") Integer status); + + /** + * 根据用户ID查询商家信息(包含用户信息) + */ + Merchant selectByUserId(@Param("userId") Long userId); + + /** + * 查询待审核商家数量 + */ + @Select("SELECT COUNT(*) FROM merchants WHERE status = 0") + Long countPendingMerchants(); + + // Dashboard统计查询方法 + Long getTotalOrdersByMerchant(@Param("merchantId") Long merchantId); + Long getTodayOrdersByMerchant(@Param("merchantId") Long merchantId); + BigDecimal getTotalRevenueByMerchant(@Param("merchantId") Long merchantId); + BigDecimal getTodayRevenueByMerchant(@Param("merchantId") Long merchantId); + Long getTotalProductsByMerchant(@Param("merchantId") Long merchantId); + Long getActiveProductsByMerchant(@Param("merchantId") Long merchantId); + Long getTotalCustomersByMerchant(@Param("merchantId") Long merchantId); + + List getRecentOrdersByMerchant(@Param("merchantId") Long merchantId); + List getSalesTrendByMerchant(@Param("merchantId") Long merchantId); + List getTopProductsByMerchant(@Param("merchantId") Long merchantId); +} diff --git a/backend/src/main/java/com/sunnyfarm/mapper/OrderDetailMapper.java b/backend/src/main/java/com/sunnyfarm/mapper/OrderDetailMapper.java new file mode 100644 index 0000000..a50fb75 --- /dev/null +++ b/backend/src/main/java/com/sunnyfarm/mapper/OrderDetailMapper.java @@ -0,0 +1,27 @@ +package com.sunnyfarm.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.sunnyfarm.entity.OrderDetail; +import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Param; +import org.apache.ibatis.annotations.Select; + +import java.util.List; + +@Mapper +public interface OrderDetailMapper extends BaseMapper { + /** + * 获取订单详情(包含商家ID) + */ + List getOrderDetailsByOrderId(@Param("orderId") Long orderId); + + + @Select("SELECT od.*, p.name as product_name, p.main_image as product_image " + + "FROM order_details od " + + "LEFT JOIN products p ON od.product_id = p.id " + + "WHERE od.order_id = #{orderId} AND od.deleted = 0") + List findByOrderId(@Param("orderId") Long orderId); + + @Select("SELECT SUM(quantity) FROM order_details WHERE product_id = #{productId} AND deleted = 0") + Long getTotalSalesByProduct(@Param("productId") Long productId); +} diff --git a/backend/src/main/java/com/sunnyfarm/mapper/OrderMapper.java b/backend/src/main/java/com/sunnyfarm/mapper/OrderMapper.java new file mode 100644 index 0000000..0eeb65f --- /dev/null +++ b/backend/src/main/java/com/sunnyfarm/mapper/OrderMapper.java @@ -0,0 +1,55 @@ +package com.sunnyfarm.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.sunnyfarm.entity.Order; +import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Param; +import org.apache.ibatis.annotations.Select; + +import java.math.BigDecimal; +import java.time.LocalDateTime; +import java.util.List; + +@Mapper +public interface OrderMapper extends BaseMapper { + + @Select("SELECT o.*, u.nickname as user_name " + + "FROM orders o " + + "LEFT JOIN users u ON o.user_id = u.id " + + "WHERE o.id = #{id} AND o.deleted = 0") + Order findOrderWithUser(@Param("id") Long id); + + @Select("SELECT o.*, u.nickname as user_name " + + "FROM orders o " + + "LEFT JOIN users u ON o.user_id = u.id " + + "WHERE o.user_id = #{userId} AND o.deleted = 0 " + + "ORDER BY o.create_time DESC") + IPage findOrdersByUser(Page page, @Param("userId") Long userId); + + @Select("SELECT o.*, u.nickname as user_name " + + "FROM orders o " + + "LEFT JOIN users u ON o.user_id = u.id " + + "WHERE o.user_id = #{userId} AND o.status = #{status} AND o.deleted = 0 " + + "ORDER BY o.create_time DESC") + IPage findOrdersByUserAndStatus(Page page, @Param("userId") Long userId, @Param("status") Integer status); + + @Select("SELECT * FROM orders WHERE order_no = #{orderNo} AND deleted = 0") + Order findByOrderNo(@Param("orderNo") String orderNo); + + @Select("SELECT SUM(actual_amount) FROM orders WHERE status = 2 AND deleted = 0") + BigDecimal getTotalRevenue(); + + @Select("SELECT COUNT(*) FROM orders WHERE deleted = 0") + Long countOrders(); + + @Select("SELECT COUNT(*) FROM orders WHERE status = #{status} AND deleted = 0") + Long countOrdersByStatus(@Param("status") Integer status); + + @Select("SELECT COUNT(*) FROM orders WHERE user_id = #{userId} AND deleted = 0") + Long countOrdersByUser(@Param("userId") Long userId); + + @Select("SELECT COUNT(*) FROM orders WHERE pay_time >= #{startTime} AND pay_time <= #{endTime} AND deleted = 0") + Long countOrdersByTimeRange(@Param("startTime") LocalDateTime startTime, @Param("endTime") LocalDateTime endTime); +} diff --git a/backend/src/main/java/com/sunnyfarm/mapper/PaymentMapper.java b/backend/src/main/java/com/sunnyfarm/mapper/PaymentMapper.java new file mode 100644 index 0000000..b574de4 --- /dev/null +++ b/backend/src/main/java/com/sunnyfarm/mapper/PaymentMapper.java @@ -0,0 +1,9 @@ +package com.sunnyfarm.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.sunnyfarm.entity.Payment; +import org.apache.ibatis.annotations.Mapper; + +@Mapper +public interface PaymentMapper extends BaseMapper { +} diff --git a/backend/src/main/java/com/sunnyfarm/mapper/PermissionMapper.java b/backend/src/main/java/com/sunnyfarm/mapper/PermissionMapper.java new file mode 100644 index 0000000..0ba4acb --- /dev/null +++ b/backend/src/main/java/com/sunnyfarm/mapper/PermissionMapper.java @@ -0,0 +1,17 @@ +package com.sunnyfarm.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.sunnyfarm.entity.Permission; +import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Param; + +import java.util.List; + +@Mapper +public interface PermissionMapper extends BaseMapper { + + /** + * 根据角色ID查询权限列表 + */ + List selectPermissionsByRoleId(@Param("roleId") Long roleId); +} diff --git a/backend/src/main/java/com/sunnyfarm/mapper/ProductImageMapper.java b/backend/src/main/java/com/sunnyfarm/mapper/ProductImageMapper.java new file mode 100644 index 0000000..4b02d7b --- /dev/null +++ b/backend/src/main/java/com/sunnyfarm/mapper/ProductImageMapper.java @@ -0,0 +1,32 @@ +package com.sunnyfarm.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.sunnyfarm.entity.ProductImage; +import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Select; +import org.apache.ibatis.annotations.Delete; +import org.apache.ibatis.annotations.Param; + +import java.util.List; + +@Mapper +public interface ProductImageMapper extends BaseMapper { + + /** + * 根据商品ID获取图片列表 + */ + @Select("SELECT * FROM product_images WHERE product_id = #{productId} ORDER BY sort_order ASC, id ASC") + List getImagesByProductId(@Param("productId") Long productId); + + /** + * 根据商品ID删除所有图片 + */ + @Delete("DELETE FROM product_images WHERE product_id = #{productId}") + int deleteByProductId(@Param("productId") Long productId); + + /** + * 获取商品主图 + */ + @Select("SELECT * FROM product_images WHERE product_id = #{productId} AND is_main = 1 LIMIT 1") + ProductImage getMainImageByProductId(@Param("productId") Long productId); +} diff --git a/backend/src/main/java/com/sunnyfarm/mapper/ProductMapper.java b/backend/src/main/java/com/sunnyfarm/mapper/ProductMapper.java new file mode 100644 index 0000000..fdd55d4 --- /dev/null +++ b/backend/src/main/java/com/sunnyfarm/mapper/ProductMapper.java @@ -0,0 +1,52 @@ +package com.sunnyfarm.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.sunnyfarm.entity.Product; +import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Param; +import org.apache.ibatis.annotations.Select; +import org.apache.ibatis.annotations.Update; + +import java.util.List; + +@Mapper +public interface ProductMapper extends BaseMapper { + + @Select("SELECT p.*, c.name as category_name, u.nickname as seller_name " + + "FROM products p " + + "LEFT JOIN categories c ON p.category_id = c.id " + + "LEFT JOIN users u ON p.seller_id = u.id " + + "WHERE p.id = #{id} AND p.deleted = 0") + Product findProductWithDetails(@Param("id") Long id); + + @Select("SELECT p.*, c.name as category_name, u.nickname as seller_name " + + "FROM products p " + + "LEFT JOIN categories c ON p.category_id = c.id " + + "LEFT JOIN users u ON p.seller_id = u.id " + + "WHERE p.status = 1 AND p.deleted = 0 " + + "ORDER BY p.sort ASC, p.create_time DESC") + IPage findActiveProducts(Page page); + + @Select("SELECT p.*, c.name as category_name, u.nickname as seller_name " + + "FROM products p " + + "LEFT JOIN categories c ON p.category_id = c.id " + + "LEFT JOIN users u ON p.seller_id = u.id " + + "WHERE p.category_id = #{categoryId} AND p.status = 1 AND p.deleted = 0 " + + "ORDER BY p.sort ASC, p.create_time DESC") + IPage findProductsByCategory(Page page, @Param("categoryId") Long categoryId); + + @Select("SELECT * FROM products WHERE recommended = 1 AND status = 1 AND deleted = 0 " + + "ORDER BY sort ASC, create_time DESC LIMIT #{limit}") + List findRecommendedProducts(@Param("limit") Integer limit); + + @Update("UPDATE products SET sales = sales + #{quantity} WHERE id = #{productId}") + int updateSales(@Param("productId") Long productId, @Param("quantity") Integer quantity); + + @Update("UPDATE products SET stock = stock - #{quantity} WHERE id = #{productId}") + int updateStock(@Param("productId") Long productId, @Param("quantity") Integer quantity); + + @Select("SELECT COUNT(*) FROM products WHERE deleted = 0") + Long countProducts(); +} diff --git a/backend/src/main/java/com/sunnyfarm/mapper/RoleMapper.java b/backend/src/main/java/com/sunnyfarm/mapper/RoleMapper.java new file mode 100644 index 0000000..8db9544 --- /dev/null +++ b/backend/src/main/java/com/sunnyfarm/mapper/RoleMapper.java @@ -0,0 +1,15 @@ +package com.sunnyfarm.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.sunnyfarm.entity.Role; +import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Param; + +@Mapper +public interface RoleMapper extends BaseMapper { + + /** + * 根据角色ID查询角色及权限 + */ + Role selectRoleWithPermissions(@Param("roleId") Long roleId); +} diff --git a/backend/src/main/java/com/sunnyfarm/mapper/SystemConfigMapper.java b/backend/src/main/java/com/sunnyfarm/mapper/SystemConfigMapper.java new file mode 100644 index 0000000..1125692 --- /dev/null +++ b/backend/src/main/java/com/sunnyfarm/mapper/SystemConfigMapper.java @@ -0,0 +1,16 @@ +package com.sunnyfarm.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.sunnyfarm.entity.SystemConfig; +import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Select; + +@Mapper +public interface SystemConfigMapper extends BaseMapper { + + /** + * 根据配置键获取配置值 + */ + @Select("SELECT config_value FROM system_configs WHERE config_key = #{configKey}") + String selectValueByKey(String configKey); +} diff --git a/backend/src/main/java/com/sunnyfarm/mapper/SystemLogMapper.java b/backend/src/main/java/com/sunnyfarm/mapper/SystemLogMapper.java new file mode 100644 index 0000000..43cf2b9 --- /dev/null +++ b/backend/src/main/java/com/sunnyfarm/mapper/SystemLogMapper.java @@ -0,0 +1,24 @@ +package com.sunnyfarm.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.sunnyfarm.entity.SystemLog; +import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Select; + +import java.util.List; + +@Mapper +public interface SystemLogMapper extends BaseMapper { + + /** + * 获取最新的操作日志 + */ + @Select("SELECT * FROM system_logs ORDER BY created_at DESC LIMIT #{limit}") + List selectRecentLogs(int limit); + + /** + * 根据操作类型获取日志 + */ + @Select("SELECT * FROM system_logs WHERE operation_type = #{operationType} ORDER BY created_at DESC LIMIT #{limit}") + List selectLogsByType(String operationType, int limit); +} diff --git a/backend/src/main/java/com/sunnyfarm/mapper/UserMapper.java b/backend/src/main/java/com/sunnyfarm/mapper/UserMapper.java new file mode 100644 index 0000000..896cd35 --- /dev/null +++ b/backend/src/main/java/com/sunnyfarm/mapper/UserMapper.java @@ -0,0 +1,23 @@ +package com.sunnyfarm.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.sunnyfarm.entity.User; +import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Param; +import org.apache.ibatis.annotations.Select; + +@Mapper +public interface UserMapper extends BaseMapper { + + @Select("SELECT * FROM users WHERE username = #{username}") + User findByUsername(@Param("username") String username); + + @Select("SELECT * FROM users WHERE email = #{email}") + User findByEmail(@Param("email") String email); + + @Select("SELECT * FROM users WHERE phone = #{phone}") + User findByPhone(@Param("phone") String phone); + + @Select("SELECT COUNT(*) FROM users") + Long countUsers(); +} diff --git a/backend/src/main/java/com/sunnyfarm/service/AddressService.java b/backend/src/main/java/com/sunnyfarm/service/AddressService.java new file mode 100644 index 0000000..1cca816 --- /dev/null +++ b/backend/src/main/java/com/sunnyfarm/service/AddressService.java @@ -0,0 +1,44 @@ +package com.sunnyfarm.service; + +import com.baomidou.mybatisplus.extension.service.IService; +import com.sunnyfarm.entity.Address; + +import java.util.List; + +public interface AddressService extends IService
{ + + /** + * 获取用户地址列表 + */ + List
getUserAddresses(Long userId); + + /** + * 获取用户默认地址 + */ + Address getUserDefaultAddress(Long userId); + + /** + * 创建地址 + */ + Address createAddress(Address address); + + /** + * 更新地址 + */ + Address updateAddress(Long id, Address address); + + /** + * 删除地址 + */ + boolean deleteAddress(Long id); + + /** + * 设置默认地址 + */ + boolean setDefaultAddress(Long userId, Long addressId); + + /** + * 获取用户地址数量 + */ + Long getUserAddressCount(Long userId); +} diff --git a/backend/src/main/java/com/sunnyfarm/service/AdminService.java b/backend/src/main/java/com/sunnyfarm/service/AdminService.java new file mode 100644 index 0000000..a3a8476 --- /dev/null +++ b/backend/src/main/java/com/sunnyfarm/service/AdminService.java @@ -0,0 +1,48 @@ +package com.sunnyfarm.service; + +import com.baomidou.mybatisplus.extension.service.IService; +import com.sunnyfarm.dto.AdminLoginRequest; +import com.sunnyfarm.dto.AdminUpdateRequest; +import com.sunnyfarm.dto.AdminPasswordRequest; +import com.sunnyfarm.entity.Admin; + +import java.util.List; +import java.util.Map; + +public interface AdminService extends IService { + + /** + * 管理员登录 + */ + Admin login(AdminLoginRequest request); + + /** + * 根据用户名查询管理员 + */ + Admin getByUsername(String username); + + /** + * 获取管理员详情(包含角色权限) + */ + Admin getAdminWithRole(Long adminId); + + /** + * 更新最后登录时间 + */ + void updateLastLoginTime(Long adminId); + + /** + * 获取最新动态 + */ + List> getRecentActivities(); + + /** + * 更新管理员个人信息 + */ + boolean updateProfile(Long adminId, AdminUpdateRequest request); + + /** + * 修改管理员密码 + */ + boolean changePassword(Long adminId, AdminPasswordRequest request); +} diff --git a/backend/src/main/java/com/sunnyfarm/service/AlipayService.java b/backend/src/main/java/com/sunnyfarm/service/AlipayService.java new file mode 100644 index 0000000..a03168d --- /dev/null +++ b/backend/src/main/java/com/sunnyfarm/service/AlipayService.java @@ -0,0 +1,32 @@ +package com.sunnyfarm.service; + +import java.math.BigDecimal; +import java.util.Map; + +public interface AlipayService { + + /** + * 创建支付表单 + */ + String createPayForm(Long orderId, String subject, BigDecimal amount); + + /** + * 处理支付回调 + */ + boolean handlePayNotify(Map params); + + /** + * 验证签名 + */ + boolean verifySign(Map params); + + /** + * 查询支付结果 + */ + Map queryPayResult(String outTradeNo); + + /** + * 申请退款 + */ + Map refund(String outTradeNo, BigDecimal refundAmount, String refundReason); +} diff --git a/backend/src/main/java/com/sunnyfarm/service/AnnouncementService.java b/backend/src/main/java/com/sunnyfarm/service/AnnouncementService.java new file mode 100644 index 0000000..adad2f4 --- /dev/null +++ b/backend/src/main/java/com/sunnyfarm/service/AnnouncementService.java @@ -0,0 +1,27 @@ +package com.sunnyfarm.service; + +import com.baomidou.mybatisplus.extension.service.IService; +import com.sunnyfarm.entity.Announcement; + +public interface AnnouncementService extends IService { + + /** + * 创建公告 + */ + boolean createAnnouncement(Announcement announcement, Long creatorId, String creatorName); + + /** + * 更新公告 + */ + boolean updateAnnouncement(Announcement announcement, Long operatorId, String operatorName); + + /** + * 删除公告 + */ + boolean deleteAnnouncement(Long id, Long operatorId, String operatorName); + + /** + * 发布公告 + */ + boolean publishAnnouncement(Long id, Long operatorId, String operatorName); +} diff --git a/backend/src/main/java/com/sunnyfarm/service/CartService.java b/backend/src/main/java/com/sunnyfarm/service/CartService.java new file mode 100644 index 0000000..77b370c --- /dev/null +++ b/backend/src/main/java/com/sunnyfarm/service/CartService.java @@ -0,0 +1,80 @@ +package com.sunnyfarm.service; + +import com.baomidou.mybatisplus.extension.service.IService; +import com.sunnyfarm.dto.CartItemDTO; +import com.sunnyfarm.entity.Cart; + +import java.util.List; + +public interface CartService extends IService { + + /** + * 添加商品到购物车 + */ + boolean addToCart(Long userId, Long productId, Integer quantity); + + /** + * 更新购物车商品数量 + */ + boolean updateCartQuantity(Long userId, Long productId, Integer quantity); + + /** + * 从购物车删除商品 + */ + boolean removeFromCart(Long userId, Long productId); + + /** + * 获取用户购物车列表 + */ + List getUserCart(Long userId); + + /** + * 获取用户购物车详情列表(包含商品信息) + */ + List getUserCartItems(Long userId); + + /** + * 获取用户购物车商品数量 + */ + Long getUserCartCount(Long userId); + + /** + * 获取购物车数量 + */ + Long getCartCount(Long userId); + + /** + * 选择/取消选择购物车商品 + */ + boolean toggleCartSelection(Long userId, Long productId, Boolean selected); + + /** + * 选择购物车商品 + */ + boolean toggleCartSelect(Long userId, Long productId, Boolean selected); + + /** + * 全选/取消全选购物车商品 + */ + boolean toggleAllCartSelection(Long userId, Boolean selected); + + /** + * 清空购物车 + */ + boolean clearCart(Long userId); + + /** + * 批量删除购物车商品 + */ + boolean batchRemoveFromCart(Long userId, List productIds); + + /** + * 获取选中的购物车项 + */ + List getSelectedCartItems(Long userId, List cartIds); + + /** + * 清空指定购物车项 + */ + boolean removeCartItems(List cartIds); +} diff --git a/backend/src/main/java/com/sunnyfarm/service/CategoryService.java b/backend/src/main/java/com/sunnyfarm/service/CategoryService.java new file mode 100644 index 0000000..e395c94 --- /dev/null +++ b/backend/src/main/java/com/sunnyfarm/service/CategoryService.java @@ -0,0 +1,59 @@ +package com.sunnyfarm.service; + +import com.baomidou.mybatisplus.extension.service.IService; +import com.sunnyfarm.entity.Category; + +import java.util.List; + +public interface CategoryService extends IService { + + /** + * 获取分类树形结构 + */ + List getCategoryTree(); + + /** + * 根据父ID获取子分类 + */ + List getChildCategories(Long parentId); + + /** + * 获取活跃分类 + */ + List getActiveCategories(); + + /** + * 获取所有分类 + */ + List getAllCategories(); + + /** + * 获取主分类(父级分类) + */ + List getMainCategories(); + + /** + * 创建分类 + */ + Category createCategory(Category category); + + /** + * 更新分类 + */ + Category updateCategory(Long id, Category category); + + /** + * 删除分类 + */ + boolean deleteCategory(Long id); + + /** + * 更新分类排序 + */ + boolean updateCategorySort(Long id, Integer sort); + + /** + * 切换分类状态 + */ + boolean toggleCategoryStatus(Long id); +} diff --git a/backend/src/main/java/com/sunnyfarm/service/ChatService.java b/backend/src/main/java/com/sunnyfarm/service/ChatService.java new file mode 100644 index 0000000..7215e23 --- /dev/null +++ b/backend/src/main/java/com/sunnyfarm/service/ChatService.java @@ -0,0 +1,60 @@ +package com.sunnyfarm.service; + +import com.sunnyfarm.dto.SendMessageRequest; +import com.sunnyfarm.entity.ChatMessage; +import com.sunnyfarm.entity.ChatSession; + +import java.util.List; + +public interface ChatService { + + /** + * 获取用户的聊天会话列表 + */ + List getUserSessions(Long userId); + + /** + * 获取商家的聊天会话列表 + */ + List getMerchantSessions(Long merchantId); + + /** + * 获取或创建聊天会话 + */ + ChatSession getOrCreateSession(Long userId, Long merchantId); + + /** + * 获取会话消息列表 + */ + List getSessionMessages(Long sessionId, Integer page, Integer size); + + /** + * 发送消息(用户端) + */ + ChatMessage sendUserMessage(Long userId, SendMessageRequest request); + + /** + * 发送消息(商家端) + */ + ChatMessage sendMerchantMessage(Long merchantId, Long sessionId, String content, Integer messageType); + + /** + * 标记消息为已读 + */ + boolean markMessagesAsRead(Long sessionId, Long readerId); + + /** + * 用户标记消息为已读(标记商家发送的消息) + */ + boolean markUserMessagesAsRead(Long sessionId, Long userId); + + /** + * 商家标记消息为已读(标记用户发送的消息) + */ + boolean markMerchantMessagesAsRead(Long sessionId, Long merchantId); + + /** + * 获取未读消息数量 + */ + Integer getUnreadCount(Long sessionId, Long receiverId); +} diff --git a/backend/src/main/java/com/sunnyfarm/service/CommentService.java b/backend/src/main/java/com/sunnyfarm/service/CommentService.java new file mode 100644 index 0000000..f03fb4c --- /dev/null +++ b/backend/src/main/java/com/sunnyfarm/service/CommentService.java @@ -0,0 +1,61 @@ +package com.sunnyfarm.service; + +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.baomidou.mybatisplus.extension.service.IService; +import com.sunnyfarm.entity.Comment; + +import java.math.BigDecimal; + +public interface CommentService extends IService { + + /** + * 创建评论 + */ + Comment createComment(Comment comment); + + /** + * 获取商品评论列表 + */ + IPage getProductComments(Page page, Long productId); + + /** + * 获取用户评论列表 + */ + IPage getUserComments(Page page, Long userId); + + /** + * 获取商品平均评分 + */ + BigDecimal getProductAverageRating(Long productId); + + /** + * 获取商品评论数量 + */ + Long getProductCommentCount(Long productId); + + /** + * 检查用户是否已评论 + */ + boolean hasUserCommented(Long userId, Long productId, Long orderId); + + /** + * 回复评论 + */ + boolean replyComment(Long commentId, String reply); + + /** + * 审核评论 + */ + boolean auditComment(Long commentId, Integer status); + + /** + * 删除评论 + */ + boolean deleteComment(Long commentId); + + /** + * 获取所有评论分页列表 + */ + IPage getAllComments(Page page, String keyword, Integer status); +} diff --git a/backend/src/main/java/com/sunnyfarm/service/FavoriteService.java b/backend/src/main/java/com/sunnyfarm/service/FavoriteService.java new file mode 100644 index 0000000..026bded --- /dev/null +++ b/backend/src/main/java/com/sunnyfarm/service/FavoriteService.java @@ -0,0 +1,44 @@ +package com.sunnyfarm.service; + +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.baomidou.mybatisplus.extension.service.IService; +import com.sunnyfarm.entity.Favorite; + +public interface FavoriteService extends IService { + + /** + * 添加收藏 + */ + boolean addFavorite(Long userId, Long productId); + + /** + * 取消收藏 + */ + boolean removeFavorite(Long userId, Long productId); + + /** + * 检查是否已收藏 + */ + boolean isFavorited(Long userId, Long productId); + + /** + * 获取用户收藏列表 + */ + IPage getUserFavorites(Page page, Long userId); + + /** + * 获取用户收藏数量 + */ + Long getUserFavoriteCount(Long userId); + + /** + * 获取商品收藏数量 + */ + Long getProductFavoriteCount(Long productId); + + /** + * 批量取消收藏 + */ + boolean batchRemoveFavorite(Long userId, java.util.List productIds); +} diff --git a/backend/src/main/java/com/sunnyfarm/service/InventoryService.java b/backend/src/main/java/com/sunnyfarm/service/InventoryService.java new file mode 100644 index 0000000..62ead32 --- /dev/null +++ b/backend/src/main/java/com/sunnyfarm/service/InventoryService.java @@ -0,0 +1,87 @@ +package com.sunnyfarm.service; + +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.baomidou.mybatisplus.extension.service.IService; +import com.sunnyfarm.dto.InventoryWithProduct; +import com.sunnyfarm.entity.Inventory; + +import java.util.List; + +public interface InventoryService extends IService { + + /** + * 根据商品ID获取库存信息 + */ + Inventory getByProductId(Long productId); + + /** + * 获取商品库存数量 + */ + Integer getStockQuantity(Long productId); + + /** + * 更新库存数量 + */ + boolean updateStockQuantity(Long productId, Integer quantity); + + /** + * 创建或更新库存 + */ + boolean createOrUpdateInventory(Long productId, Integer stockQuantity, int warningQuantity); + + /** + * 批量更新库存 + */ + boolean batchUpdateStock(List productIds, List quantities); + + /** + * 设置预警数量 + */ + boolean setWarningQuantity(Long productId, Integer warningQuantity); + + /** + * 获取库存预警商品 + */ + List getWarningProducts(Long merchantId); + + /** + * 分页获取商家库存信息(包含商品信息) + */ + IPage getMerchantInventoryPage(Page page, Long merchantId, String keyword); + + /** + * 获取商家库存统计 + */ + InventoryStatistics getMerchantInventoryStats(Long merchantId); + + /** + * 扣减库存 + */ + boolean decreaseStock(Long productId, Integer quantity); + + /** + * 增加库存 + */ + boolean increaseStock(Long productId, Integer quantity); + + /** + * 库存统计内部类 + */ + class InventoryStatistics { + private Integer totalProducts; // 商品总数 + private Integer inStockProducts; // 有库存商品数 + private Integer warningProducts; // 预警商品数 + private Integer outStockProducts; // 缺货商品数 + + // getters and setters + public Integer getTotalProducts() { return totalProducts; } + public void setTotalProducts(Integer totalProducts) { this.totalProducts = totalProducts; } + public Integer getInStockProducts() { return inStockProducts; } + public void setInStockProducts(Integer inStockProducts) { this.inStockProducts = inStockProducts; } + public Integer getWarningProducts() { return warningProducts; } + public void setWarningProducts(Integer warningProducts) { this.warningProducts = warningProducts; } + public Integer getOutStockProducts() { return outStockProducts; } + public void setOutStockProducts(Integer outStockProducts) { this.outStockProducts = outStockProducts; } + } +} diff --git a/backend/src/main/java/com/sunnyfarm/service/MerchantService.java b/backend/src/main/java/com/sunnyfarm/service/MerchantService.java new file mode 100644 index 0000000..78e48c1 --- /dev/null +++ b/backend/src/main/java/com/sunnyfarm/service/MerchantService.java @@ -0,0 +1,72 @@ +package com.sunnyfarm.service; + +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.baomidou.mybatisplus.extension.service.IService; +import com.sunnyfarm.dto.AdminMerchantPageDTO; +import com.sunnyfarm.dto.MerchantDashboardDTO; +import com.sunnyfarm.dto.MerchantRegisterRequest; +import com.sunnyfarm.entity.Merchant; + +public interface MerchantService extends IService { + + /** + * 商家注册 + */ + boolean register(MerchantRegisterRequest request, Long userId); + + /** + * 商家注册(仅请求对象) + */ + boolean register(MerchantRegisterRequest request); + + /** + * 商家登录 + */ + Merchant login(String username, String password); + + /** + * 根据用户ID获取商家信息 + */ + Merchant getByUserId(Long userId); + + /** + * 审核商家 + */ + boolean auditMerchant(Long merchantId, Integer status, String remark, Long operatorId, String operatorName); + + /** + * 验证商家 + */ + boolean verifyMerchant(Long merchantId, Integer status, String remark); + + /** + * 更新商家信息 + */ + boolean updateMerchantInfo(Merchant merchant); + + /** + * 获取商家Dashboard数据 + */ + MerchantDashboardDTO getDashboardData(Long merchantId); + + /** + * 分页获取商家列表(管理员用) + */ + IPage getAdminMerchantPage(Page page, String keyword, Integer status); + + /** + * 分页获取商家列表 + */ + IPage getMerchantPage(Page page, String keyword, Integer status); + + /** + * 根据用户ID获取商家ID + */ + Long getMerchantIdByUserId(Long userId); + + /** + * 修改密码 + */ + boolean changePassword(Long userId, String oldPassword, String newPassword); +} diff --git a/backend/src/main/java/com/sunnyfarm/service/OrderDetailService.java b/backend/src/main/java/com/sunnyfarm/service/OrderDetailService.java new file mode 100644 index 0000000..fd7caae --- /dev/null +++ b/backend/src/main/java/com/sunnyfarm/service/OrderDetailService.java @@ -0,0 +1,19 @@ +package com.sunnyfarm.service; + +import com.baomidou.mybatisplus.extension.service.IService; +import com.sunnyfarm.entity.OrderDetail; + +import java.util.List; + +public interface OrderDetailService extends IService { + + /** + * 根据订单ID获取订单详情列表 + */ + List getByOrderId(Long orderId); + + /** + * 批量保存订单详情 + */ + boolean batchSave(List orderDetails); +} diff --git a/backend/src/main/java/com/sunnyfarm/service/OrderService.java b/backend/src/main/java/com/sunnyfarm/service/OrderService.java new file mode 100644 index 0000000..40eb4ff --- /dev/null +++ b/backend/src/main/java/com/sunnyfarm/service/OrderService.java @@ -0,0 +1,73 @@ +package com.sunnyfarm.service; + +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.baomidou.mybatisplus.extension.service.IService; +import com.sunnyfarm.dto.OrderCreateRequest; +import com.sunnyfarm.entity.Order; + +import java.math.BigDecimal; +import java.util.List; +import java.util.Map; + +public interface OrderService extends IService { + + /** + * 创建订单(根据请求类型自动判断) + */ + Order createOrder(Long userId, OrderCreateRequest request); + + /** + * 创建订单(购物车选中商品) + */ + Order createOrder(Long userId, Long addressId, String remark); + + /** + * 根据购物车ID创建订单 + */ + Order createOrder(Order order, List cartIds); + + /** + * 根据商品信息创建订单(立即购买) + */ + Order createOrderFromProducts(Long userId, OrderCreateRequest request); + + /** + * 根据购物车ID创建订单 + */ + Order createOrderFromCart(Long userId, List cartIds, String remark); + + // 订单详情查询 + Order getOrderDetail(Long orderId); + Order getOrderDetail(Long orderId, Long userId); + Order getOrderByOrderNo(String orderNo); + + // 订单列表查询 + IPage getUserOrders(Page page, Long userId, Integer status); + IPage getMerchantOrders(Page page, Long merchantId, Integer status); + IPage getAllOrders(Page page, String keyword, Integer status); + + // 订单操作 + boolean payOrder(Long orderId, String payTransactionId); + boolean cancelOrder(Long orderId, String reason); + boolean cancelOrder(Long orderId, Long userId); + boolean shipOrder(Long orderId, String shipCompany, String shipNo); + boolean confirmReceive(Long orderId); + boolean confirmOrder(Long orderId, Long userId); + boolean refundOrder(Long orderId, String reason, BigDecimal amount); + boolean applyRefund(Long orderId, Long userId, String reason); + boolean deleteOrder(Long orderId, Long userId); + + // 管理员操作 + boolean updateOrderStatus(Long orderId, Integer status); + boolean handleRefund(Long orderId, Boolean approved, String remark); + + // 定时任务 + void cancelTimeoutOrders(); + void autoConfirmReceive(); + + // 统计数据 + BigDecimal getTotalRevenue(); + BigDecimal getTodayRevenue(); + Map getOrderStatistics(); +} diff --git a/backend/src/main/java/com/sunnyfarm/service/PaymentService.java b/backend/src/main/java/com/sunnyfarm/service/PaymentService.java new file mode 100644 index 0000000..fff94a8 --- /dev/null +++ b/backend/src/main/java/com/sunnyfarm/service/PaymentService.java @@ -0,0 +1,39 @@ +package com.sunnyfarm.service; + +import com.baomidou.mybatisplus.extension.service.IService; +import com.sunnyfarm.entity.Payment; + +import java.math.BigDecimal; + +public interface PaymentService extends IService { + + /** + * 创建支付记录 + */ + Payment createPayment(Long orderId, BigDecimal amount); + + /** + * 根据订单ID获取支付记录 + */ + Payment getByOrderId(Long orderId); + + /** + * 根据支付单号获取支付记录 + */ + Payment getByPaymentNo(String paymentNo); + + /** + * 更新支付状态为已支付 + */ + boolean updatePaymentSuccess(String paymentNo, String tradeNo, BigDecimal buyerPayAmount, String buyerLogonId); + + /** + * 更新支付状态为失败 + */ + boolean updatePaymentFailed(String paymentNo, String failReason); + + /** + * 退款 + */ + boolean refundPayment(String paymentNo, BigDecimal refundAmount, String refundReason); +} diff --git a/backend/src/main/java/com/sunnyfarm/service/PermissionService.java b/backend/src/main/java/com/sunnyfarm/service/PermissionService.java new file mode 100644 index 0000000..279a450 --- /dev/null +++ b/backend/src/main/java/com/sunnyfarm/service/PermissionService.java @@ -0,0 +1,14 @@ +package com.sunnyfarm.service; + +import com.baomidou.mybatisplus.extension.service.IService; +import com.sunnyfarm.entity.Permission; + +import java.util.List; + +public interface PermissionService extends IService { + + /** + * 根据角色ID查询权限列表 + */ + List getPermissionsByRoleId(Long roleId); +} diff --git a/backend/src/main/java/com/sunnyfarm/service/ProductImageService.java b/backend/src/main/java/com/sunnyfarm/service/ProductImageService.java new file mode 100644 index 0000000..7f1409f --- /dev/null +++ b/backend/src/main/java/com/sunnyfarm/service/ProductImageService.java @@ -0,0 +1,44 @@ +package com.sunnyfarm.service; + +import com.baomidou.mybatisplus.extension.service.IService; +import com.sunnyfarm.entity.ProductImage; + +import java.util.List; + +public interface ProductImageService extends IService { + + /** + * 根据商品ID获取图片列表 + */ + List getImagesByProductId(Long productId); + + /** + * 为商品添加图片 + */ + boolean addProductImages(Long productId, List imageUrls); + + /** + * 更新商品图片(先删除旧的,再添加新的) + */ + boolean updateProductImages(Long productId, List imageUrls); + + /** + * 删除商品的所有图片 + */ + boolean deleteProductImages(Long productId); + + /** + * 设置主图 + */ + boolean setMainImage(Long productId, String imageUrl); + + /** + * 获取商品主图URL + */ + String getMainImageUrl(Long productId); + + /** + * 获取商品所有图片URL列表 + */ + List getImageUrls(Long productId); +} diff --git a/backend/src/main/java/com/sunnyfarm/service/ProductService.java b/backend/src/main/java/com/sunnyfarm/service/ProductService.java new file mode 100644 index 0000000..514fe0c --- /dev/null +++ b/backend/src/main/java/com/sunnyfarm/service/ProductService.java @@ -0,0 +1,111 @@ +package com.sunnyfarm.service; + +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.baomidou.mybatisplus.extension.service.IService; +import com.sunnyfarm.dto.MerchantDashboardDTO; +import com.sunnyfarm.entity.Product; + +import java.math.BigDecimal; +import java.util.List; + +public interface ProductService extends IService { + + /** + * 分页查询产品列表 + */ + IPage getProductList(Page page, String keyword, Long categoryId, + BigDecimal minPrice, BigDecimal maxPrice, String sort); + + /** + * 获取产品详情 + */ + Product getProductDetail(Long id); + + /** + * 获取热门产品 + */ + List getHotProducts(Integer limit); + + /** + * 获取新品推荐 + */ + List getNewProducts(Integer limit); + + /** + * 获取分类产品 + */ + IPage getCategoryProducts(Page page, Long categoryId, String sort); + + /** + * 搜索产品 + */ + IPage searchProducts(Page page, String keyword, String sort); + + /** + * 创建产品 + */ + Product createProduct(Product product); + + /** + * 更新产品 + */ + Product updateProduct(Long id, Product product); + + /** + * 删除产品 + */ + boolean deleteProduct(Long id); + + /** + * 更新产品库存 + */ + boolean updateProductStock(Long id, Integer stock); + + /** + * 更新产品状态 + */ + boolean updateProductStatus(Long id, Integer status); + + // ==================== 商家专用方法 ==================== + + /** + * 获取商家商品分页列表 + */ + IPage getMerchantProductPage(Page page, Long merchantId, String keyword, Integer status); + + /** + * 创建商家商品 + */ + Product createMerchantProduct(Product product); + + /** + * 更新商家商品 + */ + Product updateMerchantProduct(Product product); + + /** + * 删除商家商品 + */ + boolean deleteMerchantProduct(Long id); + + /** + * 更新商家商品状态 + */ + boolean updateMerchantProductStatus(Long id, Integer status); + + /** + * 获取商家商品详情 + */ + Product getMerchantProductDetail(Long id, Long merchantId); + + /** + * 获取商家商品总数 + */ + Long getProductCountByMerchant(Long merchantId); + + /** + * 获取商家热销商品排行 + */ + List getTopProductsByMerchant(Long merchantId, Integer limit); +} diff --git a/backend/src/main/java/com/sunnyfarm/service/RoleService.java b/backend/src/main/java/com/sunnyfarm/service/RoleService.java new file mode 100644 index 0000000..b3c3f50 --- /dev/null +++ b/backend/src/main/java/com/sunnyfarm/service/RoleService.java @@ -0,0 +1,12 @@ +package com.sunnyfarm.service; + +import com.baomidou.mybatisplus.extension.service.IService; +import com.sunnyfarm.entity.Role; + +public interface RoleService extends IService { + + /** + * 根据角色ID查询角色及权限 + */ + Role getRoleWithPermissions(Long roleId); +} diff --git a/backend/src/main/java/com/sunnyfarm/service/StatisticsService.java b/backend/src/main/java/com/sunnyfarm/service/StatisticsService.java new file mode 100644 index 0000000..4e96a21 --- /dev/null +++ b/backend/src/main/java/com/sunnyfarm/service/StatisticsService.java @@ -0,0 +1,26 @@ +package com.sunnyfarm.service; + +import java.util.Map; + +public interface StatisticsService { + + /** + * 获取系统概览统计 + */ + Map getOverviewStatistics(); + + /** + * 获取销售统计 + */ + Map getSalesStatistics(String startDate, String endDate); + + /** + * 获取产品统计 + */ + Map getProductStatistics(); + + /** + * 获取用户统计 + */ + Map getUserStatistics(); +} diff --git a/backend/src/main/java/com/sunnyfarm/service/SystemConfigService.java b/backend/src/main/java/com/sunnyfarm/service/SystemConfigService.java new file mode 100644 index 0000000..1221575 --- /dev/null +++ b/backend/src/main/java/com/sunnyfarm/service/SystemConfigService.java @@ -0,0 +1,29 @@ +package com.sunnyfarm.service; + +import com.baomidou.mybatisplus.extension.service.IService; +import com.sunnyfarm.entity.SystemConfig; + +import java.util.Map; + +public interface SystemConfigService extends IService { + + /** + * 获取系统配置 + */ + Map getSystemConfig(); + + /** + * 更新系统配置 + */ + boolean updateSystemConfig(Map config, Long operatorId, String operatorName); + + /** + * 根据键获取配置值 + */ + String getConfigValue(String key); + + /** + * 根据键获取配置值,如果不存在则返回默认值 + */ + String getConfigValue(String key, String defaultValue); +} diff --git a/backend/src/main/java/com/sunnyfarm/service/SystemLogService.java b/backend/src/main/java/com/sunnyfarm/service/SystemLogService.java new file mode 100644 index 0000000..fa2074e --- /dev/null +++ b/backend/src/main/java/com/sunnyfarm/service/SystemLogService.java @@ -0,0 +1,92 @@ +package com.sunnyfarm.service; + +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.baomidou.mybatisplus.extension.service.IService; +import com.sunnyfarm.entity.SystemLog; + +import java.util.List; +import java.util.Map; + +public interface SystemLogService extends IService { + + /** + * 记录操作日志 + */ + void logOperation(String operationType, String operationDesc, Long operatorId, String operatorName, Integer operatorType); + + /** + * 记录成功日志 + */ + void recordSuccessLog(String operationType, String operationDesc, Long operatorId, String operatorName, int operatorType); + + /** + * 记录失败日志 + */ + void recordFailLog(String operationType, String operationDesc, Long operatorId, String operatorName, int operatorType, String errorMsg); + + /** + * 获取最近活动记录 + */ + List> getRecentActivities(int limit); + + /** + * 记录用户登录日志 + */ + void logUserLogin(String username, Long userId, String ipAddress, String userAgent, boolean success, String errorMsg); + + /** + * 记录商家登录日志 + */ + void logMerchantLogin(String username, Long userId, String ipAddress, String userAgent, boolean success, String errorMsg); + + /** + * 记录管理员登录日志 + */ + void logAdminLogin(String username, Long adminId, String ipAddress, String userAgent, boolean success, String errorMsg); + + /** + * 分页查询系统日志 + */ + IPage pageSystemLogs(Page page, String operationType, Integer operatorType, String startTime, String endTime); + + /** + * 记录商品审核日志 + */ + void logProductAudit(String productName, Long operatorId, String operatorName, boolean approved); + + /** + * 记录商家审核日志 + */ + void logMerchantAudit(String merchantName, Long operatorId, String operatorName, boolean approved); + + /** + * 记录订单创建日志 + */ + void logOrderCreate(String orderNo, Long userId); + + /** + * 记录订单支付日志 + */ + void logOrderPay(String orderNo, Long userId); + + /** + * 记录订单取消日志 + */ + void logOrderCancel(String orderNo, Long userId); + + /** + * 记录订单发货日志 + */ + void logOrderShip(String orderNo, Long operatorId); + + /** + * 记录订单完成日志 + */ + void logOrderComplete(String orderNo, Long userId); + + /** + * 记录订单退款日志 + */ + void logOrderRefund(String orderNo, Long operatorId); +} diff --git a/backend/src/main/java/com/sunnyfarm/service/UserService.java b/backend/src/main/java/com/sunnyfarm/service/UserService.java new file mode 100644 index 0000000..6936b46 --- /dev/null +++ b/backend/src/main/java/com/sunnyfarm/service/UserService.java @@ -0,0 +1,69 @@ +package com.sunnyfarm.service; + +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.baomidou.mybatisplus.extension.service.IService; +import com.sunnyfarm.entity.User; + +public interface UserService extends IService { + + /** + * 用户注册 + */ + User register(User user); + + /** + * 用户登录 + */ + User login(String username, String password); + + /** + * 更新用户信息 + */ + User updateProfile(User user); + + /** + * 修改密码 + */ + boolean changePassword(Long userId, String oldPassword, String newPassword); + + /** + * 发送邮箱验证码 + */ + boolean sendEmailCode(String email); + + /** + * 验证邮箱验证码 + */ + boolean verifyEmailCode(String email, String code); + + /** + * 获取用户列表 + */ + IPage getUserList(Page page, String keyword, Integer status); + + /** + * 更新用户状态 + */ + boolean updateUserStatus(Long userId, Integer status); + + /** + * 根据用户名或手机号查找用户 + */ + User findByUsernameOrPhone(String username); + + /** + * 检查用户名是否存在 + */ + boolean existsByUsername(String username); + + /** + * 检查手机号是否存在 + */ + boolean existsByPhone(String phone); + + /** + * 检查邮箱是否存在 + */ + boolean existsByEmail(String email); +} diff --git a/backend/src/main/java/com/sunnyfarm/service/impl/AddressServiceImpl.java b/backend/src/main/java/com/sunnyfarm/service/impl/AddressServiceImpl.java new file mode 100644 index 0000000..c1f4781 --- /dev/null +++ b/backend/src/main/java/com/sunnyfarm/service/impl/AddressServiceImpl.java @@ -0,0 +1,93 @@ +package com.sunnyfarm.service.impl; + +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.sunnyfarm.entity.Address; +import com.sunnyfarm.mapper.AddressMapper; +import com.sunnyfarm.service.AddressService; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.time.LocalDateTime; +import java.util.List; + +@Service +@Transactional +public class AddressServiceImpl extends ServiceImpl implements AddressService { + + @Override + public List
getUserAddresses(Long userId) { + QueryWrapper
wrapper = new QueryWrapper<>(); + wrapper.eq("user_id", userId); + wrapper.orderByDesc("is_default", "created_at"); + return list(wrapper); + } + + @Override + public Address getUserDefaultAddress(Long userId) { + QueryWrapper
wrapper = new QueryWrapper<>(); + wrapper.eq("user_id", userId); + wrapper.eq("is_default", true); + return getOne(wrapper); + } + + @Override + public Address createAddress(Address address) { + if (address.getIsDefault()) { + UpdateWrapper
updateWrapper = new UpdateWrapper<>(); + updateWrapper.eq("user_id", address.getUserId()); + updateWrapper.set("is_default", false); + update(updateWrapper); + } + + address.setCreateTime(LocalDateTime.now()); + address.setUpdateTime(LocalDateTime.now()); + save(address); + return address; + } + + @Override + public Address updateAddress(Long id, Address address) { + address.setId(id); + if (address.getIsDefault()) { + UpdateWrapper
updateWrapper = new UpdateWrapper<>(); + updateWrapper.eq("user_id", address.getUserId()); + updateWrapper.ne("id", id); + updateWrapper.set("is_default", false); + update(updateWrapper); + } + + address.setUpdateTime(LocalDateTime.now()); + updateById(address); + return address; + } + + @Override + public boolean deleteAddress(Long id) { + return removeById(id); + } + + @Override + public boolean setDefaultAddress(Long userId, Long addressId) { + UpdateWrapper
updateWrapper = new UpdateWrapper<>(); + updateWrapper.eq("user_id", userId); + updateWrapper.set("is_default", false); + update(updateWrapper); + + Address address = getById(addressId); + if (address != null && address.getUserId().equals(userId)) { + address.setIsDefault(true); + address.setUpdateTime(LocalDateTime.now()); + return updateById(address); + } + return false; + } + + @Override + public Long getUserAddressCount(Long userId) { + QueryWrapper
wrapper = new QueryWrapper<>(); + wrapper.eq("user_id", userId); + return count(wrapper); + } +} diff --git a/backend/src/main/java/com/sunnyfarm/service/impl/AdminServiceImpl.java b/backend/src/main/java/com/sunnyfarm/service/impl/AdminServiceImpl.java new file mode 100644 index 0000000..41f765c --- /dev/null +++ b/backend/src/main/java/com/sunnyfarm/service/impl/AdminServiceImpl.java @@ -0,0 +1,138 @@ +package com.sunnyfarm.service.impl; + +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.sunnyfarm.dto.AdminLoginRequest; +import com.sunnyfarm.dto.AdminUpdateRequest; +import com.sunnyfarm.dto.AdminPasswordRequest; +import com.sunnyfarm.entity.Admin; +import com.sunnyfarm.exception.BusinessException; +import com.sunnyfarm.mapper.AdminMapper; +import com.sunnyfarm.service.AdminService; +import com.sunnyfarm.service.SystemLogService; +import com.sunnyfarm.util.PasswordUtil; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import java.time.LocalDateTime; +import java.util.List; +import java.util.Map; + +@Service +@Slf4j +public class AdminServiceImpl extends ServiceImpl implements AdminService { + + @Autowired + private SystemLogService systemLogService; + + @Override + public Admin login(AdminLoginRequest request) { + // 1. 根据用户名查询管理员 + Admin admin = this.getByUsername(request.getUsername()); + if (admin == null) { + throw new BusinessException("用户名或密码错误"); + } + + // 2. 验证密码 + if (!PasswordUtil.matches(request.getPassword(), admin.getPassword())) { + throw new BusinessException("用户名或密码错误"); + } + + // 3. 检查账号状态 + if (admin.getStatus() == 0) { + throw new BusinessException("账号已被禁用,请联系系统管理员"); + } + + // 4. 更新最后登录时间 + this.updateLastLoginTime(admin.getId()); + + log.info("管理员登录成功:{}", admin.getUsername()); + return admin; + } + + @Override + public Admin getByUsername(String username) { + return baseMapper.selectByUsername(username); + } + + @Override + public Admin getAdminWithRole(Long adminId) { + return baseMapper.selectAdminWithRole(adminId); + } + + @Override + public void updateLastLoginTime(Long adminId) { + Admin admin = new Admin(); + admin.setId(adminId); + admin.setLastLoginTime(LocalDateTime.now()); + this.updateById(admin); + } + + @Override + public List> getRecentActivities() { + try { + return systemLogService.getRecentActivities(10); + } catch (Exception e) { + log.error("获取最新动态失败", e); + throw new BusinessException("获取最新动态失败:" + e.getMessage()); + } + } + + @Override + public boolean updateProfile(Long adminId, AdminUpdateRequest request) { + try { + Admin admin = new Admin(); + admin.setId(adminId); + admin.setEmail(request.getEmail()); + admin.setPhone(request.getPhone()); + admin.setRealName(request.getRealName()); + + boolean success = this.updateById(admin); + if (success) { + log.info("管理员更新个人信息成功:{}", adminId); + } + return success; + } catch (Exception e) { + log.error("更新管理员个人信息失败", e); + throw new BusinessException("更新个人信息失败:" + e.getMessage()); + } + } + + @Override + public boolean changePassword(Long adminId, AdminPasswordRequest request) { + try { + // 1. 验证确认密码 + if (!request.getNewPassword().equals(request.getConfirmPassword())) { + throw new BusinessException("新密码与确认密码不一致"); + } + + // 2. 获取当前管理员信息 + Admin currentAdmin = this.getById(adminId); + if (currentAdmin == null) { + throw new BusinessException("管理员信息不存在"); + } + + // 3. 验证原密码 + if (!PasswordUtil.matches(request.getOldPassword(), currentAdmin.getPassword())) { + throw new BusinessException("原密码不正确"); + } + + // 4. 更新新密码 + Admin admin = new Admin(); + admin.setId(adminId); + admin.setPassword(PasswordUtil.encode(request.getNewPassword())); + + boolean success = this.updateById(admin); + if (success) { + log.info("管理员修改密码成功:{}", adminId); + } + return success; + } catch (BusinessException e) { + throw e; + } catch (Exception e) { + log.error("修改管理员密码失败", e); + throw new BusinessException("修改密码失败:" + e.getMessage()); + } + } +} diff --git a/backend/src/main/java/com/sunnyfarm/service/impl/AlipayServiceImpl.java b/backend/src/main/java/com/sunnyfarm/service/impl/AlipayServiceImpl.java new file mode 100644 index 0000000..bca36ca --- /dev/null +++ b/backend/src/main/java/com/sunnyfarm/service/impl/AlipayServiceImpl.java @@ -0,0 +1,281 @@ +package com.sunnyfarm.service.impl; + +import com.alipay.api.AlipayApiException; +import com.alipay.api.AlipayClient; +import com.alipay.api.domain.AlipayTradePagePayModel; +import com.alipay.api.domain.AlipayTradeQueryModel; +import com.alipay.api.domain.AlipayTradeRefundModel; +import com.alipay.api.internal.util.AlipaySignature; +import com.alipay.api.request.AlipayTradePagePayRequest; +import com.alipay.api.request.AlipayTradeQueryRequest; +import com.alipay.api.request.AlipayTradeRefundRequest; +import com.alipay.api.response.AlipayTradePagePayResponse; +import com.alipay.api.response.AlipayTradeQueryResponse; +import com.alipay.api.response.AlipayTradeRefundResponse; +import com.sunnyfarm.config.properties.AlipayProperties; +import com.sunnyfarm.entity.Order; +import com.sunnyfarm.entity.Payment; +import com.sunnyfarm.service.AlipayService; +import com.sunnyfarm.service.OrderService; +import com.sunnyfarm.service.PaymentService; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.util.StringUtils; + +import java.math.BigDecimal; +import java.util.HashMap; +import java.util.Map; + +@Slf4j +@Transactional +@Service +public class AlipayServiceImpl implements AlipayService { + + @Autowired + private AlipayClient alipayClient; + + @Autowired + private AlipayProperties alipayProperties; + + @Autowired + private PaymentService paymentService; + + @Autowired + private OrderService orderService; + + @Override + public String createPayForm(Long orderId, String subject, BigDecimal amount) { + try { + // 配置验证 + if (!StringUtils.hasText(alipayProperties.getAppId())) { + throw new RuntimeException("支付宝APPID未配置"); + } + if (!StringUtils.hasText(alipayProperties.getAppPrivateKey())) { + throw new RuntimeException("支付宝应用私钥未配置"); + } + + log.info("支付宝配置检查: APPID={}, 私钥长度={}", + alipayProperties.getAppId(), + alipayProperties.getAppPrivateKey().length()); + + // 获取订单信息 + Order order = orderService.getById(orderId); + if (order == null) { + throw new RuntimeException("订单不存在"); + } + + // 检查是否已存在未完成的支付记录 + Payment existingPayment = paymentService.getByOrderId(orderId); + if (existingPayment != null && existingPayment.getStatus() == 1) { + // 如果存在待支付记录,删除旧记录 + log.info("删除订单{}的旧支付记录: {}", orderId, existingPayment.getPaymentNo()); + paymentService.removeById(existingPayment.getId()); + } + + // 创建支付记录 + Payment payment = paymentService.createPayment(orderId, amount); + log.info("创建支付记录: 支付单号={}, 订单ID={}, 金额={}", + payment.getPaymentNo(), orderId, amount); + + // 创建支付请求 + AlipayTradePagePayRequest request = new AlipayTradePagePayRequest(); + request.setNotifyUrl(alipayProperties.getNotifyUrl()); + request.setReturnUrl(alipayProperties.getReturnUrl()); + + AlipayTradePagePayModel model = new AlipayTradePagePayModel(); + model.setOutTradeNo(payment.getPaymentNo()); // 使用支付单号 + model.setTotalAmount(amount.toString()); + model.setSubject(subject); + model.setProductCode("FAST_INSTANT_TRADE_PAY"); + model.setBody("农产品直销平台订单支付"); + + request.setBizModel(model); + + log.info("准备调用支付宝API创建支付表单..."); + AlipayTradePagePayResponse response = alipayClient.pageExecute(request); + + if (response.isSuccess()) { + log.info("支付宝支付表单创建成功"); + return response.getBody(); + } else { + log.error("创建支付表单失败: code={}, msg={}, subMsg={}", + response.getCode(), response.getMsg(), response.getSubMsg()); + throw new RuntimeException("创建支付失败: " + response.getSubMsg()); + } + + } catch (AlipayApiException e) { + log.error("支付宝API调用异常: {}", e.getMessage(), e); + throw new RuntimeException("支付服务异常: " + e.getMessage(), e); + } + } + + @Override + @Transactional(rollbackFor = Exception.class) + public synchronized boolean handlePayNotify(Map params) { + try { + log.info("收到支付宝回调参数: {}", params); + + // 验证签名 + if (!verifySign(params)) { + log.error("支付回调签名验证失败, params: {}", params); + return false; + } + log.info("支付宝回调签名验证成功"); + + String outTradeNo = params.get("out_trade_no"); + String tradeStatus = params.get("trade_status"); + String tradeNo = params.get("trade_no"); + String buyerPayAmount = params.get("buyer_pay_amount"); + String buyerLogonId = params.get("buyer_logon_id"); + + log.info("支付回调详情: 支付单号={}, 交易状态={}, 支付宝交易号={}, 实付金额={}, 买家账号={}", + outTradeNo, tradeStatus, tradeNo, buyerPayAmount, buyerLogonId); + + if ("TRADE_SUCCESS".equals(tradeStatus)) { + log.info("开始处理支付成功回调"); + + // 先检查是否已经处理过 + Payment existingPayment = paymentService.getByPaymentNo(outTradeNo); + if (existingPayment == null) { + log.error("支付记录不存在, 支付单号: {}", outTradeNo); + return false; + } + + if (existingPayment.getStatus() == 2) { + log.info("支付记录已经是成功状态,跳过处理"); + return true; // 已经处理过,返回成功 + } + + // 开始事务处理 + try { + // 更新支付记录 + boolean paymentUpdated = paymentService.updatePaymentSuccess( + outTradeNo, + tradeNo, + buyerPayAmount != null ? new BigDecimal(buyerPayAmount) : existingPayment.getAmount(), + buyerLogonId + ); + + log.info("支付记录更新结果: {}", paymentUpdated ? "成功" : "失败"); + + if (paymentUpdated) { + log.info("开始更新订单状态, 订单ID: {}", existingPayment.getOrderId()); + + // 更新订单状态 + boolean orderUpdated = orderService.payOrder(existingPayment.getOrderId(), tradeNo); + log.info("订单状态更新结果: {}", orderUpdated ? "成功" : "失败"); + + if (orderUpdated) { + log.info("支付回调处理完全成功! 订单ID: {}, 支付宝交易号: {}", existingPayment.getOrderId(), tradeNo); + return true; + } else { + log.error("订单状态更新失败, 订单ID: {}, 回滚事务", existingPayment.getOrderId()); + throw new RuntimeException("订单状态更新失败"); + } + } else { + log.error("支付记录更新失败, 支付单号: {}, 回滚事务", outTradeNo); + throw new RuntimeException("支付记录更新失败"); + } + } catch (Exception e) { + log.error("支付回调事务处理异常: {}, 将自动回滚", e.getMessage(), e); + throw e; // 重新抛出异常,让事务回滚 + } + } else { + log.info("非支付成功状态: {}", tradeStatus); + return true; // 其他状态也返回true,避免支付宝重复通知 + } + + } catch (Exception e) { + log.error("处理支付回调异常: {}", e.getMessage(), e); + return false; // 处理失败,支付宝会重新通知 + } + } + + @Override + public boolean verifySign(Map params) { + try { + return AlipaySignature.rsaCheckV1( + params, + alipayProperties.getAlipayPublicKey(), + alipayProperties.getCharset(), + alipayProperties.getSignType() + ); + } catch (AlipayApiException e) { + log.error("验证签名异常", e); + return false; + } + } + + @Override + public Map queryPayResult(String outTradeNo) { + Map result = new HashMap<>(); + + try { + AlipayTradeQueryRequest request = new AlipayTradeQueryRequest(); + AlipayTradeQueryModel model = new AlipayTradeQueryModel(); + model.setOutTradeNo(outTradeNo); + request.setBizModel(model); + + AlipayTradeQueryResponse response = alipayClient.execute(request); + + result.put("success", response.isSuccess()); + result.put("code", response.getCode()); + result.put("msg", response.getMsg()); + + if (response.isSuccess()) { + result.put("tradeStatus", response.getTradeStatus()); + result.put("tradeNo", response.getTradeNo()); + result.put("totalAmount", response.getTotalAmount()); + result.put("buyerLogonId", response.getBuyerLogonId()); + result.put("buyerPayAmount", response.getBuyerPayAmount()); + } + + } catch (AlipayApiException e) { + log.error("查询支付结果异常", e); + result.put("success", false); + result.put("msg", "查询异常: " + e.getMessage()); + } + + return result; + } + + @Override + public Map refund(String outTradeNo, BigDecimal refundAmount, String refundReason) { + Map result = new HashMap<>(); + + try { + AlipayTradeRefundRequest request = new AlipayTradeRefundRequest(); + AlipayTradeRefundModel model = new AlipayTradeRefundModel(); + model.setOutTradeNo(outTradeNo); + model.setRefundAmount(refundAmount.toString()); + model.setRefundReason(refundReason); + model.setOutRequestNo(outTradeNo + "_refund_" + System.currentTimeMillis()); + + request.setBizModel(model); + + AlipayTradeRefundResponse response = alipayClient.execute(request); + + result.put("success", response.isSuccess()); + result.put("code", response.getCode()); + result.put("msg", response.getMsg()); + + if (response.isSuccess()) { + result.put("tradeNo", response.getTradeNo()); + result.put("refundFee", response.getRefundFee()); + result.put("gmtRefundPay", response.getGmtRefundPay()); + + // 更新支付记录 + paymentService.refundPayment(outTradeNo, refundAmount, refundReason); + } + + } catch (AlipayApiException e) { + log.error("退款异常", e); + result.put("success", false); + result.put("msg", "退款异常: " + e.getMessage()); + } + + return result; + } +} \ No newline at end of file diff --git a/backend/src/main/java/com/sunnyfarm/service/impl/AnnouncementServiceImpl.java b/backend/src/main/java/com/sunnyfarm/service/impl/AnnouncementServiceImpl.java new file mode 100644 index 0000000..970f3ac --- /dev/null +++ b/backend/src/main/java/com/sunnyfarm/service/impl/AnnouncementServiceImpl.java @@ -0,0 +1,105 @@ +package com.sunnyfarm.service.impl; + +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.sunnyfarm.entity.Announcement; +import com.sunnyfarm.entity.SystemLog; +import com.sunnyfarm.mapper.AnnouncementMapper; +import com.sunnyfarm.service.AnnouncementService; +import com.sunnyfarm.service.SystemLogService; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import java.time.LocalDateTime; + +@Service +@Slf4j +public class AnnouncementServiceImpl extends ServiceImpl implements AnnouncementService { + + @Autowired + private SystemLogService systemLogService; + + @Override + public boolean createAnnouncement(Announcement announcement, Long creatorId, String creatorName) { + announcement.setCreatorId(creatorId); + announcement.setCreatorName(creatorName); + + if (announcement.getStatus() == Announcement.STATUS_PUBLISHED) { + announcement.setPublishTime(LocalDateTime.now()); + } + + boolean result = this.save(announcement); + + if (result) { + systemLogService.recordSuccessLog( + SystemLog.ANNOUNCEMENT_CREATE, + "创建公告: " + announcement.getTitle(), + creatorId, + creatorName, + SystemLog.OPERATOR_ADMIN + ); + } + + return result; + } + + @Override + public boolean updateAnnouncement(Announcement announcement, Long operatorId, String operatorName) { + Announcement original = this.getById(announcement.getId()); + if (original == null) { + return false; + } + + // 如果状态从草稿变为发布,设置发布时间 + if (original.getStatus() == Announcement.STATUS_DRAFT && + announcement.getStatus() == Announcement.STATUS_PUBLISHED) { + announcement.setPublishTime(LocalDateTime.now()); + } + + boolean result = this.updateById(announcement); + + if (result) { + systemLogService.recordSuccessLog( + SystemLog.ANNOUNCEMENT_UPDATE, + "更新公告: " + announcement.getTitle(), + operatorId, + operatorName, + SystemLog.OPERATOR_ADMIN + ); + } + + return result; + } + + @Override + public boolean deleteAnnouncement(Long id, Long operatorId, String operatorName) { + Announcement announcement = this.getById(id); + if (announcement == null) { + return false; + } + + boolean result = this.removeById(id); + + if (result) { + systemLogService.recordSuccessLog( + SystemLog.ANNOUNCEMENT_DELETE, + "删除公告: " + announcement.getTitle(), + operatorId, + operatorName, + SystemLog.OPERATOR_ADMIN + ); + } + + return result; + } + + @Override + public boolean publishAnnouncement(Long id, Long operatorId, String operatorName) { + Announcement announcement = new Announcement(); + announcement.setId(id); + announcement.setStatus(Announcement.STATUS_PUBLISHED); + announcement.setPublishTime(LocalDateTime.now()); + + return this.updateAnnouncement(announcement, operatorId, operatorName); + } +} diff --git a/backend/src/main/java/com/sunnyfarm/service/impl/CartServiceImpl.java b/backend/src/main/java/com/sunnyfarm/service/impl/CartServiceImpl.java new file mode 100644 index 0000000..a2d7241 --- /dev/null +++ b/backend/src/main/java/com/sunnyfarm/service/impl/CartServiceImpl.java @@ -0,0 +1,218 @@ +package com.sunnyfarm.service.impl; + +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.sunnyfarm.dto.CartItemDTO; +import com.sunnyfarm.entity.Cart; +import com.sunnyfarm.entity.Product; +import com.sunnyfarm.exception.BusinessException; +import com.sunnyfarm.mapper.CartMapper; +import com.sunnyfarm.service.CartService; +import com.sunnyfarm.service.ProductService; +import com.sunnyfarm.service.InventoryService; +import com.sunnyfarm.service.MerchantService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.List; + +@Service +@Transactional +public class CartServiceImpl extends ServiceImpl implements CartService { + + @Autowired + private ProductService productService; + + @Autowired + private InventoryService inventoryService; + + @Autowired + private MerchantService merchantService; + + @Override + public boolean addToCart(Long userId, Long productId, Integer quantity) { + // 检查商品是否存在和上架 + Product product = productService.getById(productId); + if (product == null || product.getStatus() != 1) { + throw new BusinessException("商品不存在或已下架"); + } + + // 检查库存 + Integer stockQuantity = inventoryService.getStockQuantity(productId); + if (stockQuantity == null || stockQuantity <= 0) { + throw new BusinessException("商品库存不足"); + } + + QueryWrapper wrapper = new QueryWrapper<>(); + wrapper.eq("user_id", userId); + wrapper.eq("product_id", productId); + + Cart existingCart = getOne(wrapper); + if (existingCart != null) { + int newQuantity = existingCart.getQuantity() + quantity; + if (newQuantity > stockQuantity) { + throw new BusinessException("库存不足,当前可用库存:" + stockQuantity); + } + existingCart.setQuantity(newQuantity); + existingCart.setUpdateTime(LocalDateTime.now()); + return updateById(existingCart); + } else { + if (quantity > stockQuantity) { + throw new BusinessException("库存不足,当前可用库存:" + stockQuantity); + } + Cart cart = new Cart(); + cart.setUserId(userId); + cart.setProductId(productId); + cart.setQuantity(quantity); + cart.setSelected(false); + cart.setCreateTime(LocalDateTime.now()); + cart.setUpdateTime(LocalDateTime.now()); + return save(cart); + } + } + + @Override + public boolean updateCartQuantity(Long userId, Long productId, Integer quantity) { + if (quantity <= 0) { + return removeFromCart(userId, productId); + } + + // 检查库存 + Integer stockQuantity = inventoryService.getStockQuantity(productId); + if (stockQuantity == null || quantity > stockQuantity) { + throw new BusinessException("库存不足,当前可用库存:" + (stockQuantity != null ? stockQuantity : 0)); + } + + QueryWrapper wrapper = new QueryWrapper<>(); + wrapper.eq("user_id", userId); + wrapper.eq("product_id", productId); + + Cart cart = getOne(wrapper); + if (cart != null) { + cart.setQuantity(quantity); + cart.setUpdateTime(LocalDateTime.now()); + return updateById(cart); + } + return false; + } + + @Override + public boolean removeFromCart(Long userId, Long productId) { + QueryWrapper wrapper = new QueryWrapper<>(); + wrapper.eq("user_id", userId); + wrapper.eq("product_id", productId); + return remove(wrapper); + } + + @Override + public List getUserCart(Long userId) { + QueryWrapper wrapper = new QueryWrapper<>(); + wrapper.eq("user_id", userId); + wrapper.orderByDesc("created_at"); + List carts = list(wrapper); + + // 填充商品信息 + for (Cart cart : carts) { + Product product = productService.getById(cart.getProductId()); + cart.setProduct(product); + } + + return carts; + } + + public List getUserCartItems(Long userId) { + // 使用自定义查询获取购物车详情 + return baseMapper.getUserCartItems(userId); + } + + @Override + public Long getUserCartCount(Long userId) { + QueryWrapper wrapper = new QueryWrapper<>(); + wrapper.eq("user_id", userId); + return count(wrapper); + } + + @Override + public Long getCartCount(Long userId) { + return getUserCartCount(userId); + } + + @Override + public boolean toggleCartSelection(Long userId, Long productId, Boolean selected) { + QueryWrapper wrapper = new QueryWrapper<>(); + wrapper.eq("user_id", userId); + wrapper.eq("product_id", productId); + + Cart cart = getOne(wrapper); + if (cart != null) { + cart.setSelected(selected); + cart.setUpdateTime(LocalDateTime.now()); + return updateById(cart); + } + return false; + } + + @Override + public boolean toggleCartSelect(Long userId, Long productId, Boolean selected) { + return toggleCartSelection(userId, productId, selected); + } + + @Override + public boolean toggleAllCartSelection(Long userId, Boolean selected) { + QueryWrapper wrapper = new QueryWrapper<>(); + wrapper.eq("user_id", userId); + + List carts = list(wrapper); + for (Cart cart : carts) { + cart.setSelected(selected); + cart.setUpdateTime(LocalDateTime.now()); + updateById(cart); + } + return true; + } + + @Override + public boolean clearCart(Long userId) { + QueryWrapper wrapper = new QueryWrapper<>(); + wrapper.eq("user_id", userId); + return remove(wrapper); + } + + @Override + public boolean batchRemoveFromCart(Long userId, List productIds) { + QueryWrapper wrapper = new QueryWrapper<>(); + wrapper.eq("user_id", userId); + wrapper.in("product_id", productIds); + return remove(wrapper); + } + + /** + * 获取选中的购物车项 + */ + public List getSelectedCartItems(Long userId, List cartIds) { + QueryWrapper wrapper = new QueryWrapper<>(); + wrapper.eq("user_id", userId); + wrapper.in("id", cartIds); + wrapper.eq("selected", true); + + List carts = list(wrapper); + + // 填充商品信息 + for (Cart cart : carts) { + Product product = productService.getById(cart.getProductId()); + cart.setProduct(product); + } + + return carts; + } + + /** + * 清空指定购物车项 + */ + public boolean removeCartItems(List cartIds) { + return removeByIds(cartIds); + } +} diff --git a/backend/src/main/java/com/sunnyfarm/service/impl/CategoryServiceImpl.java b/backend/src/main/java/com/sunnyfarm/service/impl/CategoryServiceImpl.java new file mode 100644 index 0000000..35737a8 --- /dev/null +++ b/backend/src/main/java/com/sunnyfarm/service/impl/CategoryServiceImpl.java @@ -0,0 +1,122 @@ +package com.sunnyfarm.service.impl; + +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.sunnyfarm.entity.Category; +import com.sunnyfarm.mapper.CategoryMapper; +import com.sunnyfarm.service.CategoryService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import java.time.LocalDateTime; +import java.util.List; +import java.util.stream.Collectors; + +@Service +public class CategoryServiceImpl extends ServiceImpl implements CategoryService { + + @Autowired + private CategoryMapper categoryMapper; + + @Override + public List getCategoryTree() { + List allCategories = categoryMapper.selectList(new QueryWrapper().orderByAsc("sort_order")); + + // 筛选出一级分类 + return allCategories.stream() + .filter(c -> c.getParentId() == 0) + .peek(c -> c.setChildren(findChildren(c, allCategories))) + .collect(Collectors.toList()); + } + + private List findChildren(Category parent, List allCategories) { + return allCategories.stream() + .filter(c -> parent.getId().equals(c.getParentId())) + .peek(c -> c.setChildren(findChildren(c, allCategories))) + .collect(Collectors.toList()); + } + + @Override + public List getChildCategories(Long parentId) { + QueryWrapper queryWrapper = new QueryWrapper<>(); + queryWrapper.eq("parent_id", parentId) + .eq("status", 1) + .orderByAsc("sort_order"); + return this.list(queryWrapper); + } + + @Override + public List getActiveCategories() { + QueryWrapper queryWrapper = new QueryWrapper<>(); + queryWrapper.eq("status", 1) + .orderByAsc("sort_order"); + return this.list(queryWrapper); + } + + @Override + public List getAllCategories() { + QueryWrapper queryWrapper = new QueryWrapper<>(); + queryWrapper.eq("status", 1) + .orderByAsc("sort_order"); + return this.list(queryWrapper); + } + + @Override + public List getMainCategories() { + QueryWrapper queryWrapper = new QueryWrapper<>(); + queryWrapper.eq("parent_id", 0) + .eq("status", 1) + .orderByAsc("sort_order"); + return this.list(queryWrapper); + } + + @Override + public Category createCategory(Category category) { + category.setCreatedAt(LocalDateTime.now()); + category.setUpdatedAt(LocalDateTime.now()); + save(category); + return category; + } + + @Override + public Category updateCategory(Long id, Category category) { + category.setId(id); + category.setUpdatedAt(LocalDateTime.now()); + updateById(category); + return category; + } + + @Override + public boolean deleteCategory(Long id) { + // 检查是否有子分类 + if (!getChildCategories(id).isEmpty()) { + throw new RuntimeException("该分类下有子分类,不能删除"); + } + + // TODO: 检查分类下是否有商品 + + return removeById(id); + } + + @Override + public boolean updateCategorySort(Long id, Integer sort) { + Category category = getById(id); + if (category == null) { + return false; + } + category.setSortOrder(sort); + category.setUpdatedAt(LocalDateTime.now()); + return updateById(category); + } + + @Override + public boolean toggleCategoryStatus(Long id) { + Category category = getById(id); + if (category == null) { + return false; + } + category.setStatus(category.getStatus() == 1 ? 0 : 1); + category.setUpdatedAt(LocalDateTime.now()); + return updateById(category); + } +} diff --git a/backend/src/main/java/com/sunnyfarm/service/impl/ChatServiceImpl.java b/backend/src/main/java/com/sunnyfarm/service/impl/ChatServiceImpl.java new file mode 100644 index 0000000..fb8f6df --- /dev/null +++ b/backend/src/main/java/com/sunnyfarm/service/impl/ChatServiceImpl.java @@ -0,0 +1,172 @@ +package com.sunnyfarm.service.impl; + +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import com.sunnyfarm.dto.SendMessageRequest; +import com.sunnyfarm.entity.ChatMessage; +import com.sunnyfarm.entity.ChatSession; +import com.sunnyfarm.exception.BusinessException; +import com.sunnyfarm.mapper.ChatMessageMapper; +import com.sunnyfarm.mapper.ChatSessionMapper; +import com.sunnyfarm.service.ChatService; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.time.LocalDateTime; +import java.util.List; + +@Slf4j +@Service +public class ChatServiceImpl implements ChatService { + + @Autowired + private ChatSessionMapper chatSessionMapper; + + @Autowired + private ChatMessageMapper chatMessageMapper; + + @Override + public List getUserSessions(Long userId) { + return chatSessionMapper.getUserSessions(userId); + } + + @Override + public List getMerchantSessions(Long merchantId) { + return chatSessionMapper.getMerchantSessions(merchantId); + } + + @Override + @Transactional + public ChatSession getOrCreateSession(Long userId, Long merchantId) { + // 先尝试查找已有会话 + ChatSession session = chatSessionMapper.getOrCreateSession(userId, merchantId); + + if (session == null) { + // 创建新会话 + session = new ChatSession(); + session.setUserId(userId); + session.setMerchantId(merchantId); + session.setStatus(1); + session.setLastMessageTime(LocalDateTime.now()); + + chatSessionMapper.insert(session); + + // 重新查询获取完整信息 + session = chatSessionMapper.getOrCreateSession(userId, merchantId); + } + + return session; + } + + @Override + public List getSessionMessages(Long sessionId, Integer page, Integer size) { + int offset = (page - 1) * size; + return chatMessageMapper.getSessionMessages(sessionId, offset, size); + } + + @Override + @Transactional + public ChatMessage sendUserMessage(Long userId, SendMessageRequest request) { + try { + // 获取或创建会话 + ChatSession session = getOrCreateSession(userId, request.getMerchantId()); + + // 创建消息 + ChatMessage message = new ChatMessage(); + message.setSessionId(session.getId()); + message.setSenderId(userId); + message.setSenderType(1); // 用户 + message.setMessageType(request.getMessageType()); + message.setContent(request.getContent()); + message.setIsRead(0); + message.setCreatedAt(LocalDateTime.now()); + + chatMessageMapper.insert(message); + + // 更新会话最后消息信息 + session.setLastMessage(request.getContent()); + session.setLastMessageTime(LocalDateTime.now()); + chatSessionMapper.updateById(session); + + return message; + } catch (Exception e) { + log.error("发送用户消息失败", e); + throw new BusinessException("发送消息失败"); + } + } + + @Override + @Transactional + public ChatMessage sendMerchantMessage(Long merchantId, Long sessionId, String content, Integer messageType) { + try { + // 验证会话是否属于该商家 + ChatSession session = chatSessionMapper.selectById(sessionId); + if (session == null || !session.getMerchantId().equals(merchantId)) { + throw new BusinessException("会话不存在或无权限"); + } + + // 创建消息 + ChatMessage message = new ChatMessage(); + message.setSessionId(sessionId); + message.setSenderId(merchantId); + message.setSenderType(2); // 商家 + message.setMessageType(messageType); + message.setContent(content); + message.setIsRead(0); + message.setCreatedAt(LocalDateTime.now()); + + chatMessageMapper.insert(message); + + // 更新会话最后消息信息 + session.setLastMessage(content); + session.setLastMessageTime(LocalDateTime.now()); + chatSessionMapper.updateById(session); + + return message; + } catch (Exception e) { + log.error("发送商家消息失败", e); + throw new BusinessException("发送消息失败"); + } + } + + @Override + public boolean markMessagesAsRead(Long sessionId, Long readerId) { + try { + int result = chatMessageMapper.markMessagesAsRead(sessionId, readerId); + return result > 0; + } catch (Exception e) { + log.error("标记消息已读失败", e); + return false; + } + } + + @Override + public boolean markUserMessagesAsRead(Long sessionId, Long userId) { + try { + // 用户标记商家发送的消息为已读(senderType = 2) + int result = chatMessageMapper.markMessagesByTypeAsRead(sessionId, 2); + return true; // 总是返回成功,即使没有消息需要标记 + } catch (Exception e) { + log.error("用户标记消息已读失败", e); + return false; + } + } + + @Override + public boolean markMerchantMessagesAsRead(Long sessionId, Long merchantId) { + try { + // 商家标记用户发送的消息为已读(senderType = 1) + int result = chatMessageMapper.markMessagesByTypeAsRead(sessionId, 1); + return true; // 总是返回成功,即使没有消息需要标记 + } catch (Exception e) { + log.error("商家标记消息已读失败", e); + return false; + } + } + + @Override + public Integer getUnreadCount(Long sessionId, Long receiverId) { + return chatMessageMapper.getUnreadCount(sessionId, receiverId); + } +} diff --git a/backend/src/main/java/com/sunnyfarm/service/impl/CommentServiceImpl.java b/backend/src/main/java/com/sunnyfarm/service/impl/CommentServiceImpl.java new file mode 100644 index 0000000..040e322 --- /dev/null +++ b/backend/src/main/java/com/sunnyfarm/service/impl/CommentServiceImpl.java @@ -0,0 +1,133 @@ +package com.sunnyfarm.service.impl; + +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.sunnyfarm.entity.Comment; +import com.sunnyfarm.mapper.CommentMapper; +import com.sunnyfarm.service.CommentService; +import org.apache.commons.lang3.StringUtils; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.math.BigDecimal; +import java.math.RoundingMode; +import java.time.LocalDateTime; +import java.util.List; + +@Service +@Transactional +public class CommentServiceImpl extends ServiceImpl implements CommentService { + + @Override + public Comment createComment(Comment comment) { + comment.setStatus(1); // 默认状态为显示 + save(comment); + return comment; + } + + @Override + public IPage getProductComments(Page page, Long productId) { + QueryWrapper wrapper = new QueryWrapper<>(); + wrapper.eq("product_id", productId); + wrapper.eq("status", 1); + wrapper.orderByDesc("created_at"); + + return page(page, wrapper); + } + @Override + public IPage getUserComments(Page page, Long userId) { + QueryWrapper wrapper = new QueryWrapper<>(); + wrapper.eq("user_id", userId); + wrapper.orderByDesc("created_at"); + + return page(page, wrapper); + } + + @Override + public BigDecimal getProductAverageRating(Long productId) { + QueryWrapper wrapper = new QueryWrapper<>(); + wrapper.eq("product_id", productId); + wrapper.eq("status", 1); + wrapper.isNotNull("rating"); + + List comments = list(wrapper); + if (comments.isEmpty()) { + return BigDecimal.ZERO; + } + + BigDecimal totalRating = BigDecimal.ZERO; + for (Comment comment : comments) { + totalRating = totalRating.add(comment.getRating()); + } + + return totalRating.divide(new BigDecimal(comments.size()), 1, RoundingMode.HALF_UP); + } + + @Override + public Long getProductCommentCount(Long productId) { + QueryWrapper wrapper = new QueryWrapper<>(); + wrapper.eq("product_id", productId); + wrapper.eq("status", 1); + + return count(wrapper); + } + + @Override + public boolean hasUserCommented(Long userId, Long productId, Long orderId) { + QueryWrapper wrapper = new QueryWrapper<>(); + wrapper.eq("user_id", userId); + wrapper.eq("product_id", productId); + if (orderId != null) { + wrapper.eq("order_id", orderId); + } + + return count(wrapper) > 0; + } + + @Override + public boolean replyComment(Long commentId, String reply) { + Comment comment = getById(commentId); + if (comment != null) { + comment.setReply(reply); + comment.setReplyTime(LocalDateTime.now()); + // 自动设置更新时间 + return updateById(comment); + } + return false; + } + + @Override + public boolean auditComment(Long commentId, Integer status) { + Comment comment = getById(commentId); + if (comment != null) { + comment.setStatus(status); + // 自动设置更新时间 + return updateById(comment); + } + return false; + } + + @Override + public boolean deleteComment(Long commentId) { + return removeById(commentId); + } + + @Override + public IPage getAllComments(Page page, String keyword, Integer status) { + QueryWrapper wrapper = new QueryWrapper<>(); + + if (StringUtils.isNotBlank(keyword)) { + wrapper.like("content", keyword); + } + + if (status != null) { + wrapper.eq("status", status); + } + + wrapper.orderByDesc("created_at"); + + return page(page, wrapper); + } +} diff --git a/backend/src/main/java/com/sunnyfarm/service/impl/FavoriteServiceImpl.java b/backend/src/main/java/com/sunnyfarm/service/impl/FavoriteServiceImpl.java new file mode 100644 index 0000000..95c902c --- /dev/null +++ b/backend/src/main/java/com/sunnyfarm/service/impl/FavoriteServiceImpl.java @@ -0,0 +1,137 @@ +package com.sunnyfarm.service.impl; + +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.sunnyfarm.entity.Favorite; +import com.sunnyfarm.mapper.FavoriteMapper; +import com.sunnyfarm.service.ProductService; +import com.sunnyfarm.service.ProductImageService; +import com.sunnyfarm.entity.Product; +import org.springframework.beans.factory.annotation.Autowired; +import com.sunnyfarm.service.FavoriteService; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.time.LocalDateTime; +import java.util.List; + +@Service +@Transactional +public class FavoriteServiceImpl extends ServiceImpl implements FavoriteService { + @Autowired + private ProductService productService; + + @Autowired + private ProductImageService productImageService; + + @Override + public boolean addFavorite(Long userId, Long productId) { + QueryWrapper wrapper = new QueryWrapper<>(); + wrapper.eq("user_id", userId); + wrapper.eq("product_id", productId); + + Favorite existing = getOne(wrapper); + if (existing != null) { + return true; + } + + Favorite favorite = new Favorite(); + favorite.setUserId(userId); + favorite.setProductId(productId); + favorite.setCreateTime(LocalDateTime.now()); + favorite.setUpdateTime(LocalDateTime.now()); + return save(favorite); + } + + @Override + public boolean removeFavorite(Long userId, Long productId) { + QueryWrapper wrapper = new QueryWrapper<>(); + wrapper.eq("user_id", userId); + wrapper.eq("product_id", productId); + return remove(wrapper); + } + + @Override + public boolean isFavorited(Long userId, Long productId) { + QueryWrapper wrapper = new QueryWrapper<>(); + wrapper.eq("user_id", userId); + wrapper.eq("product_id", productId); + return count(wrapper) > 0; + } + + @Override + public IPage getUserFavorites(Page page, Long userId) { + try { + // 尝试使用自定义查询方法 + IPage result = baseMapper.getUserFavoritesWithProduct(page, userId); + + // 为每个收藏项填充商品图片信息 + for (Favorite favorite : result.getRecords()) { + if (favorite.getProduct() != null) { + // 获取商品图片列表 + List imageList = productImageService.getImageUrls(favorite.getProduct().getId()); + favorite.getProduct().setImageList(imageList); + + // 获取库存信息 + try { + Product productDetail = productService.getById(favorite.getProduct().getId()); + if (productDetail != null) { + favorite.getProduct().setStock(productDetail.getStock()); + } + } catch (Exception e) { + // 忽略库存获取错误 + favorite.getProduct().setStock(0); + } + } + } + + return result; + } catch (Exception e) { + // 如果自定义查询失败,使用基础查询 + QueryWrapper wrapper = new QueryWrapper<>(); + wrapper.eq("user_id", userId); + wrapper.orderByDesc("created_at"); + IPage result = page(page, wrapper); + + // 手动填充商品信息 + for (Favorite favorite : result.getRecords()) { + try { + Product product = productService.getById(favorite.getProductId()); + if (product != null) { + List imageList = productImageService.getImageUrls(product.getId()); + product.setImageList(imageList); + favorite.setProduct(product); + } + } catch (Exception ex) { + // 忽略单个商品获取错误 + } + } + + return result; + } + } + + @Override + public Long getUserFavoriteCount(Long userId) { + QueryWrapper wrapper = new QueryWrapper<>(); + wrapper.eq("user_id", userId); + return count(wrapper); + } + + @Override + public Long getProductFavoriteCount(Long productId) { + QueryWrapper wrapper = new QueryWrapper<>(); + wrapper.eq("product_id", productId); + return count(wrapper); + } + + @Override + public boolean batchRemoveFavorite(Long userId, List productIds) { + QueryWrapper wrapper = new QueryWrapper<>(); + wrapper.eq("user_id", userId); + wrapper.in("product_id", productIds); + return remove(wrapper); + } +} diff --git a/backend/src/main/java/com/sunnyfarm/service/impl/InventoryServiceImpl.java b/backend/src/main/java/com/sunnyfarm/service/impl/InventoryServiceImpl.java new file mode 100644 index 0000000..5e304cf --- /dev/null +++ b/backend/src/main/java/com/sunnyfarm/service/impl/InventoryServiceImpl.java @@ -0,0 +1,177 @@ +package com.sunnyfarm.service.impl; + +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.sunnyfarm.dto.InventoryWithProduct; +import com.sunnyfarm.entity.Inventory; +import com.sunnyfarm.exception.BusinessException; +import com.sunnyfarm.mapper.InventoryMapper; +import com.sunnyfarm.service.InventoryService; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.time.LocalDateTime; +import java.util.List; + +@Slf4j +@Service +@Transactional +public class InventoryServiceImpl extends ServiceImpl implements InventoryService { + + @Override + public Inventory getByProductId(Long productId) { + QueryWrapper wrapper = new QueryWrapper<>(); + wrapper.eq("product_id", productId); + return getOne(wrapper); + } + + @Override + public Integer getStockQuantity(Long productId) { + Inventory inventory = getByProductId(productId); + return inventory != null ? inventory.getStockQuantity() : 0; + } + + @Override + public boolean updateStockQuantity(Long productId, Integer quantity) { + Inventory inventory = getByProductId(productId); + if (inventory == null) { + // 创建新的库存记录 + inventory = new Inventory(); + inventory.setProductId(productId); + inventory.setStockQuantity(quantity); + inventory.setWarningQuantity(10); // 默认预警数量 + inventory.setUpdatedAt(LocalDateTime.now()); + return save(inventory); + } else { + inventory.setStockQuantity(quantity); + inventory.setUpdatedAt(LocalDateTime.now()); + return updateById(inventory); + } + } + + @Override + public boolean createOrUpdateInventory(Long productId, Integer stockQuantity, int warningQuantity) { + Inventory inventory = getByProductId(productId); + if (inventory == null) { + // 创建新的库存记录 + inventory = new Inventory(); + inventory.setProductId(productId); + inventory.setStockQuantity(stockQuantity); + inventory.setWarningQuantity(warningQuantity); + inventory.setUpdatedAt(LocalDateTime.now()); + return save(inventory); + } else { + inventory.setStockQuantity(stockQuantity); + inventory.setWarningQuantity(warningQuantity); + inventory.setUpdatedAt(LocalDateTime.now()); + return updateById(inventory); + } + } + + @Override + public boolean batchUpdateStock(List productIds, List quantities) { + if (productIds.size() != quantities.size()) { + throw new BusinessException("商品ID和库存数量不匹配"); + } + + for (int i = 0; i < productIds.size(); i++) { + updateStockQuantity(productIds.get(i), quantities.get(i)); + } + return true; + } + + @Override + public boolean setWarningQuantity(Long productId, Integer warningQuantity) { + Inventory inventory = getByProductId(productId); + if (inventory == null) { + // 创建新的库存记录 + inventory = new Inventory(); + inventory.setProductId(productId); + inventory.setStockQuantity(0); + inventory.setWarningQuantity(warningQuantity); + inventory.setUpdatedAt(LocalDateTime.now()); + return save(inventory); + } else { + inventory.setWarningQuantity(warningQuantity); + inventory.setUpdatedAt(LocalDateTime.now()); + return updateById(inventory); + } + } + + @Override + public List getWarningProducts(Long merchantId) { + // 这里需要join查询,简化处理 + QueryWrapper wrapper = new QueryWrapper<>(); + wrapper.le("stock_quantity", "warning_quantity"); + return list(wrapper); + } + + @Override + public IPage getMerchantInventoryPage(Page page, Long merchantId, String keyword) { + return baseMapper.getMerchantInventoryPage(page, merchantId, keyword); + } + + @Override + public InventoryStatistics getMerchantInventoryStats(Long merchantId) { + List allInventory = baseMapper.getMerchantInventoryList(merchantId); + + InventoryStatistics stats = new InventoryStatistics(); + stats.setTotalProducts(allInventory.size()); + stats.setInStockProducts((int) allInventory.stream().filter(i -> i.getStockQuantity() > 0).count()); + stats.setWarningProducts((int) allInventory.stream().filter(i -> + i.getStockQuantity() > 0 && i.getStockQuantity() <= i.getWarningQuantity()).count()); + stats.setOutStockProducts((int) allInventory.stream().filter(i -> i.getStockQuantity() == 0).count()); + + return stats; + } + + @Override + public boolean decreaseStock(Long productId, Integer quantity) { + try { + Inventory inventory = getByProductId(productId); + if (inventory == null) { + throw new BusinessException("商品库存不存在"); + } + + if (inventory.getStockQuantity() < quantity) { + throw new BusinessException("库存不足"); + } + + inventory.setStockQuantity(inventory.getStockQuantity() - quantity); + inventory.setUpdatedAt(LocalDateTime.now()); + + return updateById(inventory); + } catch (Exception e) { + log.error("扣减库存失败,商品ID:{},数量:{}", productId, quantity, e); + return false; + } + } + + @Override + public boolean increaseStock(Long productId, Integer quantity) { + try { + Inventory inventory = getByProductId(productId); + if (inventory == null) { + // 如果库存记录不存在,创建新的 + inventory = new Inventory(); + inventory.setProductId(productId); + inventory.setStockQuantity(quantity); + inventory.setWarningQuantity(10); + inventory.setUpdatedAt(LocalDateTime.now()); + + return save(inventory); + } else { + inventory.setStockQuantity(inventory.getStockQuantity() + quantity); + inventory.setUpdatedAt(LocalDateTime.now()); + + return updateById(inventory); + } + } catch (Exception e) { + log.error("增加库存失败,商品ID:{},数量:{}", productId, quantity, e); + return false; + } + } +} diff --git a/backend/src/main/java/com/sunnyfarm/service/impl/MerchantServiceImpl.java b/backend/src/main/java/com/sunnyfarm/service/impl/MerchantServiceImpl.java new file mode 100644 index 0000000..c7de758 --- /dev/null +++ b/backend/src/main/java/com/sunnyfarm/service/impl/MerchantServiceImpl.java @@ -0,0 +1,227 @@ +package com.sunnyfarm.service.impl; + +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.sunnyfarm.dto.AdminMerchantPageDTO; +import com.sunnyfarm.dto.MerchantDashboardDTO; +import com.sunnyfarm.dto.MerchantRegisterRequest; +import com.sunnyfarm.entity.Merchant; +import com.sunnyfarm.entity.User; +import com.sunnyfarm.exception.BusinessException; +import com.sunnyfarm.mapper.MerchantMapper; +import com.sunnyfarm.service.MerchantService; +import com.sunnyfarm.service.UserService; +import com.sunnyfarm.util.PasswordUtil; +import com.sunnyfarm.util.UserContext; +import com.sunnyfarm.mapper.UserMapper; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import lombok.extern.slf4j.Slf4j; + +import java.math.BigDecimal; +import java.util.ArrayList; + +@Slf4j +@Service +public class MerchantServiceImpl extends ServiceImpl implements MerchantService { + + @Autowired + private UserService userService; + + @Autowired + private UserMapper userMapper; + + @Override + public boolean register(MerchantRegisterRequest request, Long userId) { + // 检查用户是否已经是商家 + QueryWrapper wrapper = new QueryWrapper<>(); + wrapper.eq("user_id", userId); + Merchant existMerchant = getOne(wrapper); + if (existMerchant != null) { + throw new BusinessException("该用户已经是商家"); + } + + // 创建商家记录 + Merchant merchant = new Merchant(); + merchant.setUserId(userId); + merchant.setShopName(request.getShopName()); + merchant.setLegalPerson(request.getLegalPerson()); + merchant.setContactPhone(request.getContactPhone()); + merchant.setBusinessScope(request.getBusinessScope()); + merchant.setAddress(request.getAddress()); + merchant.setStatus(0); // 待审核 + + return save(merchant); + } + + @Override + public boolean register(MerchantRegisterRequest request) { + Long userId = UserContext.getCurrentUserId(); + if (userId == null) { + throw new BusinessException("用户未登录"); + } + return register(request, userId); + } + + @Override + public Merchant login(String username, String password) { + // 1. 先通过用户服务验证用户账号和密码 + User user = userService.findByUsernameOrPhone(username); + if (user == null) { + throw new BusinessException("用户不存在"); + } + + // 2. 验证密码 + if (!PasswordUtil.matches(password, user.getPassword())) { + throw new BusinessException("密码错误"); + } + + // 3. 检查用户状态 + if (user.getStatus() != 1) { + throw new BusinessException("用户账号已被禁用"); + } + + // 4. 获取商家信息 + QueryWrapper wrapper = new QueryWrapper<>(); + wrapper.eq("user_id", user.getId()); + Merchant merchant = getOne(wrapper); + + if (merchant == null) { + throw new BusinessException("该用户不是商家,请先申请商家认证"); + } + + // 5. 检查商家状态 + if (merchant.getStatus() == 0) { + throw new BusinessException("商家认证审核中,请耐心等待"); + } else if (merchant.getStatus() == 2) { + throw new BusinessException("商家认证被拒绝,请重新申请"); + } + + // 6. 设置用户信息到商家对象中 + merchant.setUser(user); + + return merchant; + } + + @Override + public Merchant getByUserId(Long userId) { + QueryWrapper wrapper = new QueryWrapper<>(); + wrapper.eq("user_id", userId); + return getOne(wrapper); + } + + @Override + public Long getMerchantIdByUserId(Long userId) { + Merchant merchant = getByUserId(userId); + return merchant != null ? merchant.getId() : null; + } + + @Override + public boolean auditMerchant(Long merchantId, Integer status, String remark, Long operatorId, String operatorName) { + Merchant merchant = getById(merchantId); + if (merchant == null) { + throw new BusinessException("商家不存在"); + } + + merchant.setStatus(status); + + return updateById(merchant); + } + + @Override + public boolean verifyMerchant(Long merchantId, Integer status, String remark) { + return auditMerchant(merchantId, status, remark, null, null); + } + + @Override + public boolean updateMerchantInfo(Merchant merchant) { + return updateById(merchant); + } + + @Override + public MerchantDashboardDTO getDashboardData(Long merchantId) { + MerchantDashboardDTO dto = new MerchantDashboardDTO(); + + try { + // 获取基本统计数据 + dto.setTotalOrders(baseMapper.getTotalOrdersByMerchant(merchantId).intValue()); + dto.setTodayOrders(baseMapper.getTodayOrdersByMerchant(merchantId).intValue()); + dto.setTotalRevenue(baseMapper.getTotalRevenueByMerchant(merchantId)); + dto.setTodayRevenue(baseMapper.getTodayRevenueByMerchant(merchantId)); + dto.setTotalProducts(baseMapper.getTotalProductsByMerchant(merchantId)); + dto.setActiveProducts(baseMapper.getActiveProductsByMerchant(merchantId).intValue()); + dto.setLowStockProducts(0); // 暂时设为0,后续可以实现库存预警 + dto.setTotalCustomers(baseMapper.getTotalCustomersByMerchant(merchantId)); + + // 获取详细数据 + dto.setRecentOrders(baseMapper.getRecentOrdersByMerchant(merchantId)); + dto.setSalesTrend(baseMapper.getSalesTrendByMerchant(merchantId)); + dto.setTopProducts(baseMapper.getTopProductsByMerchant(merchantId)); + + } catch (Exception e) { + log.error("获取商家Dashboard数据失败", e); + // 发生异常时返回默认值 + dto.setTotalOrders(0); + dto.setTodayOrders(0); + dto.setTotalRevenue(BigDecimal.ZERO); + dto.setTodayRevenue(BigDecimal.ZERO); + dto.setTotalProducts(0L); + dto.setActiveProducts(0); + dto.setLowStockProducts(0); + dto.setTotalCustomers(0L); + dto.setRecentOrders(new ArrayList<>()); + dto.setSalesTrend(new ArrayList<>()); + dto.setTopProducts(new ArrayList<>()); + } + + return dto; + } + + @Override + public IPage getAdminMerchantPage(Page page, String keyword, Integer status) { + // 简化实现,直接返回空页面 + return page; + } + + @Override + public IPage getMerchantPage(Page page, String keyword, Integer status) { + QueryWrapper wrapper = new QueryWrapper<>(); + if (keyword != null && !keyword.trim().isEmpty()) { + wrapper.like("shop_name", keyword).or().like("legal_person", keyword); + } + if (status != null) { + wrapper.eq("status", status); + } + wrapper.orderByDesc("created_at"); + return page(page, wrapper); + } + + @Override + public boolean changePassword(Long userId, String oldPassword, String newPassword) { + try { + // 获取用户信息 + User user = userMapper.selectById(userId); + if (user == null) { + return false; + } + + // 验证原密码 + if (!PasswordUtil.matches(oldPassword, user.getPassword())) { + return false; + } + + // 更新新密码 + String encodedNewPassword = PasswordUtil.encode(newPassword); + user.setPassword(encodedNewPassword); + + int result = userMapper.updateById(user); + return result > 0; + } catch (Exception e) { + log.error("修改密码失败", e); + return false; + } + } +} diff --git a/backend/src/main/java/com/sunnyfarm/service/impl/OrderDetailServiceImpl.java b/backend/src/main/java/com/sunnyfarm/service/impl/OrderDetailServiceImpl.java new file mode 100644 index 0000000..e6d25b5 --- /dev/null +++ b/backend/src/main/java/com/sunnyfarm/service/impl/OrderDetailServiceImpl.java @@ -0,0 +1,25 @@ +package com.sunnyfarm.service.impl; + +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.sunnyfarm.entity.OrderDetail; +import com.sunnyfarm.mapper.OrderDetailMapper; +import com.sunnyfarm.service.OrderDetailService; +import org.springframework.stereotype.Service; + +import java.util.List; + +@Service +public class OrderDetailServiceImpl extends ServiceImpl implements OrderDetailService { + + @Override + public List getByOrderId(Long orderId) { + // 使用自定义查询获取包含merchantId的订单详情 + return baseMapper.getOrderDetailsByOrderId(orderId); + } + + @Override + public boolean batchSave(List orderDetails) { + return saveBatch(orderDetails); + } +} diff --git a/backend/src/main/java/com/sunnyfarm/service/impl/OrderServiceImpl.java b/backend/src/main/java/com/sunnyfarm/service/impl/OrderServiceImpl.java new file mode 100644 index 0000000..ae3713e --- /dev/null +++ b/backend/src/main/java/com/sunnyfarm/service/impl/OrderServiceImpl.java @@ -0,0 +1,750 @@ +package com.sunnyfarm.service.impl; + +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.sunnyfarm.dto.OrderCreateRequest; +import com.sunnyfarm.dto.OrderDetailDTO; +import com.sunnyfarm.entity.*; +import com.sunnyfarm.exception.BusinessException; +import com.sunnyfarm.mapper.OrderMapper; +import com.sunnyfarm.service.*; +import com.sunnyfarm.util.OrderNoUtil; +import com.sunnyfarm.util.UserContext; +import org.springframework.beans.BeanUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import lombok.extern.slf4j.Slf4j; + +import com.sunnyfarm.service.ProductImageService; +import org.springframework.transaction.annotation.Transactional; + +import java.math.BigDecimal; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; +import java.util.*; +import java.util.ArrayList; +import java.util.stream.Collectors; + +@Service +@Transactional +public class OrderServiceImpl extends ServiceImpl implements OrderService { + + @Autowired + private CartService cartService; + + @Autowired + private AddressService addressService; + + @Autowired + private ProductService productService; + + @Autowired + private InventoryService inventoryService; + + @Autowired + private OrderDetailService orderDetailService; + + @Autowired + private SystemLogService systemLogService; + + @Autowired(required = false) + private ProductImageService productImageService; + + @Override + public Order createOrder(Long userId, OrderCreateRequest request) { + // 验证请求参数 + if (!request.isValid()) { + throw new BusinessException("请选择商品或购物车项"); + } + + if (request.getItems() != null && !request.getItems().isEmpty()) { + // 立即购买模式 + return createOrderFromProducts(userId, request); + } else if (request.getCartIds() != null && !request.getCartIds().isEmpty()) { + // 购物车指定商品模式 + return createOrderFromCart(userId, request.getCartIds(), request.getAddressId(), request.getRemark()); + } else { + throw new BusinessException("无效的订单创建请求"); + } + } + + @Override + public Order createOrder(Order order, List cartIds) { + // 这个方法保持兼容,但需要地址ID + return createOrderFromCart(order.getUserId(), cartIds, null, order.getRemark()); + } + + @Override + public Order createOrder(Long userId, Long addressId, String remark) { + // 获取用户选中的购物车项 + QueryWrapper wrapper = new QueryWrapper<>(); + wrapper.eq("user_id", userId); + wrapper.eq("selected", true); + + List selectedCarts = cartService.list(wrapper); + if (selectedCarts.isEmpty()) { + throw new BusinessException("请先选择要购买的商品"); + } + + List cartIds = selectedCarts.stream() + .map(Cart::getId) + .collect(Collectors.toList()); + + return createOrderFromCart(userId, cartIds, addressId, remark); + } + + @Override + public Order createOrderFromProducts(Long userId, OrderCreateRequest request) { + // 获取收货地址 + Address address = addressService.getById(request.getAddressId()); + if (address == null || !address.getUserId().equals(userId)) { + throw new BusinessException("收货地址不存在"); + } + + // 验证商品并计算金额 + BigDecimal totalAmount = BigDecimal.ZERO; + Map> merchantGroups = new HashMap<>(); + + for (OrderCreateRequest.OrderItemRequest item : request.getItems()) { + Product product = productService.getById(item.getProductId()); + if (product == null || product.getStatus() != 1) { + throw new BusinessException("商品不存在或已下架"); + } + + // 验证库存 + Integer stockQuantity = inventoryService.getStockQuantity(product.getId()); + if (stockQuantity == null || stockQuantity < item.getQuantity()) { + throw new BusinessException("商品 " + product.getName() + " 库存不足"); + } + + // 计算金额 + BigDecimal itemTotal = product.getPrice().multiply(new BigDecimal(item.getQuantity())); + totalAmount = totalAmount.add(itemTotal); + + // 按商家分组 + Long merchantId = product.getMerchantId(); + merchantGroups.computeIfAbsent(merchantId, k -> new ArrayList<>()).add(item); + } + + List orders = new ArrayList<>(); + + // 为每个商家创建订单 + for (Map.Entry> entry : merchantGroups.entrySet()) { + Long merchantId = entry.getKey(); + List merchantItems = entry.getValue(); + + Order order = createSingleOrderFromItems(userId, merchantId, merchantItems, address, request.getRemark()); + orders.add(order); + } + + // 返回第一个订单(如果只有一个商家)或所有订单中的第一个 + return orders.get(0); + } + + @Override + public Order createOrderFromCart(Long userId, List cartIds, String remark) { + // 兼容旧方法,使用默认地址 + return createOrderFromCart(userId, cartIds, null, remark); + } + + // 新的方法,支持指定地址ID + public Order createOrderFromCart(Long userId, List cartIds, Long addressId, String remark) { + // 获取购物车项 + List cartItems = cartService.getSelectedCartItems(userId, cartIds); + if (cartItems.isEmpty()) { + throw new BusinessException("购物车为空或商品已失效"); + } + + // 获取收货地址 + Address selectedAddress = null; + if (addressId != null) { + // 使用指定的地址 + selectedAddress = addressService.getById(addressId); + if (selectedAddress == null || !selectedAddress.getUserId().equals(userId)) { + throw new BusinessException("收货地址不存在或不属于当前用户"); + } + } else { + // 使用默认地址(兼容旧逻辑) + selectedAddress = addressService.getUserDefaultAddress(userId); + if (selectedAddress == null) { + throw new BusinessException("请先设置收货地址"); + } + } + + // 按商家分组创建订单 + Map> merchantGroups = cartItems.stream() + .collect(Collectors.groupingBy(cart -> cart.getProduct().getMerchantId())); + + List orders = new ArrayList<>(); + + for (Map.Entry> entry : merchantGroups.entrySet()) { + Long merchantId = entry.getKey(); + List merchantCarts = entry.getValue(); + + Order order = createSingleOrderFromCarts(userId, merchantId, merchantCarts, selectedAddress, remark); + orders.add(order); + } + + // 清除已下单的购物车项 + cartService.removeCartItems(cartIds); + + // 如果只有一个订单,直接返回;多个订单返回第一个 + return orders.get(0); + } + + private Order createSingleOrderFromItems(Long userId, Long merchantId, List items, + Address address, String remark) { + + // 计算订单总额 + BigDecimal totalAmount = BigDecimal.ZERO; + + for (OrderCreateRequest.OrderItemRequest item : items) { + Product product = productService.getById(item.getProductId()); + BigDecimal itemTotal = product.getPrice().multiply(new BigDecimal(item.getQuantity())); + totalAmount = totalAmount.add(itemTotal); + } + + // 创建订单 + Order order = new Order(); + order.setOrderNo(OrderNoUtil.generateOrderNo()); + order.setUserId(userId); + order.setMerchantId(merchantId); + order.setTotalAmount(totalAmount); + order.setDiscountAmount(BigDecimal.ZERO); + order.setActualAmount(totalAmount); + order.setStatus(1); // 待支付 + order.setConsignee(address.getConsignee()); + order.setPhone(address.getPhone()); + order.setAddress(String.format("%s%s%s%s", + address.getProvince(), address.getCity(), + address.getDistrict(), address.getAddress())); + order.setRemark(remark); + + save(order); + + // 创建订单详情 + for (OrderCreateRequest.OrderItemRequest item : items) { + Product product = productService.getById(item.getProductId()); + + OrderDetail orderDetail = new OrderDetail(); + orderDetail.setOrderId(order.getId()); + orderDetail.setProductId(product.getId()); + orderDetail.setQuantity(item.getQuantity()); + orderDetail.setPrice(product.getPrice()); + orderDetail.setSubtotal(product.getPrice().multiply(new BigDecimal(item.getQuantity()))); + orderDetail.setProductName(product.getName()); + orderDetail.setProductImage(getProductMainImage(product.getId())); + orderDetail.setProductSpecs(product.getUnit()); + + orderDetailService.save(orderDetail); + + // 扣减库存 + inventoryService.decreaseStock(product.getId(), item.getQuantity()); + } + + // 记录系统日志 + systemLogService.logOrderCreate(order.getOrderNo(), userId); + + return order; + } + + private Order createSingleOrderFromCarts(Long userId, Long merchantId, List cartItems, + Address address, String remark) { + + // 验证库存并计算金额 + BigDecimal totalAmount = BigDecimal.ZERO; + + for (Cart cart : cartItems) { + Product product = cart.getProduct(); + Integer stockQuantity = inventoryService.getStockQuantity(product.getId()); + + if (stockQuantity == null || stockQuantity < cart.getQuantity()) { + throw new BusinessException("商品 " + product.getName() + " 库存不足"); + } + + BigDecimal itemTotal = product.getPrice().multiply(new BigDecimal(cart.getQuantity())); + totalAmount = totalAmount.add(itemTotal); + } + + // 创建订单 + Order order = new Order(); + order.setOrderNo(OrderNoUtil.generateOrderNo()); + order.setUserId(userId); + order.setMerchantId(merchantId); + order.setTotalAmount(totalAmount); + order.setDiscountAmount(BigDecimal.ZERO); + order.setActualAmount(totalAmount); + order.setStatus(1); // 待支付 + order.setConsignee(address.getConsignee()); + order.setPhone(address.getPhone()); + order.setAddress(String.format("%s%s%s%s", + address.getProvince(), address.getCity(), + address.getDistrict(), address.getAddress())); + order.setRemark(remark); + + save(order); + + // 创建订单详情 + for (Cart cart : cartItems) { + Product product = cart.getProduct(); + + OrderDetail orderDetail = new OrderDetail(); + orderDetail.setOrderId(order.getId()); + orderDetail.setProductId(product.getId()); + orderDetail.setQuantity(cart.getQuantity()); + orderDetail.setPrice(product.getPrice()); + orderDetail.setSubtotal(product.getPrice().multiply(new BigDecimal(cart.getQuantity()))); + orderDetail.setProductName(product.getName()); + orderDetail.setProductImage(getProductMainImage(product.getId())); + orderDetail.setProductSpecs(product.getUnit()); + + orderDetailService.save(orderDetail); + + // 扣减库存 + inventoryService.decreaseStock(product.getId(), cart.getQuantity()); + } + + // 记录系统日志 + systemLogService.logOrderCreate(order.getOrderNo(), userId); + + return order; + } + + // 手动加载商品图片信息 + private void loadProductImages(Product product) { + if (product != null) { + try { + if (productImageService == null) { + product.setImageList(new ArrayList<>()); + return; + } + List imageUrls = productImageService.getImageUrls(product.getId()); + product.setImageList(imageUrls); + } catch (Exception e) { + product.setImageList(new ArrayList<>()); + } + } + } + + private String getProductMainImage(Long productId) { + try { + Product product = productService.getProductDetail(productId); + if (product != null && product.getImageList() != null && !product.getImageList().isEmpty()) { + return product.getImageList().get(0); + } + } catch (Exception e) { + // 忽略异常 + } + return null; + } + + @Override + public Order getOrderDetail(Long orderId) { + Order order = this.getById(orderId); + if (order != null) { + // 加载订单详情 + List orderDetails = orderDetailService.getByOrderId(orderId); + order.setOrderDetails(orderDetails); + } + return order; + } + + @Override + public Order getOrderDetail(Long orderId, Long userId) { + QueryWrapper queryWrapper = new QueryWrapper<>(); + queryWrapper.eq("id", orderId).eq("user_id", userId); + Order order = this.getOne(queryWrapper); + + if (order != null) { + // 加载订单详情 + List orderDetails = orderDetailService.getByOrderId(orderId); + order.setOrderDetails(orderDetails); + + // 设置状态名称 + order.setStatusName(getStatusName(order.getStatus())); + } + + return order; + } + + @Override + public Order getOrderByOrderNo(String orderNo) { + QueryWrapper queryWrapper = new QueryWrapper<>(); + queryWrapper.eq("order_no", orderNo); + return this.getOne(queryWrapper); + } + + @Override + public IPage getUserOrders(Page page, Long userId, Integer status) { + QueryWrapper queryWrapper = new QueryWrapper<>(); + queryWrapper.eq("user_id", userId); + if (status != null) { + queryWrapper.eq("status", status); + } + queryWrapper.orderByDesc("created_at"); + + IPage result = this.page(page, queryWrapper); + + // 填充状态名称和订单详情 + for (Order order : result.getRecords()) { + order.setStatusName(getStatusName(order.getStatus())); + List orderDetails = orderDetailService.getByOrderId(order.getId()); + order.setOrderDetails(orderDetails); + } + + return result; + } + + @Override + public IPage getMerchantOrders(Page page, Long merchantId, Integer status) { + QueryWrapper queryWrapper = new QueryWrapper<>(); + queryWrapper.eq("merchant_id", merchantId); + if (status != null) { + queryWrapper.eq("status", status); + } + queryWrapper.orderByDesc("created_at"); + + IPage result = this.page(page, queryWrapper); + + // 填充状态名称和订单详情 + for (Order order : result.getRecords()) { + order.setStatusName(getStatusName(order.getStatus())); + List orderDetails = orderDetailService.getByOrderId(order.getId()); + order.setOrderDetails(orderDetails); + } + + return result; + } + + @Override + public IPage getAllOrders(Page page, String keyword, Integer status) { + QueryWrapper queryWrapper = new QueryWrapper<>(); + + if (keyword != null && !keyword.trim().isEmpty()) { + queryWrapper.like("order_no", keyword) + .or() + .like("consignee", keyword) + .or() + .like("phone", keyword); + } + + if (status != null) { + queryWrapper.eq("status", status); + } + + queryWrapper.orderByDesc("created_at"); + + IPage result = this.page(page, queryWrapper); + + // 填充状态名称 + for (Order order : result.getRecords()) { + order.setStatusName(getStatusName(order.getStatus())); + } + + return result; + } + + @Override + @Transactional(rollbackFor = Exception.class) + public boolean payOrder(Long orderId, String payTransactionId) { + Order order = this.getById(orderId); + if (order == null) { + throw new BusinessException("订单不存在"); + } + + if (order.getStatus() != 1) { + if (order.getStatus() == 2) { + return true; // 已经支付 + } + throw new BusinessException("订单状态不正确,当前状态: " + order.getStatus()); + } + + order.setStatus(2); + order.setPayTime(LocalDateTime.now()); + order.setPayTransactionId(payTransactionId); + + boolean success = this.updateById(order); + + if (success) { + try { + systemLogService.logOrderPay(order.getOrderNo(), order.getUserId()); + } catch (Exception e) { + // 日志记录失败不影响主要业务 + } + } + + return success; + } + + @Override + public boolean cancelOrder(Long orderId, String reason) { + Order order = this.getById(orderId); + if (order == null) { + throw new BusinessException("订单不存在"); + } + + if (order.getStatus() != 1) { + throw new BusinessException("只能取消待支付订单"); + } + + order.setStatus(5); // 已取消 + order.setCancelTime(LocalDateTime.now()); + order.setCancelReason(reason); + + boolean success = this.updateById(order); + + if (success) { + // 恢复库存 + restoreOrderStock(orderId); + systemLogService.logOrderCancel(order.getOrderNo(), order.getUserId()); + } + + return success; + } + + @Override + public boolean cancelOrder(Long orderId, Long userId) { + QueryWrapper queryWrapper = new QueryWrapper<>(); + queryWrapper.eq("id", orderId).eq("user_id", userId); + Order order = this.getOne(queryWrapper); + + if (order == null) { + throw new BusinessException("订单不存在"); + } + + return cancelOrder(orderId, "用户取消"); + } + + @Override + public boolean shipOrder(Long orderId, String shipCompany, String shipNo) { + Order order = this.getById(orderId); + if (order == null) { + throw new BusinessException("订单不存在"); + } + + if (order.getStatus() != 2) { + throw new BusinessException("只能发货已支付订单"); + } + + order.setStatus(3); // 已发货 + order.setShipTime(LocalDateTime.now()); + order.setShipCompany(shipCompany); + order.setShipNo(shipNo); + + boolean success = this.updateById(order); + + if (success) { + systemLogService.logOrderShip(order.getOrderNo(), UserContext.getCurrentUserId()); + } + + return success; + } + + @Override + public boolean confirmReceive(Long orderId) { + Order order = this.getById(orderId); + if (order == null) { + throw new BusinessException("订单不存在"); + } + + if (order.getStatus() != 3) { + throw new BusinessException("只能确认收货已发货订单"); + } + + order.setStatus(4); // 已完成 + order.setReceiveTime(LocalDateTime.now()); + order.setFinishTime(LocalDateTime.now()); + + boolean success = this.updateById(order); + + if (success) { + systemLogService.logOrderComplete(order.getOrderNo(), order.getUserId()); + } + + return success; + } + + @Override + public boolean confirmOrder(Long orderId, Long userId) { + QueryWrapper queryWrapper = new QueryWrapper<>(); + queryWrapper.eq("id", orderId).eq("user_id", userId); + Order order = this.getOne(queryWrapper); + + if (order == null) { + throw new BusinessException("订单不存在"); + } + + return confirmReceive(orderId); + } + + @Override + public boolean refundOrder(Long orderId, String reason, BigDecimal amount) { + Order order = this.getById(orderId); + if (order == null) { + throw new BusinessException("订单不存在"); + } + + order.setStatus(6); // 已退款 + order.setRefundTime(LocalDateTime.now()); + order.setRefundReason(reason); + order.setRefundAmount(amount != null ? amount : order.getActualAmount()); + + boolean success = this.updateById(order); + + if (success) { + // 恢复库存 + restoreOrderStock(orderId); + systemLogService.logOrderRefund(order.getOrderNo(), UserContext.getCurrentUserId()); + } + + return success; + } + + @Override + public boolean applyRefund(Long orderId, Long userId, String reason) { + // 这里简化处理,直接退款 + return refundOrder(orderId, reason, null); + } + + @Override + public boolean deleteOrder(Long orderId, Long userId) { + QueryWrapper queryWrapper = new QueryWrapper<>(); + queryWrapper.eq("id", orderId).eq("user_id", userId); + Order order = this.getOne(queryWrapper); + + if (order == null) { + throw new BusinessException("订单不存在"); + } + + if (order.getStatus() != 4 && order.getStatus() != 5 && order.getStatus() != 6) { + throw new BusinessException("只能删除已完成、已取消或已退款的订单"); + } + + return this.removeById(orderId); + } + + @Override + public boolean updateOrderStatus(Long orderId, Integer status) { + Order order = new Order(); + order.setId(orderId); + order.setStatus(status); + return this.updateById(order); + } + + @Override + public boolean handleRefund(Long orderId, Boolean approved, String remark) { + if (approved) { + return refundOrder(orderId, remark, null); + } else { + // 拒绝退款,可以记录日志或发送通知 + return true; + } + } + + @Override + public void cancelTimeoutOrders() { + // 取消30分钟未支付订单 + LocalDateTime timeoutTime = LocalDateTime.now().minusMinutes(30); + + QueryWrapper queryWrapper = new QueryWrapper<>(); + queryWrapper.eq("status", 1) + .lt("created_at", timeoutTime); + + List timeoutOrders = this.list(queryWrapper); + + for (Order order : timeoutOrders) { + cancelOrder(order.getId(), "订单超时自动取消"); + } + } + + @Override + public void autoConfirmReceive() { + // 发货后7天自动确认收货 + LocalDateTime autoConfirmTime = LocalDateTime.now().minusDays(7); + + QueryWrapper queryWrapper = new QueryWrapper<>(); + queryWrapper.eq("status", 3) + .lt("ship_time", autoConfirmTime); + + List autoConfirmOrders = this.list(queryWrapper); + + for (Order order : autoConfirmOrders) { + confirmReceive(order.getId()); + } + } + + private void restoreOrderStock(Long orderId) { + List orderDetails = orderDetailService.getByOrderId(orderId); + for (OrderDetail detail : orderDetails) { + inventoryService.increaseStock(detail.getProductId(), detail.getQuantity()); + } + } + + private String getStatusName(Integer status) { + switch (status) { + case 1: return "待支付"; + case 2: return "已支付"; + case 3: return "已发货"; + case 4: return "已完成"; + case 5: return "已取消"; + case 6: return "已退款"; + default: return "未知状态"; + } + } + + @Override + public BigDecimal getTotalRevenue() { + QueryWrapper queryWrapper = new QueryWrapper<>(); + queryWrapper.select("IFNULL(SUM(actual_amount), 0) as total_revenue"); + queryWrapper.in("status", Arrays.asList(2, 3, 4)); // 已支付、已发货、已完成的订单 + + Map result = this.getMap(queryWrapper); + return result != null ? (BigDecimal) result.get("total_revenue") : BigDecimal.ZERO; + } + + @Override + public BigDecimal getTodayRevenue() { + LocalDateTime todayStart = LocalDateTime.of(LocalDate.now(), LocalTime.MIN); + LocalDateTime todayEnd = LocalDateTime.of(LocalDate.now(), LocalTime.MAX); + + QueryWrapper queryWrapper = new QueryWrapper<>(); + queryWrapper.select("IFNULL(SUM(actual_amount), 0) as today_revenue"); + queryWrapper.between("created_at", todayStart, todayEnd); + queryWrapper.in("status", Arrays.asList(2, 3, 4)); // 已支付、已发货、已完成的订单 + + Map result = this.getMap(queryWrapper); + return result != null ? (BigDecimal) result.get("today_revenue") : BigDecimal.ZERO; + } + + @Override + public Map getOrderStatistics() { + Map statistics = new HashMap<>(); + + // 总订单数 + long totalOrders = this.count(); + statistics.put("totalOrders", totalOrders); + + // 今日新增订单 + LocalDateTime todayStart = LocalDateTime.of(LocalDate.now(), LocalTime.MIN); + LocalDateTime todayEnd = LocalDateTime.of(LocalDate.now(), LocalTime.MAX); + long todayOrders = this.count(new QueryWrapper().between("created_at", todayStart, todayEnd)); + statistics.put("todayOrders", todayOrders); + + // 各状态订单数 + for (int status = 1; status <= 6; status++) { + long count = this.count(new QueryWrapper().eq("status", status)); + statistics.put("status" + status + "Count", count); + } + + // 总收入 + statistics.put("totalRevenue", getTotalRevenue()); + + // 今日收入 + statistics.put("todayRevenue", getTodayRevenue()); + + return statistics; + } +} diff --git a/backend/src/main/java/com/sunnyfarm/service/impl/PaymentServiceImpl.java b/backend/src/main/java/com/sunnyfarm/service/impl/PaymentServiceImpl.java new file mode 100644 index 0000000..07da55e --- /dev/null +++ b/backend/src/main/java/com/sunnyfarm/service/impl/PaymentServiceImpl.java @@ -0,0 +1,111 @@ +package com.sunnyfarm.service.impl; + +import lombok.extern.slf4j.Slf4j; + +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.sunnyfarm.entity.Payment; +import com.sunnyfarm.mapper.PaymentMapper; +import com.sunnyfarm.service.PaymentService; +import com.sunnyfarm.util.OrderNoUtil; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.math.BigDecimal; +import java.time.LocalDateTime; + +@Slf4j +@Service +@Transactional +public class PaymentServiceImpl extends ServiceImpl implements PaymentService { + + @Override + public Payment createPayment(Long orderId, BigDecimal amount) { + Payment payment = new Payment(); + payment.setOrderId(orderId); + payment.setPaymentNo(OrderNoUtil.generatePaymentNo()); + payment.setPaymentMethod("alipay"); + payment.setAmount(amount); + payment.setStatus(1); // 待支付 + + save(payment); + return payment; + } + + @Override + public Payment getByOrderId(Long orderId) { + QueryWrapper queryWrapper = new QueryWrapper<>(); + queryWrapper.eq("order_id", orderId); + return getOne(queryWrapper); + } + + @Override + public Payment getByPaymentNo(String paymentNo) { + QueryWrapper queryWrapper = new QueryWrapper<>(); + queryWrapper.eq("payment_no", paymentNo); + return getOne(queryWrapper); + } + + @Override + public boolean updatePaymentSuccess(String paymentNo, String tradeNo, BigDecimal buyerPayAmount, String buyerLogonId) { + try { + log.info("💳 开始更新支付记录: 支付单号={}, 支付宝交易号={}, 实付金额={}, 买家账号={}", + paymentNo, tradeNo, buyerPayAmount, buyerLogonId); + + QueryWrapper queryWrapper = new QueryWrapper<>(); + queryWrapper.eq("payment_no", paymentNo); + + Payment payment = getOne(queryWrapper); + if (payment != null) { + log.info("📄 找到支付记录: ID={}, 当前状态={}", payment.getId(), payment.getStatus()); + + payment.setStatus(2); // 已支付 + payment.setTradeNo(tradeNo); + payment.setAlipayTradeNo(tradeNo); + payment.setBuyerPayAmount(buyerPayAmount.toString()); + payment.setBuyerLogonId(buyerLogonId); + payment.setPaidAt(LocalDateTime.now()); + + boolean result = updateById(payment); + log.info("💾 支付记录更新结果: {}", result ? "成功" : "失败"); + return result; + } else { + log.error("❌ 未找到支付记录, 支付单号: {}", paymentNo); + return false; + } + } catch (Exception e) { + log.error("❌ 更新支付记录异常: {}", e.getMessage(), e); + return false; + } + } + + @Override + public boolean updatePaymentFailed(String paymentNo, String failReason) { + QueryWrapper queryWrapper = new QueryWrapper<>(); + queryWrapper.eq("payment_no", paymentNo); + + Payment payment = getOne(queryWrapper); + if (payment != null) { + payment.setStatus(3); // 支付失败 + return updateById(payment); + } + return false; + } + + @Override + public boolean refundPayment(String paymentNo, BigDecimal refundAmount, String refundReason) { + QueryWrapper queryWrapper = new QueryWrapper<>(); + queryWrapper.eq("payment_no", paymentNo); + + Payment payment = getOne(queryWrapper); + if (payment != null) { + payment.setStatus(4); // 已退款 + payment.setRefundAmount(refundAmount); + payment.setRefundTime(LocalDateTime.now()); + payment.setRefundReason(refundReason); + + return updateById(payment); + } + return false; + } +} diff --git a/backend/src/main/java/com/sunnyfarm/service/impl/PermissionServiceImpl.java b/backend/src/main/java/com/sunnyfarm/service/impl/PermissionServiceImpl.java new file mode 100644 index 0000000..67cfa40 --- /dev/null +++ b/backend/src/main/java/com/sunnyfarm/service/impl/PermissionServiceImpl.java @@ -0,0 +1,18 @@ +package com.sunnyfarm.service.impl; + +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.sunnyfarm.entity.Permission; +import com.sunnyfarm.mapper.PermissionMapper; +import com.sunnyfarm.service.PermissionService; +import org.springframework.stereotype.Service; + +import java.util.List; + +@Service +public class PermissionServiceImpl extends ServiceImpl implements PermissionService { + + @Override + public List getPermissionsByRoleId(Long roleId) { + return baseMapper.selectPermissionsByRoleId(roleId); + } +} diff --git a/backend/src/main/java/com/sunnyfarm/service/impl/ProductImageServiceImpl.java b/backend/src/main/java/com/sunnyfarm/service/impl/ProductImageServiceImpl.java new file mode 100644 index 0000000..184ffbd --- /dev/null +++ b/backend/src/main/java/com/sunnyfarm/service/impl/ProductImageServiceImpl.java @@ -0,0 +1,89 @@ +package com.sunnyfarm.service.impl; + +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.sunnyfarm.entity.ProductImage; +import com.sunnyfarm.mapper.ProductImageMapper; +import com.sunnyfarm.service.ProductImageService; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.util.CollectionUtils; + +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; + +@Service +@Slf4j +public class ProductImageServiceImpl extends ServiceImpl implements ProductImageService { + + @Override + public List getImagesByProductId(Long productId) { + return baseMapper.getImagesByProductId(productId); + } + + @Override + @Transactional + public boolean addProductImages(Long productId, List imageUrls) { + if (CollectionUtils.isEmpty(imageUrls)) { + return true; + } + + List productImages = new ArrayList<>(); + for (int i = 0; i < imageUrls.size(); i++) { + ProductImage productImage = new ProductImage(); + productImage.setProductId(productId); + productImage.setImageUrl(imageUrls.get(i)); + productImage.setIsMain(i == 0); // 第一张图设为主图 + productImage.setSortOrder(i); + productImages.add(productImage); + } + + return this.saveBatch(productImages); + } + + @Override + @Transactional + public boolean updateProductImages(Long productId, List imageUrls) { + // 先删除旧图片 + this.deleteProductImages(productId); + + // 再添加新图片 + return this.addProductImages(productId, imageUrls); + } + + @Override + public boolean deleteProductImages(Long productId) { + return baseMapper.deleteByProductId(productId) >= 0; + } + + @Override + @Transactional + public boolean setMainImage(Long productId, String imageUrl) { + // 先将所有图片设为非主图 + QueryWrapper queryWrapper = new QueryWrapper<>(); + queryWrapper.eq("product_id", productId); + List images = this.list(queryWrapper); + + for (ProductImage image : images) { + image.setIsMain(image.getImageUrl().equals(imageUrl)); + } + + return this.updateBatchById(images); + } + + @Override + public String getMainImageUrl(Long productId) { + ProductImage mainImage = baseMapper.getMainImageByProductId(productId); + return mainImage != null ? mainImage.getImageUrl() : null; + } + + @Override + public List getImageUrls(Long productId) { + List images = this.getImagesByProductId(productId); + return images.stream() + .map(ProductImage::getImageUrl) + .collect(Collectors.toList()); + } +} diff --git a/backend/src/main/java/com/sunnyfarm/service/impl/ProductServiceImpl.java b/backend/src/main/java/com/sunnyfarm/service/impl/ProductServiceImpl.java new file mode 100644 index 0000000..f79bd4d --- /dev/null +++ b/backend/src/main/java/com/sunnyfarm/service/impl/ProductServiceImpl.java @@ -0,0 +1,370 @@ +package com.sunnyfarm.service.impl; + +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.sunnyfarm.dto.MerchantDashboardDTO; +import com.sunnyfarm.entity.Product; +import com.sunnyfarm.exception.BusinessException; +import com.sunnyfarm.mapper.ProductMapper; +import com.sunnyfarm.service.ProductService; +import com.sunnyfarm.service.ProductImageService; +import com.sunnyfarm.service.InventoryService; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.util.CollectionUtils; + +import java.math.BigDecimal; +import java.util.ArrayList; +import java.util.List; + +@Service +@Slf4j +public class ProductServiceImpl extends ServiceImpl implements ProductService { + + @Autowired(required = false) + private ProductImageService productImageService; + + @Autowired(required = false) + private InventoryService inventoryService; + + // 安全地加载图片(如果ProductImageService可用的话) + private void loadProductImages(Product product) { + if (productImageService != null && product != null) { + try { + List imageUrls = productImageService.getImageUrls(product.getId()); + product.setImageList(imageUrls); + } catch (Exception e) { + log.warn("加载商品{}图片失败: {}", product.getId(), e.getMessage()); + product.setImageList(new ArrayList<>()); + } + } + } + + // 安全地加载库存信息 + private void loadProductStock(Product product) { + if (inventoryService != null && product != null) { + try { + Integer stock = inventoryService.getStockQuantity(product.getId()); + product.setStock(stock); + } catch (Exception e) { + log.warn("加载商品{}库存失败: {}", product.getId(), e.getMessage()); + product.setStock(0); + } + } else { + product.setStock(0); + } + } + + // 安全地加载批量图片和库存 + private void loadProductImagesBatch(List products) { + if (!CollectionUtils.isEmpty(products)) { + for (Product product : products) { + loadProductImages(product); + loadProductStock(product); + } + } + } + + // 安全地保存图片 + private void saveProductImages(Long productId, List imageUrls) { + if (productImageService != null && !CollectionUtils.isEmpty(imageUrls)) { + try { + productImageService.addProductImages(productId, imageUrls); + } catch (Exception e) { + log.warn("保存商品{}图片失败: {}", productId, e.getMessage()); + } + } + } + + // 安全地更新图片 + private void updateProductImages(Long productId, List imageUrls) { + if (productImageService != null && imageUrls != null) { + try { + productImageService.updateProductImages(productId, imageUrls); + } catch (Exception e) { + log.warn("更新商品{}图片失败: {}", productId, e.getMessage()); + } + } + } + + // 安全地删除图片 + private void deleteProductImages(Long productId) { + if (productImageService != null) { + try { + productImageService.deleteProductImages(productId); + } catch (Exception e) { + log.warn("删除商品{}图片失败: {}", productId, e.getMessage()); + } + } + } + + @Override + public IPage getProductList(Page page, String keyword, Long categoryId, + BigDecimal minPrice, BigDecimal maxPrice, String sort) { + QueryWrapper queryWrapper = new QueryWrapper<>(); + + // 只显示已上架的产品 + queryWrapper.eq("status", 1); + + if (keyword != null && !keyword.trim().isEmpty()) { + queryWrapper.like("name", keyword); + } + + if (categoryId != null) { + queryWrapper.eq("category_id", categoryId); + } + + if (minPrice != null) { + queryWrapper.ge("price", minPrice); + } + + if (maxPrice != null) { + queryWrapper.le("price", maxPrice); + } + + // 排序 + if ("price_asc".equals(sort)) { + queryWrapper.orderByAsc("price"); + } else if ("price_desc".equals(sort)) { + queryWrapper.orderByDesc("price"); + } else if ("sales".equals(sort)) { + queryWrapper.orderByDesc("sales_count"); + } else { + queryWrapper.orderByDesc("created_at"); + } + + IPage result = this.page(page, queryWrapper); + loadProductImagesBatch(result.getRecords()); + return result; + } + + @Override + public Product getProductDetail(Long id) { + Product product = this.getById(id); + if (product != null && product.getStatus() == 1) { + loadProductImages(product); + loadProductStock(product); + + // 增加浏览量 + product.setViewCount(product.getViewCount() + 1); + this.updateById(product); + return product; + } + return null; + } + + @Override + public List getHotProducts(Integer limit) { + QueryWrapper queryWrapper = new QueryWrapper<>(); + queryWrapper.eq("status", 1) + .orderByDesc("sales_count") + .last("LIMIT " + limit); + List products = this.list(queryWrapper); + loadProductImagesBatch(products); + return products; + } + + @Override + public List getNewProducts(Integer limit) { + QueryWrapper queryWrapper = new QueryWrapper<>(); + queryWrapper.eq("status", 1) + .orderByDesc("created_at") + .last("LIMIT " + limit); + List products = this.list(queryWrapper); + loadProductImagesBatch(products); + return products; + } + + @Override + public IPage getCategoryProducts(Page page, Long categoryId, String sort) { + QueryWrapper queryWrapper = new QueryWrapper<>(); + queryWrapper.eq("status", 1) + .eq("category_id", categoryId); + + if ("price_asc".equals(sort)) { + queryWrapper.orderByAsc("price"); + } else if ("price_desc".equals(sort)) { + queryWrapper.orderByDesc("price"); + } else if ("sales".equals(sort)) { + queryWrapper.orderByDesc("sales_count"); + } else { + queryWrapper.orderByDesc("created_at"); + } + + IPage result = this.page(page, queryWrapper); + loadProductImagesBatch(result.getRecords()); + return result; + } + + @Override + public IPage searchProducts(Page page, String keyword, String sort) { + QueryWrapper queryWrapper = new QueryWrapper<>(); + queryWrapper.eq("status", 1) + .like("name", keyword); + + if ("price_asc".equals(sort)) { + queryWrapper.orderByAsc("price"); + } else if ("price_desc".equals(sort)) { + queryWrapper.orderByDesc("price"); + } else if ("sales".equals(sort)) { + queryWrapper.orderByDesc("sales_count"); + } else { + queryWrapper.orderByDesc("created_at"); + } + + IPage result = this.page(page, queryWrapper); + loadProductImagesBatch(result.getRecords()); + return result; + } + + @Override + @Transactional + public Product createProduct(Product product) { + product.setStatus(1); // 管理员创建的直接上架 + product.setSalesCount(0); + product.setViewCount(0); + + if (this.save(product)) { + saveProductImages(product.getId(), product.getImageList()); + return product; + } + throw new BusinessException("产品创建失败"); + } + + @Override + @Transactional + public Product updateProduct(Long id, Product product) { + product.setId(id); + if (this.updateById(product)) { + updateProductImages(id, product.getImageList()); + + Product updatedProduct = this.getById(id); + loadProductImages(updatedProduct); + loadProductStock(updatedProduct); + return updatedProduct; + } + throw new BusinessException("产品更新失败"); + } + + @Override + @Transactional + public boolean deleteProduct(Long id) { + deleteProductImages(id); + return this.removeById(id); + } + + @Override + public boolean updateProductStock(Long id, Integer stock) { + // 这个方法保留,但实际库存管理由InventoryService处理 + if (inventoryService != null) { + return inventoryService.createOrUpdateInventory(id, stock, 10); + } + return false; + } + + @Override + public boolean updateProductStatus(Long id, Integer status) { + Product product = new Product(); + product.setId(id); + product.setStatus(status); + return this.updateById(product); + } + + // ==================== 商家专用方法实现 ==================== + + @Override + public IPage getMerchantProductPage(Page page, Long merchantId, String keyword, Integer status) { + QueryWrapper queryWrapper = new QueryWrapper<>(); + queryWrapper.eq("merchant_id", merchantId); + + if (keyword != null && !keyword.trim().isEmpty()) { + queryWrapper.like("name", keyword); + } + + if (status != null) { + queryWrapper.eq("status", status); + } + + queryWrapper.orderByDesc("created_at"); + IPage result = this.page(page, queryWrapper); + loadProductImagesBatch(result.getRecords()); + return result; + } + + @Override + @Transactional + public Product createMerchantProduct(Product product) { + // 设置默认状态为审核中 + product.setStatus(0); + product.setSalesCount(0); + product.setViewCount(0); + + if (this.save(product)) { + saveProductImages(product.getId(), product.getImageList()); + return product; + } + throw new BusinessException("商品创建失败"); + } + + @Override + @Transactional + public Product updateMerchantProduct(Product product) { + // 商家编辑后需要重新审核 + product.setStatus(0); + + if (this.updateById(product)) { + updateProductImages(product.getId(), product.getImageList()); + + Product updatedProduct = this.getById(product.getId()); + loadProductImages(updatedProduct); + loadProductStock(updatedProduct); + return updatedProduct; + } + throw new BusinessException("商品更新失败"); + } + + @Override + @Transactional + public boolean deleteMerchantProduct(Long id) { + deleteProductImages(id); + return this.removeById(id); + } + + @Override + public boolean updateMerchantProductStatus(Long id, Integer status) { + Product product = new Product(); + product.setId(id); + product.setStatus(status); + return this.updateById(product); + } + + @Override + public Product getMerchantProductDetail(Long id, Long merchantId) { + QueryWrapper queryWrapper = new QueryWrapper<>(); + queryWrapper.eq("id", id).eq("merchant_id", merchantId); + Product product = this.getOne(queryWrapper); + + if (product != null) { + loadProductImages(product); + loadProductStock(product); + } + + return product; + } + + @Override + public Long getProductCountByMerchant(Long merchantId) { + QueryWrapper queryWrapper = new QueryWrapper<>(); + queryWrapper.eq("merchant_id", merchantId); + return this.count(queryWrapper); + } + + @Override + public List getTopProductsByMerchant(Long merchantId, Integer limit) { + return new ArrayList<>(); + } +} diff --git a/backend/src/main/java/com/sunnyfarm/service/impl/RoleServiceImpl.java b/backend/src/main/java/com/sunnyfarm/service/impl/RoleServiceImpl.java new file mode 100644 index 0000000..3622e91 --- /dev/null +++ b/backend/src/main/java/com/sunnyfarm/service/impl/RoleServiceImpl.java @@ -0,0 +1,16 @@ +package com.sunnyfarm.service.impl; + +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.sunnyfarm.entity.Role; +import com.sunnyfarm.mapper.RoleMapper; +import com.sunnyfarm.service.RoleService; +import org.springframework.stereotype.Service; + +@Service +public class RoleServiceImpl extends ServiceImpl implements RoleService { + + @Override + public Role getRoleWithPermissions(Long roleId) { + return baseMapper.selectRoleWithPermissions(roleId); + } +} diff --git a/backend/src/main/java/com/sunnyfarm/service/impl/StatisticsServiceImpl.java b/backend/src/main/java/com/sunnyfarm/service/impl/StatisticsServiceImpl.java new file mode 100644 index 0000000..d328fb6 --- /dev/null +++ b/backend/src/main/java/com/sunnyfarm/service/impl/StatisticsServiceImpl.java @@ -0,0 +1,212 @@ +package com.sunnyfarm.service.impl; + +import com.sunnyfarm.service.StatisticsService; +import com.sunnyfarm.service.UserService; +import com.sunnyfarm.service.ProductService; +import com.sunnyfarm.service.OrderService; +import com.sunnyfarm.service.CategoryService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import java.math.BigDecimal; +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; +import java.util.HashMap; +import java.util.Map; + +@Service +public class StatisticsServiceImpl implements StatisticsService { + + @Autowired + private UserService userService; + + @Autowired + private ProductService productService; + + @Autowired + private OrderService orderService; + + @Autowired + private CategoryService categoryService; + + @Override + public Map getOverviewStatistics() { + Map result = new HashMap<>(); + + // 总用户数 + result.put("totalUsers", userService.count()); + + // 总产品数 + result.put("totalProducts", productService.count()); + + // 总订单数 + result.put("totalOrders", orderService.count()); + + // 总分类数 + result.put("totalCategories", categoryService.count()); + + // 今日新增用户 + result.put("todayNewUsers", getTodayNewUsers()); + + // 今日订单数 + result.put("todayOrders", getTodayOrders()); + + // 总销售额 + result.put("totalSales", getTotalSales()); + + // 待处理订单 + result.put("pendingOrders", getPendingOrders()); + + return result; + } + + @Override + public Map getSalesStatistics(String startDate, String endDate) { + Map result = new HashMap<>(); + + // 如果没有指定日期,默认最近7天 + if (startDate == null || endDate == null) { + LocalDateTime now = LocalDateTime.now(); + LocalDateTime start = now.minusDays(7); + startDate = start.format(DateTimeFormatter.ofPattern("yyyy-MM-dd")); + endDate = now.format(DateTimeFormatter.ofPattern("yyyy-MM-dd")); + } + + result.put("startDate", startDate); + result.put("endDate", endDate); + result.put("totalSales", BigDecimal.valueOf(50000.00)); // 模拟数据 + result.put("totalOrders", 156); // 模拟数据 + result.put("averageOrderValue", BigDecimal.valueOf(320.51)); // 模拟数据 + + // 每日销售数据(模拟) + result.put("dailySales", getDailySalesData()); + + return result; + } + + @Override + public Map getProductStatistics() { + Map result = new HashMap<>(); + + result.put("totalProducts", productService.count()); + result.put("activeProducts", getActiveProductCount()); + result.put("inactiveProducts", getInactiveProductCount()); + result.put("lowStockProducts", getLowStockProductCount()); + + // 热销产品TOP10(模拟) + result.put("hotProducts", getHotProductsData()); + + // 分类产品分布(模拟) + result.put("categoryDistribution", getCategoryDistributionData()); + + return result; + } + + @Override + public Map getUserStatistics() { + Map result = new HashMap<>(); + + result.put("totalUsers", userService.count()); + result.put("activeUsers", getActiveUserCount()); + result.put("newUsersThisMonth", getNewUsersThisMonth()); + result.put("userGrowthRate", getUserGrowthRate()); + + // 用户增长趋势(模拟) + result.put("userGrowthTrend", getUserGrowthTrendData()); + + // 用户地区分布(模拟) + result.put("userRegionDistribution", getUserRegionDistributionData()); + + return result; + } + + // 私有方法 - 获取今日新增用户 + private Long getTodayNewUsers() { + // TODO: 实现实际的今日新增用户统计 + return 15L; // 模拟数据 + } + + // 私有方法 - 获取今日订单数 + private Long getTodayOrders() { + // TODO: 实现实际的今日订单统计 + return 28L; // 模拟数据 + } + + // 私有方法 - 获取总销售额 + private BigDecimal getTotalSales() { + // TODO: 实现实际的总销售额统计 + return BigDecimal.valueOf(1250000.00); // 模拟数据 + } + + // 私有方法 - 获取待处理订单 + private Long getPendingOrders() { + // TODO: 实现实际的待处理订单统计 + return 12L; // 模拟数据 + } + + // 私有方法 - 获取活跃产品数量 + private Long getActiveProductCount() { + // TODO: 实现实际的活跃产品统计 + return productService.count() - 5; // 模拟数据 + } + + // 私有方法 - 获取非活跃产品数量 + private Long getInactiveProductCount() { + // TODO: 实现实际的非活跃产品统计 + return 5L; // 模拟数据 + } + + // 私有方法 - 获取低库存产品数量 + private Long getLowStockProductCount() { + // TODO: 实现实际的低库存产品统计 + return 8L; // 模拟数据 + } + + // 私有方法 - 获取活跃用户数量 + private Long getActiveUserCount() { + // TODO: 实现实际的活跃用户统计 + return userService.count() - 10; // 模拟数据 + } + + // 私有方法 - 获取本月新增用户 + private Long getNewUsersThisMonth() { + // TODO: 实现实际的本月新增用户统计 + return 85L; // 模拟数据 + } + + // 私有方法 - 获取用户增长率 + private Double getUserGrowthRate() { + // TODO: 实现实际的用户增长率统计 + return 12.5; // 模拟数据 + } + + // 私有方法 - 获取每日销售数据 + private Object getDailySalesData() { + // TODO: 实现实际的每日销售数据 + return new HashMap<>(); // 模拟数据 + } + + // 私有方法 - 获取热销产品数据 + private Object getHotProductsData() { + // TODO: 实现实际的热销产品数据 + return new HashMap<>(); // 模拟数据 + } + + // 私有方法 - 获取分类分布数据 + private Object getCategoryDistributionData() { + // TODO: 实现实际的分类分布数据 + return new HashMap<>(); // 模拟数据 + } + + // 私有方法 - 获取用户增长趋势数据 + private Object getUserGrowthTrendData() { + // TODO: 实现实际的用户增长趋势数据 + return new HashMap<>(); // 模拟数据 + } + + // 私有方法 - 获取用户地区分布数据 + private Object getUserRegionDistributionData() { + // TODO: 实现实际的用户地区分布数据 + return new HashMap<>(); // 模拟数据 + } +} diff --git a/backend/src/main/java/com/sunnyfarm/service/impl/SystemConfigServiceImpl.java b/backend/src/main/java/com/sunnyfarm/service/impl/SystemConfigServiceImpl.java new file mode 100644 index 0000000..51de5af --- /dev/null +++ b/backend/src/main/java/com/sunnyfarm/service/impl/SystemConfigServiceImpl.java @@ -0,0 +1,145 @@ +package com.sunnyfarm.service.impl; + +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.sunnyfarm.entity.SystemConfig; +import com.sunnyfarm.entity.SystemLog; +import com.sunnyfarm.mapper.SystemConfigMapper; +import com.sunnyfarm.service.SystemConfigService; +import com.sunnyfarm.service.SystemLogService; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +@Service +@Slf4j +public class SystemConfigServiceImpl extends ServiceImpl implements SystemConfigService { + + @Autowired + private SystemLogService systemLogService; + + @Override + public Map getSystemConfig() { + Map config = new HashMap<>(); + + // 获取所有配置 + List configList = this.list(); + + // 设置默认值 + config.put("siteName", "农产品直销平台"); + config.put("siteLogo", ""); + config.put("siteDescription", "新鲜农产品直销平台"); + config.put("contactPhone", "400-888-8888"); + config.put("contactEmail", "service@sunnyfarm.com"); + config.put("autoConfirmDays", 7); + config.put("maxCartItems", 50); + + // 覆盖数据库中的配置 + for (SystemConfig item : configList) { + String key = toCamelCase(item.getConfigKey()); + String value = item.getConfigValue(); + + // 根据类型转换值 + if ("number".equals(item.getConfigType())) { + try { + config.put(key, Integer.parseInt(value)); + } catch (NumberFormatException e) { + config.put(key, value); + } + } else { + config.put(key, value); + } + } + + return config; + } + + @Override + public boolean updateSystemConfig(Map config, Long operatorId, String operatorName) { + try { + // 更新各个配置项 + updateConfigItem(SystemConfig.SITE_NAME, config.get("siteName"), "string"); + updateConfigItem(SystemConfig.SITE_LOGO, config.get("siteLogo"), "string"); + updateConfigItem(SystemConfig.SITE_DESCRIPTION, config.get("siteDescription"), "string"); + updateConfigItem(SystemConfig.CONTACT_PHONE, config.get("contactPhone"), "string"); + updateConfigItem(SystemConfig.CONTACT_EMAIL, config.get("contactEmail"), "string"); + updateConfigItem(SystemConfig.AUTO_CONFIRM_DAYS, config.get("autoConfirmDays"), "number"); + updateConfigItem(SystemConfig.MAX_CART_ITEMS, config.get("maxCartItems"), "number"); + + // 记录操作日志 + systemLogService.recordSuccessLog( + SystemLog.SYSTEM_CONFIG, + "更新系统配置", + operatorId, + operatorName, + SystemLog.OPERATOR_ADMIN + ); + + return true; + } catch (Exception e) { + log.error("更新系统配置失败", e); + + systemLogService.recordFailLog( + SystemLog.SYSTEM_CONFIG, + "更新系统配置", + operatorId, + operatorName, + SystemLog.OPERATOR_ADMIN, + e.getMessage() + ); + + return false; + } + } + + @Override + public String getConfigValue(String key) { + return baseMapper.selectValueByKey(key); + } + + @Override + public String getConfigValue(String key, String defaultValue) { + String value = getConfigValue(key); + return value != null ? value : defaultValue; + } + + private void updateConfigItem(String key, Object value, String type) { + if (value == null) { + return; + } + + SystemConfig config = this.getOne(new QueryWrapper().eq("config_key", key)); + + if (config == null) { + config = new SystemConfig(); + config.setConfigKey(key); + config.setConfigValue(value.toString()); + config.setConfigType(type); + this.save(config); + } else { + config.setConfigValue(value.toString()); + config.setConfigType(type); + this.updateById(config); + } + } + + private String toCamelCase(String str) { + if (str == null || str.isEmpty()) { + return str; + } + + String[] parts = str.split("_"); + StringBuilder result = new StringBuilder(parts[0]); + + for (int i = 1; i < parts.length; i++) { + result.append(Character.toUpperCase(parts[i].charAt(0))) + .append(parts[i].substring(1)); + } + + return result.toString(); + } +} diff --git a/backend/src/main/java/com/sunnyfarm/service/impl/SystemLogServiceImpl.java b/backend/src/main/java/com/sunnyfarm/service/impl/SystemLogServiceImpl.java new file mode 100644 index 0000000..db94857 --- /dev/null +++ b/backend/src/main/java/com/sunnyfarm/service/impl/SystemLogServiceImpl.java @@ -0,0 +1,197 @@ +package com.sunnyfarm.service.impl; + +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.sunnyfarm.entity.SystemLog; +import com.sunnyfarm.mapper.SystemLogMapper; +import com.sunnyfarm.service.SystemLogService; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; + +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +@Slf4j +@Service +public class SystemLogServiceImpl extends ServiceImpl implements SystemLogService { + + @Override + public void logOperation(String operationType, String operationDesc, Long operatorId, String operatorName, Integer operatorType) { + try { + SystemLog systemLog = new SystemLog(); + systemLog.setOperationType(operationType); + systemLog.setOperationDesc(operationDesc); + systemLog.setOperatorId(operatorId); + systemLog.setOperatorName(operatorName); + systemLog.setOperatorType(operatorType); + systemLog.setResult(1); // 1表示成功 + systemLog.setCreateTime(LocalDateTime.now()); + systemLog.setUpdateTime(LocalDateTime.now()); + + this.save(systemLog); + } catch (Exception e) { + log.error("记录系统日志失败", e); + } + } + + @Override + public void recordSuccessLog(String operationType, String operationDesc, Long operatorId, String operatorName, int operatorType) { + logOperation(operationType, operationDesc, operatorId, operatorName, operatorType); + } + + @Override + public void recordFailLog(String operationType, String operationDesc, Long operatorId, String operatorName, int operatorType, String errorMsg) { + try { + SystemLog systemLog = new SystemLog(); + systemLog.setOperationType(operationType); + systemLog.setOperationDesc(operationDesc); + systemLog.setOperatorId(operatorId); + systemLog.setOperatorName(operatorName); + systemLog.setOperatorType(operatorType); + systemLog.setResult(0); // 0表示失败 + systemLog.setErrorMsg(errorMsg); + systemLog.setCreateTime(LocalDateTime.now()); + systemLog.setUpdateTime(LocalDateTime.now()); + + this.save(systemLog); + } catch (Exception e) { + log.error("记录系统日志失败", e); + } + } + + @Override + public List> getRecentActivities(int limit) { + QueryWrapper wrapper = new QueryWrapper<>(); + wrapper.orderByDesc("created_at"); + wrapper.last("LIMIT " + limit); + + List systemLogs = this.list(wrapper); + + // 转换为Map格式 + return systemLogs.stream().map(log -> { + Map activity = new HashMap<>(); + activity.put("id", log.getId()); + activity.put("type", log.getOperationType()); + activity.put("description", log.getOperationDesc()); + activity.put("operator", log.getOperatorName()); + activity.put("operatorType", log.getOperatorType()); + activity.put("result", log.getResult()); + activity.put("createTime", log.getCreateTime() != null ? + log.getCreateTime().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")) : null); + return activity; + }).collect(Collectors.toList()); + } + + @Override + public void logUserLogin(String username, Long userId, String ipAddress, String userAgent, boolean success, String errorMsg) { + String desc = success ? "用户登录成功:" + username : "用户登录失败:" + username; + logOperation("user_login", desc, userId, username, 1, ipAddress, userAgent, success ? 1 : 0, errorMsg); + } + + @Override + public void logMerchantLogin(String username, Long userId, String ipAddress, String userAgent, boolean success, String errorMsg) { + String desc = success ? "商家登录成功:" + username : "商家登录失败:" + username; + logOperation("merchant_login", desc, userId, username, 2, ipAddress, userAgent, success ? 1 : 0, errorMsg); + } + + @Override + public void logAdminLogin(String username, Long adminId, String ipAddress, String userAgent, boolean success, String errorMsg) { + String desc = success ? "管理员登录成功:" + username : "管理员登录失败:" + username; + logOperation("admin_login", desc, adminId, username, 3, ipAddress, userAgent, success ? 1 : 0, errorMsg); + } + + private void logOperation(String operationType, String operationDesc, Long operatorId, String operatorName, + Integer operatorType, String ipAddress, String userAgent, Integer result, String errorMsg) { + try { + SystemLog systemLog = new SystemLog(); + systemLog.setOperationType(operationType); + systemLog.setOperationDesc(operationDesc); + systemLog.setOperatorId(operatorId); + systemLog.setOperatorName(operatorName); + systemLog.setOperatorType(operatorType); + systemLog.setIpAddress(ipAddress); + systemLog.setUserAgent(userAgent); + systemLog.setResult(result); + systemLog.setErrorMsg(errorMsg); + systemLog.setCreateTime(LocalDateTime.now()); + systemLog.setUpdateTime(LocalDateTime.now()); + + this.save(systemLog); + } catch (Exception e) { + log.error("记录系统日志失败", e); + } + } + + @Override + public IPage pageSystemLogs(Page page, String operationType, Integer operatorType, String startTime, String endTime) { + QueryWrapper wrapper = new QueryWrapper<>(); + + if (operationType != null && !operationType.trim().isEmpty()) { + wrapper.eq("operation_type", operationType); + } + + if (operatorType != null) { + wrapper.eq("operator_type", operatorType); + } + + if (startTime != null && !startTime.trim().isEmpty()) { + wrapper.ge("created_at", startTime); + } + + if (endTime != null && !endTime.trim().isEmpty()) { + wrapper.le("created_at", endTime); + } + + wrapper.orderByDesc("created_at"); + + return this.page(page, wrapper); + } + + @Override + public void logProductAudit(String productName, Long operatorId, String operatorName, boolean approved) { + String desc = approved ? "商品审核通过: " + productName : "商品审核拒绝: " + productName; + logOperation("product_audit", desc, operatorId, operatorName, 3); + } + + @Override + public void logMerchantAudit(String merchantName, Long operatorId, String operatorName, boolean approved) { + String desc = approved ? "商家审核通过: " + merchantName : "商家审核拒绝: " + merchantName; + logOperation("merchant_audit", desc, operatorId, operatorName, 3); + } + + @Override + public void logOrderCreate(String orderNo, Long userId) { + logOperation("order_create", "订单创建成功:" + orderNo, userId, null, 1); + } + + @Override + public void logOrderPay(String orderNo, Long userId) { + logOperation("order_pay", "订单支付成功:" + orderNo, userId, null, 1); + } + + @Override + public void logOrderCancel(String orderNo, Long userId) { + logOperation("order_cancel", "订单取消成功:" + orderNo, userId, null, 1); + } + + @Override + public void logOrderShip(String orderNo, Long operatorId) { + logOperation("order_ship", "订单发货成功:" + orderNo, operatorId, null, 2); + } + + @Override + public void logOrderComplete(String orderNo, Long userId) { + logOperation("order_complete", "订单完成:" + orderNo, userId, null, 1); + } + + @Override + public void logOrderRefund(String orderNo, Long operatorId) { + logOperation("order_refund", "订单退款成功:" + orderNo, operatorId, null, 1); + } +} diff --git a/backend/src/main/java/com/sunnyfarm/service/impl/UserServiceImpl.java b/backend/src/main/java/com/sunnyfarm/service/impl/UserServiceImpl.java new file mode 100644 index 0000000..20500e0 --- /dev/null +++ b/backend/src/main/java/com/sunnyfarm/service/impl/UserServiceImpl.java @@ -0,0 +1,263 @@ +package com.sunnyfarm.service.impl; + +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.sunnyfarm.entity.User; +import com.sunnyfarm.exception.BusinessException; +import com.sunnyfarm.mapper.UserMapper; +import com.sunnyfarm.service.UserService; +import com.sunnyfarm.util.PasswordUtil; +import com.sunnyfarm.util.RedisUtil; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.mail.javamail.JavaMailSender; +import org.springframework.mail.javamail.MimeMessageHelper; +import org.springframework.stereotype.Service; + +import javax.mail.internet.MimeMessage; +import java.time.LocalDateTime; +import java.util.Random; + +@Service +@Slf4j +public class UserServiceImpl extends ServiceImpl implements UserService { + + @Autowired + private RedisUtil redisUtil; + + @Autowired + private JavaMailSender mailSender; + + @Override + public User register(User user) { + // 检查用户名是否已存在 + if (existsByUsername(user.getUsername())) { + throw new BusinessException("用户名已存在"); + } + + // 检查手机号是否已存在 + if (existsByPhone(user.getPhone())) { + throw new BusinessException("手机号已存在"); + } + + // 检查邮箱是否已存在 + if (user.getEmail() != null && !user.getEmail().isEmpty() && existsByEmail(user.getEmail())) { + throw new BusinessException("邮箱已存在"); + } + + // 密码加密 + user.setPassword(PasswordUtil.encode(user.getPassword())); + + // 设置默认值 + user.setStatus(1); + user.setIsVerified(false); + + // 保存用户 + this.save(user); + + log.info("用户注册成功:{}", user.getUsername()); + return user; + } + + @Override + public User login(String username, String password) { + // 根据用户名查询用户 + QueryWrapper queryWrapper = new QueryWrapper<>(); + queryWrapper.eq("username", username); + User user = this.getOne(queryWrapper); + + if (user == null) { + throw new BusinessException("用户名或密码错误"); + } + + // 验证密码 + if (!PasswordUtil.matches(password, user.getPassword())) { + throw new BusinessException("用户名或密码错误"); + } + + // 检查用户状态 + if (user.getStatus() == 0) { + throw new BusinessException("账户已被禁用"); + } + + // 更新最后登录时间 + user.setLastLoginTime(LocalDateTime.now()); + this.updateById(user); + + return user; + } + + @Override + public User updateProfile(User user) { + this.updateById(user); + return user; + } + + @Override + public boolean changePassword(Long userId, String oldPassword, String newPassword) { + User user = this.getById(userId); + if (user == null) { + throw new BusinessException("用户不存在"); + } + + // 验证旧密码 + if (!PasswordUtil.matches(oldPassword, user.getPassword())) { + throw new BusinessException("原密码错误"); + } + + // 更新密码 + user.setPassword(PasswordUtil.encode(newPassword)); + return this.updateById(user); + } + + @Override + public boolean sendEmailCode(String email) { + try { + // 生成6位验证码 + String code = String.format("%06d", new Random().nextInt(999999)); + + // 存储验证码到Redis,有效期5分钟 + redisUtil.set("email_code:" + email, code, 300); + + // 发送邮件 + MimeMessage message = mailSender.createMimeMessage(); + MimeMessageHelper helper = new MimeMessageHelper(message, true, "UTF-8"); + + // 设置发件人 + helper.setFrom("852326703@qq.com", "农产品直销平台"); + helper.setTo(email); + helper.setSubject("【农产品直销平台】邮箱验证码"); + helper.setText(generateEmailContent(code), true); + + mailSender.send(message); + + log.info("验证码发送成功:{}", email); + return true; + } catch (Exception e) { + log.error("验证码发送失败:{}", e.getMessage()); + throw new BusinessException("验证码发送失败"); + } + } + + @Override + public boolean verifyEmailCode(String email, String code) { + String cacheKey = "email_code:" + email; + String cachedCode = (String) redisUtil.get(cacheKey); + + if (cachedCode == null) { + throw new BusinessException("验证码已过期或不存在"); + } + + if (!cachedCode.equals(code)) { + throw new BusinessException("验证码错误"); + } + + // 验证成功后删除验证码 + redisUtil.del(cacheKey); + return true; + } + + @Override + public IPage getUserList(Page page, String keyword, Integer status) { + QueryWrapper queryWrapper = new QueryWrapper<>(); + + if (keyword != null && !keyword.trim().isEmpty()) { + queryWrapper.like("username", keyword) + .or() + .like("nickname", keyword) + .or() + .like("phone", keyword); + } + + if (status != null) { + queryWrapper.eq("status", status); + } + + queryWrapper.orderByDesc("created_at"); + + return this.page(page, queryWrapper); + } + + @Override + public boolean updateUserStatus(Long userId, Integer status) { + User user = new User(); + user.setId(userId); + user.setStatus(status); + return this.updateById(user); + } + + @Override + public User findByUsernameOrPhone(String username) { + QueryWrapper wrapper = new QueryWrapper<>(); + wrapper.eq("username", username).or().eq("phone", username); + return getOne(wrapper); + } + + @Override + public boolean existsByUsername(String username) { + QueryWrapper queryWrapper = new QueryWrapper<>(); + queryWrapper.eq("username", username); + return this.count(queryWrapper) > 0; + } + + @Override + public boolean existsByPhone(String phone) { + QueryWrapper queryWrapper = new QueryWrapper<>(); + queryWrapper.eq("phone", phone); + return this.count(queryWrapper) > 0; + } + + @Override + public boolean existsByEmail(String email) { + QueryWrapper queryWrapper = new QueryWrapper<>(); + queryWrapper.eq("email", email); + return this.count(queryWrapper) > 0; + } + + /** + * 生成邮件内容 + */ + private String generateEmailContent(String code) { + return "" + + "" + + "" + + " " + + " " + + " 邮箱验证码" + + "" + + "" + + "
" + + "

🌱 农产品直销平台

" + + "

您的邮箱验证码

" + + "
" + + "
" + + "
" + + "
" + + "

" + code + "

" + + "
" + + "
" + + "
" + + "

" + + " 验证码有效期:5分钟" + + "

" + + "
" + + "
" + + "

" + + " 安全提醒:
" + + " • 请勿将验证码告诉他人
" + + " • 此验证码仅用于农产品直销平台账户验证
" + + " • 如果不是您本人操作,请忽略此邮件" + + "

" + + "
" + + "
" + + "

" + + " 此邮件由系统自动发送,请勿回复
" + + " 如有疑问,请联系客服:service@sunnyfarm.com" + + "

" + + "
" + + "" + + ""; + } +} diff --git a/backend/src/main/java/com/sunnyfarm/util/FileUploadUtil.java b/backend/src/main/java/com/sunnyfarm/util/FileUploadUtil.java new file mode 100644 index 0000000..b89e8f9 --- /dev/null +++ b/backend/src/main/java/com/sunnyfarm/util/FileUploadUtil.java @@ -0,0 +1,107 @@ +package com.sunnyfarm.util; + +import com.qcloud.cos.COSClient; +import com.qcloud.cos.model.ObjectMetadata; +import com.qcloud.cos.model.PutObjectRequest; +import com.qcloud.cos.model.PutObjectResult; +import com.sunnyfarm.config.properties.CosProperties; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; +import org.springframework.web.multipart.MultipartFile; +import javax.annotation.PostConstruct; + +import java.io.IOException; +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; +import java.util.UUID; + +@Component +@Slf4j +public class FileUploadUtil { + + @Autowired + private COSClient cosClient; + + @Autowired + private CosProperties cosProperties; + + @PostConstruct + public void init() { + log.info("COS配置初始化 - baseUrl: {}, cdnUrl: {}, cdnEnabled: {}", + cosProperties.getBaseUrl(), cosProperties.getCdnUrl(), cosProperties.getCdnEnabled()); + } + + /** + * 上传文件到腾讯云COS + */ + public String uploadFile(MultipartFile file) throws IOException { + if (file.isEmpty()) { + throw new IllegalArgumentException("文件不能为空"); + } + + try { + // 生成文件名 + String originalFilename = file.getOriginalFilename(); + String extension = ""; + if (originalFilename != null && originalFilename.contains(".")) { + extension = originalFilename.substring(originalFilename.lastIndexOf(".")); + } + String fileName = UUID.randomUUID().toString() + extension; + + // 按日期分目录 + String dateDir = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy/MM/dd")); + String key = "uploads/" + dateDir + "/" + fileName; + + // 设置文件元数据 + ObjectMetadata metadata = new ObjectMetadata(); + metadata.setContentLength(file.getSize()); + metadata.setContentType(file.getContentType()); + + // 上传文件 + PutObjectRequest putObjectRequest = new PutObjectRequest( + cosProperties.getBucket(), + key, + file.getInputStream(), + metadata + ); + + PutObjectResult result = cosClient.putObject(putObjectRequest); + log.info("文件上传成功: {}, ETag: {}", key, result.getETag()); + + // 返回访问URL + String finalUrl = cosProperties.getActualUrl() + "/" + key; + log.info("最终返回的图片URL: {}", finalUrl); + log.info("CDN配置 - baseUrl: {}, cdnUrl: {}, cdnEnabled: {}", + cosProperties.getBaseUrl(), cosProperties.getCdnUrl(), cosProperties.getCdnEnabled()); + return finalUrl; + + } catch (Exception e) { + log.error("文件上传失败: {}", e.getMessage(), e); + throw new IOException("文件上传失败: " + e.getMessage()); + } + } + + /** + * 删除文件 + */ + public boolean deleteFile(String fileUrl) { + try { + if (fileUrl == null || !fileUrl.startsWith(cosProperties.getActualUrl())) { + return false; + } + + // 提取文件key + String key = fileUrl.replace(cosProperties.getActualUrl() + "/", ""); + + // 删除文件 + cosClient.deleteObject(cosProperties.getBucket(), key); + log.info("文件删除成功: {}", key); + return true; + + } catch (Exception e) { + log.error("文件删除失败: {}", e.getMessage(), e); + return false; + } + } +} diff --git a/backend/src/main/java/com/sunnyfarm/util/JwtUtil.java b/backend/src/main/java/com/sunnyfarm/util/JwtUtil.java new file mode 100644 index 0000000..9c5941b --- /dev/null +++ b/backend/src/main/java/com/sunnyfarm/util/JwtUtil.java @@ -0,0 +1,148 @@ +package com.sunnyfarm.util; + +import io.jsonwebtoken.Claims; +import io.jsonwebtoken.Jwts; +import io.jsonwebtoken.SignatureAlgorithm; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; + +import java.util.Date; +import java.util.HashMap; +import java.util.Map; + +@Component +@Slf4j +public class JwtUtil { + + @Value("${jwt.secret:sunnyfarm-agricultural-product-platform-secret-key}") + private String secret; + + @Value("${jwt.expiration:86400}") + private Long expiration; + + /** + * 生成JWT token + */ + public String generateToken(Long userId, String username) { + Map claims = new HashMap<>(); + claims.put("userId", userId); + claims.put("username", username); + return createToken(claims, username); + } + + /** + * 生成商家JWT token + */ + public String generateMerchantToken(Long userId, String username, Long merchantId) { + Map claims = new HashMap<>(); + claims.put("userId", userId); + claims.put("username", username); + claims.put("merchantId", merchantId); + claims.put("userType", "merchant"); + return createToken(claims, username); + } + + /** + * 生成管理员JWT token + */ + public String generateAdminToken(Long adminId, String username) { + Map claims = new HashMap<>(); + claims.put("adminId", adminId); + claims.put("username", username); + claims.put("userType", "admin"); + return createToken(claims, username); + } + + /** + * 创建token + */ + private String createToken(Map claims, String subject) { + return Jwts.builder() + .setClaims(claims) + .setSubject(subject) + .setIssuedAt(new Date(System.currentTimeMillis())) + .setExpiration(new Date(System.currentTimeMillis() + expiration * 1000)) + .signWith(SignatureAlgorithm.HS256, secret) + .compact(); + } + + /** + * 从token中获取用户名 + */ + public String getUsernameFromToken(String token) { + return getClaimFromToken(token, Claims::getSubject); + } + + /** + * 从token中获取用户ID + */ + public Long getUserIdFromToken(String token) { + Claims claims = getAllClaimsFromToken(token); + return Long.valueOf(claims.get("userId").toString()); + } + + /** + * 从token中获取用户类型 + */ + public String getUserTypeFromToken(String token) { + Claims claims = getAllClaimsFromToken(token); + return claims.get("userType") != null ? claims.get("userType").toString() : "user"; + } + + /** + * 从token中获取管理员ID + */ + public Long getAdminIdFromToken(String token) { + Claims claims = getAllClaimsFromToken(token); + Object adminId = claims.get("adminId"); + return adminId != null ? Long.valueOf(adminId.toString()) : null; + } + + /** + * 从token中获取商家ID + */ + public Long getMerchantIdFromToken(String token) { + Claims claims = getAllClaimsFromToken(token); + Object merchantId = claims.get("merchantId"); + return merchantId != null ? Long.valueOf(merchantId.toString()) : null; + } + + /** + * 从token中获取过期时间 + */ + public Date getExpirationDateFromToken(String token) { + return getClaimFromToken(token, Claims::getExpiration); + } + + /** + * 从token中获取指定claim + */ + public T getClaimFromToken(String token, java.util.function.Function claimsResolver) { + final Claims claims = getAllClaimsFromToken(token); + return claimsResolver.apply(claims); + } + + /** + * 从token中获取所有claims + */ + private Claims getAllClaimsFromToken(String token) { + return Jwts.parser().setSigningKey(secret).parseClaimsJws(token).getBody(); + } + + /** + * 检查token是否过期 + */ + public Boolean isTokenExpired(String token) { + final Date expiration = getExpirationDateFromToken(token); + return expiration.before(new Date()); + } + + /** + * 验证token + */ + public Boolean validateToken(String token, String username) { + final String tokenUsername = getUsernameFromToken(token); + return (tokenUsername.equals(username) && !isTokenExpired(token)); + } +} diff --git a/backend/src/main/java/com/sunnyfarm/util/OrderNoUtil.java b/backend/src/main/java/com/sunnyfarm/util/OrderNoUtil.java new file mode 100644 index 0000000..8a3378d --- /dev/null +++ b/backend/src/main/java/com/sunnyfarm/util/OrderNoUtil.java @@ -0,0 +1,40 @@ +package com.sunnyfarm.util; + +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; +import java.util.concurrent.atomic.AtomicInteger; + +public class OrderNoUtil { + + private static final AtomicInteger counter = new AtomicInteger(0); + + /** + * 生成订单号 + * 格式: O + 时间戳(yyyyMMddHHmmss) + 4位递增序列 + */ + public static String generateOrderNo() { + String timestamp = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMddHHmmss")); + int sequence = counter.incrementAndGet() % 10000; + return String.format("O%s%04d", timestamp, sequence); + } + + /** + * 生成支付单号 + * 格式: P + 时间戳(yyyyMMddHHmmss) + 4位递增序列 + */ + public static String generatePaymentNo() { + String timestamp = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMddHHmmss")); + int sequence = counter.incrementAndGet() % 10000; + return String.format("P%s%04d", timestamp, sequence); + } + + /** + * 生成退款单号 + * 格式: R + 时间戳(yyyyMMddHHmmss) + 4位递增序列 + */ + public static String generateRefundNo() { + String timestamp = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMddHHmmss")); + int sequence = counter.incrementAndGet() % 10000; + return String.format("R%s%04d", timestamp, sequence); + } +} diff --git a/backend/src/main/java/com/sunnyfarm/util/PasswordUtil.java b/backend/src/main/java/com/sunnyfarm/util/PasswordUtil.java new file mode 100644 index 0000000..5d21e40 --- /dev/null +++ b/backend/src/main/java/com/sunnyfarm/util/PasswordUtil.java @@ -0,0 +1,22 @@ +package com.sunnyfarm.util; + +import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; + +public class PasswordUtil { + + private static final BCryptPasswordEncoder encoder = new BCryptPasswordEncoder(); + + /** + * 加密密码 + */ + public static String encode(String password) { + return encoder.encode(password); + } + + /** + * 验证密码 + */ + public static boolean matches(String rawPassword, String encodedPassword) { + return encoder.matches(rawPassword, encodedPassword); + } +} diff --git a/backend/src/main/java/com/sunnyfarm/util/RedisUtil.java b/backend/src/main/java/com/sunnyfarm/util/RedisUtil.java new file mode 100644 index 0000000..4d979c9 --- /dev/null +++ b/backend/src/main/java/com/sunnyfarm/util/RedisUtil.java @@ -0,0 +1,70 @@ +package com.sunnyfarm.util; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.redis.core.StringRedisTemplate; +import org.springframework.stereotype.Component; + +import java.util.concurrent.TimeUnit; + +@Component +public class RedisUtil { + + @Autowired + private StringRedisTemplate stringRedisTemplate; + + /** + * 设置缓存 + */ + public void set(String key, String value) { + stringRedisTemplate.opsForValue().set(key, value); + } + + /** + * 设置缓存并指定过期时间(秒) + */ + public void set(String key, String value, long timeout) { + stringRedisTemplate.opsForValue().set(key, value, timeout, TimeUnit.SECONDS); + } + + /** + * 设置缓存并指定过期时间 + */ + public void setEx(String key, String value, long timeout, TimeUnit unit) { + stringRedisTemplate.opsForValue().set(key, value, timeout, unit); + } + + /** + * 获取缓存 + */ + public Object get(String key) { + return stringRedisTemplate.opsForValue().get(key); + } + + /** + * 删除缓存 + */ + public void delete(String key) { + stringRedisTemplate.delete(key); + } + + /** + * 删除缓存 - 别名方法 + */ + public void del(String key) { + delete(key); + } + + /** + * 检查key是否存在 + */ + public boolean exists(String key) { + return Boolean.TRUE.equals(stringRedisTemplate.hasKey(key)); + } + + /** + * 设置过期时间 + */ + public boolean expire(String key, long timeout, TimeUnit unit) { + return Boolean.TRUE.equals(stringRedisTemplate.expire(key, timeout, unit)); + } +} diff --git a/backend/src/main/java/com/sunnyfarm/util/UserContext.java b/backend/src/main/java/com/sunnyfarm/util/UserContext.java new file mode 100644 index 0000000..1a4f592 --- /dev/null +++ b/backend/src/main/java/com/sunnyfarm/util/UserContext.java @@ -0,0 +1,48 @@ +package com.sunnyfarm.util; + +public class UserContext { + + private static final ThreadLocal currentUserId = new ThreadLocal<>(); + private static final ThreadLocal currentUsername = new ThreadLocal<>(); + private static final ThreadLocal userType = new ThreadLocal<>(); + private static final ThreadLocal merchantId = new ThreadLocal<>(); + + public static void setCurrentUserId(Long userId) { + currentUserId.set(userId); + } + + public static Long getCurrentUserId() { + return currentUserId.get(); + } + + public static void setCurrentUsername(String username) { + currentUsername.set(username); + } + + public static String getCurrentUsername() { + return currentUsername.get(); + } + + public static void setUserType(String type) { + userType.set(type); + } + + public static String getUserType() { + return userType.get(); + } + + public static void setMerchantId(Long id) { + merchantId.set(id); + } + + public static Long getMerchantId() { + return merchantId.get(); + } + + public static void clear() { + currentUserId.remove(); + currentUsername.remove(); + userType.remove(); + merchantId.remove(); + } +} diff --git a/backend/src/main/resources/application-dev.yml.example b/backend/src/main/resources/application-dev.yml.example new file mode 100644 index 0000000..f9fd95a --- /dev/null +++ b/backend/src/main/resources/application-dev.yml.example @@ -0,0 +1,97 @@ +server: + port: 8080 + +spring: + datasource: + url: jdbc:mysql://your-mysql-host:3306/sunnyfarm?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai&useSSL=false&allowPublicKeyRetrieval=true + username: your-db-username + password: your-db-password + driver-class-name: com.mysql.cj.jdbc.Driver + hikari: + maximum-pool-size: 10 + minimum-idle: 5 + connection-timeout: 60000 + idle-timeout: 600000 + max-lifetime: 1800000 + + redis: + host: your-redis-host + port: 6379 + password: your-redis-password + database: 0 + timeout: 10000ms + lettuce: + pool: + max-active: 8 + max-wait: -1ms + max-idle: 8 + min-idle: 0 + + mail: + host: smtp.qq.com + port: 465 + username: your-email@qq.com + password: your-email-auth-code + protocol: smtp + default-encoding: UTF-8 + properties: + mail: + smtp: + auth: true + ssl: + enable: true + required: true + socketFactory: + class: javax.net.ssl.SSLSocketFactory + fallback: false + port: 465 + timeout: 25000 + connectiontimeout: 25000 + writetimeout: 25000 + + servlet: + multipart: + max-file-size: 10MB + max-request-size: 10MB + +mybatis-plus: + configuration: + log-impl: org.apache.ibatis.logging.stdout.StdOutImpl + global-config: + db-config: + id-type: auto + +# 腾讯云COS配置 +tencent: + cos: + secret-id: your-tencent-cos-secret-id + secret-key: your-tencent-cos-secret-key + region: ap-guangzhou + bucket: your-bucket-name + base-url: https://your-bucket-name.cos.ap-guangzhou.myqcloud.com + cdn-url: https://your-cdn-domain.com + cdn-enabled: true + +# 邮件配置 +email: + from: your-email@qq.com + from-name: 农产品直销平台 + +# JWT配置 +jwt: + secret: your-jwt-secret-key + expiration: 86400000 + +# 支付宝配置(沙箱环境) +alipay: + app-id: your-alipay-app-id + app-private-key: | + your-alipay-app-private-key + alipay-public-key: | + your-alipay-public-key + server-url: https://openapi-sandbox.dl.alipaydev.com/gateway.do + format: json + charset: UTF-8 + sign-type: RSA2 + notify-url: http://your-domain-or-ip:8080/api/payment/alipay/notify + return-url: http://your-domain-or-ip:3000/orders?payment=success diff --git a/backend/src/main/resources/application-prod.yml.example b/backend/src/main/resources/application-prod.yml.example new file mode 100644 index 0000000..3afb83f --- /dev/null +++ b/backend/src/main/resources/application-prod.yml.example @@ -0,0 +1,100 @@ +spring: + datasource: + driver-class-name: com.mysql.cj.jdbc.Driver + url: jdbc:mysql://your-mysql-host:3306/sunnyfarm?useUnicode=true&characterEncoding=utf8&useSSL=true&serverTimezone=GMT%2B8 + username: your-db-username + password: your-db-password + hikari: + minimum-idle: 10 + maximum-pool-size: 50 + connection-timeout: 30000 + idle-timeout: 600000 + max-lifetime: 1800000 + connection-test-query: SELECT 1 + + redis: + host: your-redis-host + port: 6379 + password: your-redis-password + database: 0 + timeout: 5000 + lettuce: + pool: + max-active: 20 + max-idle: 10 + min-idle: 5 + max-wait: -1 + + mail: + host: smtp.qq.com + port: 465 + username: your-email@qq.com + password: your-email-auth-code + protocol: smtp + default-encoding: UTF-8 + properties: + mail: + smtp: + auth: true + ssl: + enable: true + required: true + socketFactory: + class: javax.net.ssl.SSLSocketFactory + fallback: false + port: 465 + timeout: 25000 + connectiontimeout: 25000 + writetimeout: 25000 + +# 腾讯云COS配置 +tencent: + cos: + secret-id: your-tencent-cos-secret-id + secret-key: your-tencent-cos-secret-key + region: ap-guangzhou + bucket: your-bucket-name + base-url: https://your-bucket-name.cos.ap-guangzhou.myqcloud.com + cdn-url: https://your-cdn-domain.com + cdn-enabled: true + +# 邮件配置 +email: + from: your-email@qq.com + from-name: 农产品直销平台 + +# JWT配置 +jwt: + secret: your-jwt-secret-key + expiration: 86400000 + +# 支付宝配置(生产环境请更换为正式环境) +alipay: + app-id: your-alipay-app-id + app-private-key: | + your-alipay-app-private-key + alipay-public-key: | + your-alipay-public-key + server-url: https://openapi.alipay.com/gateway.do + format: json + charset: UTF-8 + sign-type: RSA2 + notify-url: https://your-domain/api/payment/alipay/notify + return-url: https://your-domain/orders?payment=success + +logging: + level: + com.sunnyfarm: info + org.springframework.security: warn + file: + path: /app/logs/ + name: sunny-farm.log + logback: + rollingpolicy: + max-file-size: 100MB + max-history: 30 + +server: + port: 8080 + +debug: false diff --git a/backend/src/main/resources/application-prod.yml.template b/backend/src/main/resources/application-prod.yml.template new file mode 100644 index 0000000..1965079 --- /dev/null +++ b/backend/src/main/resources/application-prod.yml.template @@ -0,0 +1,97 @@ +server: + port: 8080 + +spring: + datasource: + url: jdbc:mysql://119.91.236.167:3306/sunnyfarm?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai&useSSL=false&allowPublicKeyRetrieval=true + username: SunnyFarm + password: SunnyFarm1234 + driver-class-name: com.mysql.cj.jdbc.Driver + hikari: + maximum-pool-size: 10 + minimum-idle: 5 + connection-timeout: 60000 + idle-timeout: 600000 + max-lifetime: 1800000 + + redis: + host: 119.91.236.167 + port: 6379 + password: SunnyFarm + database: 0 + timeout: 10000ms + lettuce: + pool: + max-active: 8 + max-wait: -1ms + max-idle: 8 + min-idle: 0 + + mail: + host: smtp.qq.com + port: 465 + username: 852326703@qq.com + password: pqnzulxlomsqbedh + protocol: smtp + default-encoding: UTF-8 + properties: + mail: + smtp: + auth: true + ssl: + enable: true + required: true + socketFactory: + class: javax.net.ssl.SSLSocketFactory + fallback: false + port: 465 + timeout: 25000 + connectiontimeout: 25000 + writetimeout: 25000 + + servlet: + multipart: + max-file-size: 10MB + max-request-size: 10MB + +mybatis-plus: + configuration: + log-impl: org.apache.ibatis.logging.stdout.StdOutImpl + global-config: + db-config: + id-type: auto + +# 腾讯云COS配置 +tencent: + cos: + secret-id: AKIDWu3xbz7zbw1qpeDWZLs99tMYUAZiaBVZ + secret-key: qQjlX2GEvMWQ3PUIq77qIUP3RZQ0KBtL + region: ap-guangzhou + bucket: sunnyfarm-1328510989 + + base-url: https://sunnyfarm-1328510989.cos.ap-guangzhou.myqcloud.com + cdn-url: https://cdnsunnyfarm.sqai.online + cdn-enabled: true +# 邮件配置 +email: + from: 852326703@qq.com + from-name: 农产品直销平台 + +# JWT配置 +jwt: + secret: sunnyfarm-jwt-secret-key-2023 + expiration: 86400000 + +# 支付宝配置(使用YAML多行字符串格式) +alipay: + app-id: 9021000151645066 + app-private-key: | + MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCvO5yhhOMk+CMo30ZuGEfA/ejrz4urgyTAuVMUSWZaUIphEr8PtaRyiQ0eXT44d493tTDAM6cdAh3mbRK7ZHZ3dmomXS+rBEUZlqFRRh6G4+BCC51FOPjUQK0Hfc2FLqzVO4vRP1dUy7RD73ZrCtvXivzC4xbSaJ10gf3kgRmLEc7Vk4Q4c3J/ZnUVSM2Ol/0UUaBdb3wQ6nMl3dXNBr6AJtkXHkUEhYVjyHrvZdEuHp3HNXuk1uav6O3/0yeZLMwokXz+ZCknleKR7jKIy4/XgE+Lxwh+Q86ZJ1lh7F1wklElIxY1FDmi9iXwimnL1RKmeNmdTKhvmwhhqzxdAgpnAgMBAAECggEAO9F5spmOgLxEJFuzyzl2pScIMBd1cmMeVLvu5Gn6CrKbym9/0XkMXhzy4X6rEtOdohu0PKhlT729Pz/UuSueVUGTEu2UrCiU/hsxjnEFUr7PezxDv7GnH63EY15KnkBEt7XQEOoygbvYGajRH9FhIr9Lcavbyo/z0CXf6fU4JwSnd6W9y98VL1Cs0H+inz1N2MuwPA7BBXvbLFkZ44AAER/lJCclDKBjLXLGHJpuOQnfhgzkV8u6218+pD7s1ZpV4CL0XzjK8AI0DC5klA44fHUjy22m0x3WKkadeRHfCw67gUR23VbFj9sSclKhqg7jSaUm05sUY4EMN6IU+bRZoQKBgQDwWFJ1KZ/s9NvMP27bHHWSGQEZc9ndkif3F9w/i1S35AYxpT7QW9wKrW3mKWi+crBrkd/DlJQdskrmawLO/xYZAMtWyylU0DpXwy7L6Mxi5Nb8UWg+AZkG4DOQLCNLTU1v8Ri11UllbmOIESPmiLWlkEnjPGQOs2vDa0Up8FSnGwKBgQC6pZCjkWb8PrkGSY5625fDetD+7Z+45xAbBEysIObCcXgNPXYTcVqGl4b8CoCWj2WtGw6nfIJ3/EQQyYZSO88Syh20S7RIRXKn+4ewMpIkRDUndsWo1g0g4lIfuMp9DKM7MKdfBrPFzZRgHGbJmuf7PzCPHAAsRBBUvvFwcQ5ipQKBgQDLt0ElF8P7N8w5wiZ3SBcLX3BEH6MxoBmJ4cqIfdOwDnGFkPe96a6HylpVdRHYIyQP+VP91aj5xs6foWJ/C02yoa64gXkl710UWFcI9OiQhkEHGwVNUVNgtZZfBFpiEEKruGJmdUNB2yA4C7cvRv8YN0W5es2gfEnCGOFF2/QUswKBgGSxRWK44rJatD7sF+234hwXegDN+UkrfsjUfPivl053IKkj6zt5/7h0ruHyWIThP50v56v7w/cvJRlZXxmmIoSkfXnzDYOf08dEL2OjJKxkD7kGnoQDVnnTHzL14mesFbOs/96IIxwnWFCxgdJpu5UT9gJ+vKkk3xqZGG/szjHxAoGAe0h56LuqVO86AYshFSVKdazwnQDAhw4M54Ec6ajw89OzwEXb9kCs2Q1xlOkpMjSH0KuJz30fvpgi5AtPWXsq0zx/qkDcPw0CHolHZV8P6Q1bzdFzmfgpzLwmZEjOjimp7t+O6nhwhHJVjAIQZkGdyY5LM9yICXdId20UbtC+qsE= + alipay-public-key: | + MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEArS2Uhlj9MPTvEFwTMhI75Os1KDd9q/ax28s1es9mjNV+lfl4nnkjkBRGJsTDrd4KUwpMCRZ0ktfymNhPNO55dFD4AkOMw0Wvjtq3Dn9yWtPn9zmUbi5w+yoE6Xmqp3QnpfBnfDX7fxu8/a9oPL9h6r1z9hbdTcbRJU2BWHxLwnZTDbdvB4h1oBDVDU9xbR5mEEm7wrAwKSVv9trt54ZI+SEOS6OemEQZ1xL+wPPUZChVAhsq30d26LJ14htCXDMxNT0ndhOQb+SHljDML8H7mIByQ6/kePR9uVj1Mmcb75VM53BGyzbOuC8vWet0NUdLAyLJUBALIcPRrljpw0F+9wIDAQAB + server-url: https://openapi-sandbox.dl.alipaydev.com/gateway.do + format: json + charset: UTF-8 + sign-type: RSA2 + notify-url: http://localhost:8080/api/payment/alipay/notify + return-url: http://localhost:3000/orders?payment=success diff --git a/backend/src/main/resources/application.yml b/backend/src/main/resources/application.yml new file mode 100644 index 0000000..70debb6 --- /dev/null +++ b/backend/src/main/resources/application.yml @@ -0,0 +1,5 @@ +spring: + profiles: + active: dev + application: + name: sunny-farm diff --git a/backend/src/main/resources/mapper/AdminMapper.xml b/backend/src/main/resources/mapper/AdminMapper.xml new file mode 100644 index 0000000..f9d9280 --- /dev/null +++ b/backend/src/main/resources/mapper/AdminMapper.xml @@ -0,0 +1,45 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/backend/src/main/resources/mapper/CartMapper.xml b/backend/src/main/resources/mapper/CartMapper.xml new file mode 100644 index 0000000..5af59c0 --- /dev/null +++ b/backend/src/main/resources/mapper/CartMapper.xml @@ -0,0 +1,33 @@ + + + + + + + diff --git a/backend/src/main/resources/mapper/ChatMessageMapper.xml b/backend/src/main/resources/mapper/ChatMessageMapper.xml new file mode 100644 index 0000000..e526a94 --- /dev/null +++ b/backend/src/main/resources/mapper/ChatMessageMapper.xml @@ -0,0 +1,65 @@ + + + + + + + + + + + + + + + + + + + + + + + UPDATE chat_messages + SET is_read = 1 + WHERE session_id = #{sessionId} + AND sender_id != #{readerId} + AND is_read = 0 + + + + + UPDATE chat_messages + SET is_read = 1 + WHERE session_id = #{sessionId} + AND sender_type = #{senderType} + AND is_read = 0 + + + + + + diff --git a/backend/src/main/resources/mapper/ChatSessionMapper.xml b/backend/src/main/resources/mapper/ChatSessionMapper.xml new file mode 100644 index 0000000..f761075 --- /dev/null +++ b/backend/src/main/resources/mapper/ChatSessionMapper.xml @@ -0,0 +1,62 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/backend/src/main/resources/mapper/FavoriteMapper.xml b/backend/src/main/resources/mapper/FavoriteMapper.xml new file mode 100644 index 0000000..2ad772b --- /dev/null +++ b/backend/src/main/resources/mapper/FavoriteMapper.xml @@ -0,0 +1,52 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/backend/src/main/resources/mapper/InventoryMapper.xml b/backend/src/main/resources/mapper/InventoryMapper.xml new file mode 100644 index 0000000..b4795cc --- /dev/null +++ b/backend/src/main/resources/mapper/InventoryMapper.xml @@ -0,0 +1,132 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + SELECT + i.id as inventory_id, + p.id as product_id, + COALESCE(i.stock_quantity, 0) as stock_quantity, + COALESCE(i.warning_quantity, 10) as warning_quantity, + COALESCE(i.updated_at, p.created_at) as inventory_updated_at, + + p.name as product_name, + p.price as product_price, + p.status as product_status, + p.unit as product_unit, + p.description as product_description, + p.origin as product_origin, + p.sales_count, + p.view_count, + p.created_at as product_created_at, + p.category_id, + p.merchant_id, + + c.name as category_name, + + m.shop_name as merchant_name, + + (SELECT pi.image_url + FROM product_images pi + WHERE pi.product_id = p.id + AND pi.is_main = 1 + LIMIT 1) as main_image + + FROM products p + LEFT JOIN inventory i ON p.id = i.product_id + LEFT JOIN categories c ON p.category_id = c.id + LEFT JOIN merchants m ON p.merchant_id = m.id + + + + + + + + + + + + + + + + + DELETE FROM inventory WHERE product_id = #{productId} + + + + + INSERT INTO inventory (product_id, stock_quantity, warning_quantity, updated_at) + VALUES + + (#{item.productId}, #{item.stockQuantity}, #{item.warningQuantity}, #{item.updatedAt}) + + + + diff --git a/backend/src/main/resources/mapper/MerchantMapper.xml b/backend/src/main/resources/mapper/MerchantMapper.xml new file mode 100644 index 0000000..9a4d3ee --- /dev/null +++ b/backend/src/main/resources/mapper/MerchantMapper.xml @@ -0,0 +1,165 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/backend/src/main/resources/mapper/OrderDetailMapper.xml b/backend/src/main/resources/mapper/OrderDetailMapper.xml new file mode 100644 index 0000000..610c29a --- /dev/null +++ b/backend/src/main/resources/mapper/OrderDetailMapper.xml @@ -0,0 +1,36 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/backend/src/main/resources/mapper/PermissionMapper.xml b/backend/src/main/resources/mapper/PermissionMapper.xml new file mode 100644 index 0000000..9adbec0 --- /dev/null +++ b/backend/src/main/resources/mapper/PermissionMapper.xml @@ -0,0 +1,15 @@ + + + + + + + + diff --git a/backend/src/main/resources/mapper/RoleMapper.xml b/backend/src/main/resources/mapper/RoleMapper.xml new file mode 100644 index 0000000..edd6367 --- /dev/null +++ b/backend/src/main/resources/mapper/RoleMapper.xml @@ -0,0 +1,39 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/frontend/index.html b/frontend/index.html new file mode 100644 index 0000000..d68f012 --- /dev/null +++ b/frontend/index.html @@ -0,0 +1,12 @@ + + + + + + 农产品直销平台 + + +
+ + + diff --git a/frontend/package-lock.json b/frontend/package-lock.json new file mode 100644 index 0000000..3284746 --- /dev/null +++ b/frontend/package-lock.json @@ -0,0 +1,1371 @@ +{ + "name": "sunnyfarm-frontend", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "sunnyfarm-frontend", + "version": "1.0.0", + "dependencies": { + "axios": "^1.4.0", + "echarts": "^6.0.0", + "element-plus": "^2.3.8", + "vue": "^3.3.4", + "vue-router": "^4.2.4" + }, + "devDependencies": { + "@vitejs/plugin-vue": "^4.2.3", + "vite": "^4.4.5" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", + "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.27.1.tgz", + "integrity": "sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.0.tgz", + "integrity": "sha512-jVZGvOxOuNSsuQuLRTh13nU0AogFlw32w/MT+LV6D3sP5WdbW61E77RnkbaO2dUvmPAYrBDJXGn5gGS6tH4j8g==", + "license": "MIT", + "dependencies": { + "@babel/types": "^7.28.0" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/types": { + "version": "7.28.1", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.1.tgz", + "integrity": "sha512-x0LvFTekgSX+83TI28Y9wYPUfzrnl2aT5+5QLnO6v7mSJYtEEevuDRN0F0uSHRk1G1IWZC43o00Y0xDDrpBGPQ==", + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.27.1", + "@babel/helper-validator-identifier": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@ctrl/tinycolor": { + "version": "3.6.1", + "resolved": "https://registry.npmjs.org/@ctrl/tinycolor/-/tinycolor-3.6.1.tgz", + "integrity": "sha512-SITSV6aIXsuVNV3f3O0f2n/cgyEDWoSqtZMYiAmcsYHydcKrOz3gUxB/iXd/Qf08+IZX4KpgNbvUdMBmWz+kcA==", + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/@element-plus/icons-vue": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/@element-plus/icons-vue/-/icons-vue-2.3.1.tgz", + "integrity": "sha512-XxVUZv48RZAd87ucGS48jPf6pKu0yV5UCg9f4FFwtrYxXOwWuVJo6wOvSLKEoMQKjv8GsX/mhP6UsC1lRwbUWg==", + "license": "MIT", + "peerDependencies": { + "vue": "^3.2.0" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.18.20.tgz", + "integrity": "sha512-fyi7TDI/ijKKNZTUJAQqiG5T7YjJXgnzkURqmGj13C6dCqckZBLdl4h7bkhHt/t0WP+zO9/zwroDvANaOqO5Sw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.18.20.tgz", + "integrity": "sha512-Nz4rJcchGDtENV0eMKUNa6L12zz2zBDXuhj/Vjh18zGqB44Bi7MBMSXjgunJgjRhCmKOjnPuZp4Mb6OKqtMHLQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.18.20.tgz", + "integrity": "sha512-8GDdlePJA8D6zlZYJV/jnrRAi6rOiNaCC/JclcXpB+KIuvfBN4owLtgzY2bsxnx666XjJx2kDPUmnTtR8qKQUg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.18.20.tgz", + "integrity": "sha512-bxRHW5kHU38zS2lPTPOyuyTm+S+eobPUnTNkdJEfAddYgEcll4xkT8DB9d2008DtTbl7uJag2HuE5NZAZgnNEA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.18.20.tgz", + "integrity": "sha512-pc5gxlMDxzm513qPGbCbDukOdsGtKhfxD1zJKXjCCcU7ju50O7MeAZ8c4krSJcOIJGFR+qx21yMMVYwiQvyTyQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.18.20.tgz", + "integrity": "sha512-yqDQHy4QHevpMAaxhhIwYPMv1NECwOvIpGCZkECn8w2WFHXjEwrBn3CeNIYsibZ/iZEUemj++M26W3cNR5h+Tw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.18.20.tgz", + "integrity": "sha512-tgWRPPuQsd3RmBZwarGVHZQvtzfEBOreNuxEMKFcd5DaDn2PbBxfwLcj4+aenoh7ctXcbXmOQIn8HI6mCSw5MQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.18.20.tgz", + "integrity": "sha512-/5bHkMWnq1EgKr1V+Ybz3s1hWXok7mDFUMQ4cG10AfW3wL02PSZi5kFpYKrptDsgb2WAJIvRcDm+qIvXf/apvg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.18.20.tgz", + "integrity": "sha512-2YbscF+UL7SQAVIpnWvYwM+3LskyDmPhe31pE7/aoTMFKKzIc9lLbyGUpmmb8a8AixOL61sQ/mFh3jEjHYFvdA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.18.20.tgz", + "integrity": "sha512-P4etWwq6IsReT0E1KHU40bOnzMHoH73aXp96Fs8TIT6z9Hu8G6+0SHSw9i2isWrD2nbx2qo5yUqACgdfVGx7TA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.18.20.tgz", + "integrity": "sha512-nXW8nqBTrOpDLPgPY9uV+/1DjxoQ7DoB2N8eocyq8I9XuqJ7BiAMDMf9n1xZM9TgW0J8zrquIb/A7s3BJv7rjg==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.18.20.tgz", + "integrity": "sha512-d5NeaXZcHp8PzYy5VnXV3VSd2D328Zb+9dEq5HE6bw6+N86JVPExrA6O68OPwobntbNJ0pzCpUFZTo3w0GyetQ==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.18.20.tgz", + "integrity": "sha512-WHPyeScRNcmANnLQkq6AfyXRFr5D6N2sKgkFo2FqguP44Nw2eyDlbTdZwd9GYk98DZG9QItIiTlFLHJHjxP3FA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.18.20.tgz", + "integrity": "sha512-WSxo6h5ecI5XH34KC7w5veNnKkju3zBRLEQNY7mv5mtBmrP/MjNBCAlsM2u5hDBlS3NGcTQpoBvRzqBcRtpq1A==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.18.20.tgz", + "integrity": "sha512-+8231GMs3mAEth6Ja1iK0a1sQ3ohfcpzpRLH8uuc5/KVDFneH6jtAJLFGafpzpMRO6DzJ6AvXKze9LfFMrIHVQ==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.18.20.tgz", + "integrity": "sha512-UYqiqemphJcNsFEskc73jQ7B9jgwjWrSayxawS6UVFZGWrAAtkzjxSqnoclCXxWtfwLdzU+vTpcNYhpn43uP1w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.18.20.tgz", + "integrity": "sha512-iO1c++VP6xUBUmltHZoMtCUdPlnPGdBom6IrO4gyKPFFVBKioIImVooR5I83nTew5UOYrk3gIJhbZh8X44y06A==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.18.20.tgz", + "integrity": "sha512-e5e4YSsuQfX4cxcygw/UCPIEP6wbIL+se3sxPdCiMbFLBWu0eiZOJ7WoD+ptCLrmjZBK1Wk7I6D/I3NglUGOxg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.18.20.tgz", + "integrity": "sha512-kDbFRFp0YpTQVVrqUd5FTYmWo45zGaXe0X8E1G/LKFC0v8x0vWrhOWSLITcCn63lmZIxfOMXtCfti/RxN/0wnQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.18.20.tgz", + "integrity": "sha512-ddYFR6ItYgoaq4v4JmQQaAI5s7npztfV4Ag6NrhiaW0RrnOXqBkgwZLofVTlq1daVTQNhtI5oieTvkRPfZrePg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.18.20.tgz", + "integrity": "sha512-Wv7QBi3ID/rROT08SABTS7eV4hX26sVduqDOTe1MvGMjNd3EjOz4b7zeexIR62GTIEKrfJXKL9LFxTYgkyeu7g==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.18.20.tgz", + "integrity": "sha512-kTdfRcSiDfQca/y9QIkng02avJ+NCaQvrMejlsB3RRv5sE9rRoeBPISaZpKxHELzRxZyLvNts1P27W3wV+8geQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@floating-ui/core": { + "version": "1.7.2", + "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.7.2.tgz", + "integrity": "sha512-wNB5ooIKHQc+Kui96jE/n69rHFWAVoxn5CAzL1Xdd8FG03cgY3MLO+GF9U3W737fYDSgPWA6MReKhBQBop6Pcw==", + "license": "MIT", + "dependencies": { + "@floating-ui/utils": "^0.2.10" + } + }, + "node_modules/@floating-ui/dom": { + "version": "1.7.2", + "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.7.2.tgz", + "integrity": "sha512-7cfaOQuCS27HD7DX+6ib2OrnW+b4ZBwDNnCcT0uTyidcmyWb03FnQqJybDBoCnpdxwBSfA94UAYlRCt7mV+TbA==", + "license": "MIT", + "dependencies": { + "@floating-ui/core": "^1.7.2", + "@floating-ui/utils": "^0.2.10" + } + }, + "node_modules/@floating-ui/utils": { + "version": "0.2.10", + "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.10.tgz", + "integrity": "sha512-aGTxbpbg8/b5JfU1HXSrbH3wXZuLPJcNEcZQFMxLs3oSzgtVu6nFPkbbGGUvBcUjKV2YyB9Wxxabo+HEH9tcRQ==", + "license": "MIT" + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.4.tgz", + "integrity": "sha512-VT2+G1VQs/9oz078bLrYbecdZKs912zQlkelYpuf+SXF+QvZDYJlbx/LSx+meSAwdDFnF8FVXW92AVjjkVmgFw==", + "license": "MIT" + }, + "node_modules/@popperjs/core": { + "name": "@sxzz/popperjs-es", + "version": "2.11.7", + "resolved": "https://registry.npmjs.org/@sxzz/popperjs-es/-/popperjs-es-2.11.7.tgz", + "integrity": "sha512-Ccy0NlLkzr0Ex2FKvh2X+OyERHXJ88XJ1MXtsI9y9fGexlaXaVTPzBCRBwIxFkORuOb+uBqeu+RqnpgYTEZRUQ==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/popperjs" + } + }, + "node_modules/@types/lodash": { + "version": "4.17.20", + "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.17.20.tgz", + "integrity": "sha512-H3MHACvFUEiujabxhaI/ImO6gUrd8oOurg7LQtS7mbwIXA/cUqWrvBsaeJ23aZEPk1TAYkurjfMbSELfoCXlGA==", + "license": "MIT" + }, + "node_modules/@types/lodash-es": { + "version": "4.17.12", + "resolved": "https://registry.npmjs.org/@types/lodash-es/-/lodash-es-4.17.12.tgz", + "integrity": "sha512-0NgftHUcV4v34VhXm8QBSftKVXtbkBG3ViCjs6+eJ5a6y6Mi/jiFGPc1sC7QK+9BFhWrURE3EOggmWaSxL9OzQ==", + "license": "MIT", + "dependencies": { + "@types/lodash": "*" + } + }, + "node_modules/@types/web-bluetooth": { + "version": "0.0.16", + "resolved": "https://registry.npmjs.org/@types/web-bluetooth/-/web-bluetooth-0.0.16.tgz", + "integrity": "sha512-oh8q2Zc32S6gd/j50GowEjKLoOVOwHP/bWVjKJInBwQqdOYMdPrf1oVlelTlyfFK3CKxL1uahMDAr+vy8T7yMQ==", + "license": "MIT" + }, + "node_modules/@vitejs/plugin-vue": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-vue/-/plugin-vue-4.6.2.tgz", + "integrity": "sha512-kqf7SGFoG+80aZG6Pf+gsZIVvGSCKE98JbiWqcCV9cThtg91Jav0yvYFC9Zb+jKetNGF6ZKeoaxgZfND21fWKw==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "peerDependencies": { + "vite": "^4.0.0 || ^5.0.0", + "vue": "^3.2.25" + } + }, + "node_modules/@vue/compiler-core": { + "version": "3.5.17", + "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.5.17.tgz", + "integrity": "sha512-Xe+AittLbAyV0pabcN7cP7/BenRBNcteM4aSDCtRvGw0d9OL+HG1u/XHLY/kt1q4fyMeZYXyIYrsHuPSiDPosA==", + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.27.5", + "@vue/shared": "3.5.17", + "entities": "^4.5.0", + "estree-walker": "^2.0.2", + "source-map-js": "^1.2.1" + } + }, + "node_modules/@vue/compiler-dom": { + "version": "3.5.17", + "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.5.17.tgz", + "integrity": "sha512-+2UgfLKoaNLhgfhV5Ihnk6wB4ljyW1/7wUIog2puUqajiC29Lp5R/IKDdkebh9jTbTogTbsgB+OY9cEWzG95JQ==", + "license": "MIT", + "dependencies": { + "@vue/compiler-core": "3.5.17", + "@vue/shared": "3.5.17" + } + }, + "node_modules/@vue/compiler-sfc": { + "version": "3.5.17", + "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.5.17.tgz", + "integrity": "sha512-rQQxbRJMgTqwRugtjw0cnyQv9cP4/4BxWfTdRBkqsTfLOHWykLzbOc3C4GGzAmdMDxhzU/1Ija5bTjMVrddqww==", + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.27.5", + "@vue/compiler-core": "3.5.17", + "@vue/compiler-dom": "3.5.17", + "@vue/compiler-ssr": "3.5.17", + "@vue/shared": "3.5.17", + "estree-walker": "^2.0.2", + "magic-string": "^0.30.17", + "postcss": "^8.5.6", + "source-map-js": "^1.2.1" + } + }, + "node_modules/@vue/compiler-ssr": { + "version": "3.5.17", + "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.5.17.tgz", + "integrity": "sha512-hkDbA0Q20ZzGgpj5uZjb9rBzQtIHLS78mMilwrlpWk2Ep37DYntUz0PonQ6kr113vfOEdM+zTBuJDaceNIW0tQ==", + "license": "MIT", + "dependencies": { + "@vue/compiler-dom": "3.5.17", + "@vue/shared": "3.5.17" + } + }, + "node_modules/@vue/devtools-api": { + "version": "6.6.4", + "resolved": "https://registry.npmjs.org/@vue/devtools-api/-/devtools-api-6.6.4.tgz", + "integrity": "sha512-sGhTPMuXqZ1rVOk32RylztWkfXTRhuS7vgAKv0zjqk8gbsHkJ7xfFf+jbySxt7tWObEJwyKaHMikV/WGDiQm8g==", + "license": "MIT" + }, + "node_modules/@vue/reactivity": { + "version": "3.5.17", + "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.5.17.tgz", + "integrity": "sha512-l/rmw2STIscWi7SNJp708FK4Kofs97zc/5aEPQh4bOsReD/8ICuBcEmS7KGwDj5ODQLYWVN2lNibKJL1z5b+Lw==", + "license": "MIT", + "dependencies": { + "@vue/shared": "3.5.17" + } + }, + "node_modules/@vue/runtime-core": { + "version": "3.5.17", + "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.5.17.tgz", + "integrity": "sha512-QQLXa20dHg1R0ri4bjKeGFKEkJA7MMBxrKo2G+gJikmumRS7PTD4BOU9FKrDQWMKowz7frJJGqBffYMgQYS96Q==", + "license": "MIT", + "dependencies": { + "@vue/reactivity": "3.5.17", + "@vue/shared": "3.5.17" + } + }, + "node_modules/@vue/runtime-dom": { + "version": "3.5.17", + "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.5.17.tgz", + "integrity": "sha512-8El0M60TcwZ1QMz4/os2MdlQECgGoVHPuLnQBU3m9h3gdNRW9xRmI8iLS4t/22OQlOE6aJvNNlBiCzPHur4H9g==", + "license": "MIT", + "dependencies": { + "@vue/reactivity": "3.5.17", + "@vue/runtime-core": "3.5.17", + "@vue/shared": "3.5.17", + "csstype": "^3.1.3" + } + }, + "node_modules/@vue/server-renderer": { + "version": "3.5.17", + "resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.5.17.tgz", + "integrity": "sha512-BOHhm8HalujY6lmC3DbqF6uXN/K00uWiEeF22LfEsm9Q93XeJ/plHTepGwf6tqFcF7GA5oGSSAAUock3VvzaCA==", + "license": "MIT", + "dependencies": { + "@vue/compiler-ssr": "3.5.17", + "@vue/shared": "3.5.17" + }, + "peerDependencies": { + "vue": "3.5.17" + } + }, + "node_modules/@vue/shared": { + "version": "3.5.17", + "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.5.17.tgz", + "integrity": "sha512-CabR+UN630VnsJO/jHWYBC1YVXyMq94KKp6iF5MQgZJs5I8cmjw6oVMO1oDbtBkENSHSSn/UadWlW/OAgdmKrg==", + "license": "MIT" + }, + "node_modules/@vueuse/core": { + "version": "9.13.0", + "resolved": "https://registry.npmjs.org/@vueuse/core/-/core-9.13.0.tgz", + "integrity": "sha512-pujnclbeHWxxPRqXWmdkKV5OX4Wk4YeK7wusHqRwU0Q7EFusHoqNA/aPhB6KCh9hEqJkLAJo7bb0Lh9b+OIVzw==", + "license": "MIT", + "dependencies": { + "@types/web-bluetooth": "^0.0.16", + "@vueuse/metadata": "9.13.0", + "@vueuse/shared": "9.13.0", + "vue-demi": "*" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/@vueuse/core/node_modules/vue-demi": { + "version": "0.14.10", + "resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.14.10.tgz", + "integrity": "sha512-nMZBOwuzabUO0nLgIcc6rycZEebF6eeUfaiQx9+WSk8e29IbLvPU9feI6tqW4kTo3hvoYAJkMh8n8D0fuISphg==", + "hasInstallScript": true, + "license": "MIT", + "bin": { + "vue-demi-fix": "bin/vue-demi-fix.js", + "vue-demi-switch": "bin/vue-demi-switch.js" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + }, + "peerDependencies": { + "@vue/composition-api": "^1.0.0-rc.1", + "vue": "^3.0.0-0 || ^2.6.0" + }, + "peerDependenciesMeta": { + "@vue/composition-api": { + "optional": true + } + } + }, + "node_modules/@vueuse/metadata": { + "version": "9.13.0", + "resolved": "https://registry.npmjs.org/@vueuse/metadata/-/metadata-9.13.0.tgz", + "integrity": "sha512-gdU7TKNAUVlXXLbaF+ZCfte8BjRJQWPCa2J55+7/h+yDtzw3vOoGQDRXzI6pyKyo6bXFT5/QoPE4hAknExjRLQ==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/@vueuse/shared": { + "version": "9.13.0", + "resolved": "https://registry.npmjs.org/@vueuse/shared/-/shared-9.13.0.tgz", + "integrity": "sha512-UrnhU+Cnufu4S6JLCPZnkWh0WwZGUp72ktOF2DFptMlOs3TOdVv8xJN53zhHGARmVOsz5KqOls09+J1NR6sBKw==", + "license": "MIT", + "dependencies": { + "vue-demi": "*" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/@vueuse/shared/node_modules/vue-demi": { + "version": "0.14.10", + "resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.14.10.tgz", + "integrity": "sha512-nMZBOwuzabUO0nLgIcc6rycZEebF6eeUfaiQx9+WSk8e29IbLvPU9feI6tqW4kTo3hvoYAJkMh8n8D0fuISphg==", + "hasInstallScript": true, + "license": "MIT", + "bin": { + "vue-demi-fix": "bin/vue-demi-fix.js", + "vue-demi-switch": "bin/vue-demi-switch.js" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + }, + "peerDependencies": { + "@vue/composition-api": "^1.0.0-rc.1", + "vue": "^3.0.0-0 || ^2.6.0" + }, + "peerDependenciesMeta": { + "@vue/composition-api": { + "optional": true + } + } + }, + "node_modules/async-validator": { + "version": "4.2.5", + "resolved": "https://registry.npmjs.org/async-validator/-/async-validator-4.2.5.tgz", + "integrity": "sha512-7HhHjtERjqlNbZtqNqy2rckN/SpOOlmDliet+lP7k+eKZEjPk3DgyeU9lIXLdeLz0uBbbVp+9Qdow9wJWgwwfg==", + "license": "MIT" + }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "license": "MIT" + }, + "node_modules/axios": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.10.0.tgz", + "integrity": "sha512-/1xYAC4MP/HEG+3duIhFr4ZQXR4sQXOIe+o6sdqzeykGLx6Upp/1p8MHqhINOvGeP7xyNHe7tsiJByc4SSVUxw==", + "license": "MIT", + "dependencies": { + "follow-redirects": "^1.15.6", + "form-data": "^4.0.0", + "proxy-from-env": "^1.1.0" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "license": "MIT", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/csstype": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", + "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", + "license": "MIT" + }, + "node_modules/dayjs": { + "version": "1.11.13", + "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.13.tgz", + "integrity": "sha512-oaMBel6gjolK862uaPQOVTA7q3TZhuSvuMQAAglQDOWYO9A91IrAOUJEyKVlqJlHE0vq5p5UXxzdPfMH/x6xNg==", + "license": "MIT" + }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/echarts": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/echarts/-/echarts-6.0.0.tgz", + "integrity": "sha512-Tte/grDQRiETQP4xz3iZWSvoHrkCQtwqd6hs+mifXcjrCuo2iKWbajFObuLJVBlDIJlOzgQPd1hsaKt/3+OMkQ==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "2.3.0", + "zrender": "6.0.0" + } + }, + "node_modules/element-plus": { + "version": "2.10.4", + "resolved": "https://registry.npmjs.org/element-plus/-/element-plus-2.10.4.tgz", + "integrity": "sha512-UD4elWHrCnp1xlPhbXmVcaKFLCRaRAY6WWRwemGfGW3ceIjXm9fSYc9RNH3AiOEA6Ds1p9ZvhCs76CR9J8Vd+A==", + "license": "MIT", + "dependencies": { + "@ctrl/tinycolor": "^3.4.1", + "@element-plus/icons-vue": "^2.3.1", + "@floating-ui/dom": "^1.0.1", + "@popperjs/core": "npm:@sxzz/popperjs-es@^2.11.7", + "@types/lodash": "^4.14.182", + "@types/lodash-es": "^4.17.6", + "@vueuse/core": "^9.1.0", + "async-validator": "^4.2.5", + "dayjs": "^1.11.13", + "escape-html": "^1.0.3", + "lodash": "^4.17.21", + "lodash-es": "^4.17.21", + "lodash-unified": "^1.0.2", + "memoize-one": "^6.0.0", + "normalize-wheel-es": "^1.2.0" + }, + "peerDependencies": { + "vue": "^3.2.0" + } + }, + "node_modules/entities": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", + "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-object-atoms": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-set-tostringtag": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/esbuild": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.18.20.tgz", + "integrity": "sha512-ceqxoedUrcayh7Y7ZX6NdbbDzGROiyVBgC4PriJThBKSVPWnnFHZAkfI1lJT8QFkOwH4qOS2SJkS4wvpGl8BpA==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=12" + }, + "optionalDependencies": { + "@esbuild/android-arm": "0.18.20", + "@esbuild/android-arm64": "0.18.20", + "@esbuild/android-x64": "0.18.20", + "@esbuild/darwin-arm64": "0.18.20", + "@esbuild/darwin-x64": "0.18.20", + "@esbuild/freebsd-arm64": "0.18.20", + "@esbuild/freebsd-x64": "0.18.20", + "@esbuild/linux-arm": "0.18.20", + "@esbuild/linux-arm64": "0.18.20", + "@esbuild/linux-ia32": "0.18.20", + "@esbuild/linux-loong64": "0.18.20", + "@esbuild/linux-mips64el": "0.18.20", + "@esbuild/linux-ppc64": "0.18.20", + "@esbuild/linux-riscv64": "0.18.20", + "@esbuild/linux-s390x": "0.18.20", + "@esbuild/linux-x64": "0.18.20", + "@esbuild/netbsd-x64": "0.18.20", + "@esbuild/openbsd-x64": "0.18.20", + "@esbuild/sunos-x64": "0.18.20", + "@esbuild/win32-arm64": "0.18.20", + "@esbuild/win32-ia32": "0.18.20", + "@esbuild/win32-x64": "0.18.20" + } + }, + "node_modules/escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", + "license": "MIT" + }, + "node_modules/estree-walker": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", + "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", + "license": "MIT" + }, + "node_modules/follow-redirects": { + "version": "1.15.9", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.9.tgz", + "integrity": "sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "license": "MIT", + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, + "node_modules/form-data": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.3.tgz", + "integrity": "sha512-qsITQPfmvMOSAdeyZ+12I1c+CKSstAFAwu+97zrnWAbIr5u8wfsExUzCesVLC8NgHuRUqNN4Zy6UPWUTRGslcA==", + "license": "MIT", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", + "hasown": "^2.0.2", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "license": "MIT", + "dependencies": { + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", + "license": "MIT" + }, + "node_modules/lodash-es": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.21.tgz", + "integrity": "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==", + "license": "MIT" + }, + "node_modules/lodash-unified": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/lodash-unified/-/lodash-unified-1.0.3.tgz", + "integrity": "sha512-WK9qSozxXOD7ZJQlpSqOT+om2ZfcT4yO+03FuzAHD0wF6S0l0090LRPDx3vhTTLZ8cFKpBn+IOcVXK6qOcIlfQ==", + "license": "MIT", + "peerDependencies": { + "@types/lodash-es": "*", + "lodash": "*", + "lodash-es": "*" + } + }, + "node_modules/magic-string": { + "version": "0.30.17", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.17.tgz", + "integrity": "sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==", + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0" + } + }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/memoize-one": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/memoize-one/-/memoize-one-6.0.0.tgz", + "integrity": "sha512-rkpe71W0N0c0Xz6QD0eJETuWAJGnJ9afsl1srmwPrI+yBCkge5EycXXbYRyvL29zZVUWQCY7InPRCv3GDXuZNw==", + "license": "MIT" + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/nanoid": { + "version": "3.3.11", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/normalize-wheel-es": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/normalize-wheel-es/-/normalize-wheel-es-1.2.0.tgz", + "integrity": "sha512-Wj7+EJQ8mSuXr2iWfnujrimU35R2W4FAErEyTmJoJ7ucwTn2hOUSsRehMb5RSYkxXGTM7Y9QpvPmp++w5ftoJw==", + "license": "BSD-3-Clause" + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "license": "ISC" + }, + "node_modules/postcss": { + "version": "8.5.6", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", + "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.11", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", + "license": "MIT" + }, + "node_modules/rollup": { + "version": "3.29.5", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-3.29.5.tgz", + "integrity": "sha512-GVsDdsbJzzy4S/v3dqWPJ7EfvZJfCHiDqe80IyrF59LYuP+e6U1LJoUqeuqRbwAWoMNoXivMNeNAOf5E22VA1w==", + "dev": true, + "license": "MIT", + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=14.18.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/tslib": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.0.tgz", + "integrity": "sha512-N82ooyxVNm6h1riLCoyS9e3fuJ3AMG2zIZs2Gd1ATcSFjSA23Q0fzjjZeh0jbJvWVDZ0cJT8yaNNaaXHzueNjg==", + "license": "0BSD" + }, + "node_modules/vite": { + "version": "4.5.14", + "resolved": "https://registry.npmjs.org/vite/-/vite-4.5.14.tgz", + "integrity": "sha512-+v57oAaoYNnO3hIu5Z/tJRZjq5aHM2zDve9YZ8HngVHbhk66RStobhb1sqPMIPEleV6cNKYK4eGrAbE9Ulbl2g==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "^0.18.10", + "postcss": "^8.4.27", + "rollup": "^3.27.1" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + }, + "peerDependencies": { + "@types/node": ">= 14", + "less": "*", + "lightningcss": "^1.21.0", + "sass": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.4.0" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + } + } + }, + "node_modules/vue": { + "version": "3.5.17", + "resolved": "https://registry.npmjs.org/vue/-/vue-3.5.17.tgz", + "integrity": "sha512-LbHV3xPN9BeljML+Xctq4lbz2lVHCR6DtbpTf5XIO6gugpXUN49j2QQPcMj086r9+AkJ0FfUT8xjulKKBkkr9g==", + "license": "MIT", + "dependencies": { + "@vue/compiler-dom": "3.5.17", + "@vue/compiler-sfc": "3.5.17", + "@vue/runtime-dom": "3.5.17", + "@vue/server-renderer": "3.5.17", + "@vue/shared": "3.5.17" + }, + "peerDependencies": { + "typescript": "*" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/vue-router": { + "version": "4.5.1", + "resolved": "https://registry.npmjs.org/vue-router/-/vue-router-4.5.1.tgz", + "integrity": "sha512-ogAF3P97NPm8fJsE4by9dwSYtDwXIY1nFY9T6DyQnGHd1E2Da94w9JIolpe42LJGIl0DwOHBi8TcRPlPGwbTtw==", + "license": "MIT", + "dependencies": { + "@vue/devtools-api": "^6.6.4" + }, + "funding": { + "url": "https://github.com/sponsors/posva" + }, + "peerDependencies": { + "vue": "^3.2.0" + } + }, + "node_modules/zrender": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/zrender/-/zrender-6.0.0.tgz", + "integrity": "sha512-41dFXEEXuJpNecuUQq6JlbybmnHaqqpGlbH1yxnA5V9MMP4SbohSVZsJIwz+zdjQXSSlR1Vc34EgH1zxyTDvhg==", + "license": "BSD-3-Clause", + "dependencies": { + "tslib": "2.3.0" + } + } + } +} diff --git a/frontend/package.json b/frontend/package.json new file mode 100644 index 0000000..0bf278b --- /dev/null +++ b/frontend/package.json @@ -0,0 +1,21 @@ +{ + "name": "sunnyfarm-frontend", + "version": "1.0.0", + "type": "module", + "scripts": { + "dev": "vite", + "build": "vite build", + "preview": "vite preview" + }, + "dependencies": { + "axios": "^1.4.0", + "echarts": "^6.0.0", + "element-plus": "^2.3.8", + "vue": "^3.3.4", + "vue-router": "^4.2.4" + }, + "devDependencies": { + "@vitejs/plugin-vue": "^4.2.3", + "vite": "^4.4.5" + } +} diff --git a/frontend/public/alipay-logo.png b/frontend/public/alipay-logo.png new file mode 100644 index 0000000..cc35cc9 --- /dev/null +++ b/frontend/public/alipay-logo.png @@ -0,0 +1,4 @@ +iVBORw0KGgoAAAANSUhEUgAAABwAAAAcCAYAAABU1PscAAAACXBIWXMAAAsTAAALEwEAmpwYAAAArElEQVR4nO2VsQ3CMBRF38oEwCwYqQWm +2QyW2KpCkG5yW5F0lIiwZp3d4mK9yP8O7kP6Qqk2tC1wZrQOABYVJwP6N5bX6yQ3V3QhQnH4jCwqC8v5oQeCngKcZyJjYI3M3cRr9eCMgXb0cJ0y +w2Jb4qjU0c0s9jJkTnqC3WqI1bBf7QfC5sYc0FQ8VvJfQj2M3qfHqfKk8g3oGm7niWkQYFf7k4Q0BzHkqvCwQd9QxwJ2OeO0mX9iF7M7eZ0aQmYH +YQp1i3xXv5FQf6Q4c3o7vYhbpwAAAAASUVORK5CYII= diff --git a/frontend/public/alipay-logo.svg b/frontend/public/alipay-logo.svg new file mode 100644 index 0000000..4880b50 --- /dev/null +++ b/frontend/public/alipay-logo.svg @@ -0,0 +1,6 @@ + + + + A + diff --git a/frontend/public/brands/alipay.svg b/frontend/public/brands/alipay.svg new file mode 100644 index 0000000..10d98f3 --- /dev/null +++ b/frontend/public/brands/alipay.svg @@ -0,0 +1,5 @@ + + + + diff --git a/frontend/src/App.vue b/frontend/src/App.vue new file mode 100644 index 0000000..691c886 --- /dev/null +++ b/frontend/src/App.vue @@ -0,0 +1,28 @@ + + + + + diff --git a/frontend/src/api/address.js b/frontend/src/api/address.js new file mode 100644 index 0000000..0b9bac0 --- /dev/null +++ b/frontend/src/api/address.js @@ -0,0 +1,41 @@ +import request from '../utils/request' + +// 获取用户地址列表 +export const getAddressList = () => { + return request.get('/address/list') +} + +// 获取默认地址 +export const getDefaultAddress = () => { + return request.get('/address/default') +} + +// 获取地址详情 +export const getAddressDetail = (id) => { + return request.get(`/address/${id}`) +} + +// 创建地址 +export const createAddress = (data) => { + return request.post('/address/create', data) +} + +// 更新地址 +export const updateAddress = (id, data) => { + return request.put(`/address/${id}`, data) +} + +// 删除地址 +export const deleteAddress = (id) => { + return request.delete(`/address/${id}`) +} + +// 设置默认地址 +export const setDefaultAddress = (id) => { + return request.put(`/address/${id}/default`) +} + +// 获取地址数量 +export const getAddressCount = () => { + return request.get('/address/count') +} diff --git a/frontend/src/api/admin.js b/frontend/src/api/admin.js new file mode 100644 index 0000000..29cb6c5 --- /dev/null +++ b/frontend/src/api/admin.js @@ -0,0 +1,106 @@ +import request from '../utils/request' + +// 管理员登录 +export const adminLogin = (data) => { + return request.post('/admin/login', data) +} + +// 获取管理员信息 +export const getAdminProfile = (adminId) => { + return request.get('/admin/profile', { params: { adminId } }) +} + +// 管理员注销 +export const adminLogout = () => { + return request.post('/admin/logout') +} + +// 获取仪表盘统计数据 +export const getDashboardStats = () => { + return request.get('/admin/dashboard/stats') +} + +// 获取用户列表 +export const getUsers = (params) => { + return request.get('/admin/users', { params }) +} + +// 更新用户状态 +export const updateUserStatus = (id, status) => { + return request.put(`/admin/users/${id}/status`, null, { params: { status } }) +} + +// 获取商家列表 +export const getMerchants = (params) => { + return request.get('/admin/merchants', { params }) +} + +// 审核商家 +export const auditMerchant = (id, status) => { + return request.put(`/admin/merchants/${id}/audit`, null, { params: { status } }) +} + +// 获取商品列表 +export const getProducts = (params) => { + return request.get('/admin/products', { params }) +} + +// 审核商品 +export const auditProduct = (id, status) => { + return request.put(`/admin/products/${id}/audit`, null, { params: { status } }) +} + +// 获取订单列表 +export const getOrders = (params) => { + return request.get('/admin/orders', { params }) +} + +// 获取最新动态 +export const getRecentActivities = () => { + return request.get('/admin/activities') +} + +// 获取系统配置 +export const getSystemConfig = () => { + return request.get('/admin/config') +} + +// 更新系统配置 +export const updateSystemConfig = (data) => { + return request.put('/admin/config', data) +} + +// 获取公告列表 +export const getAnnouncements = (params) => { + return request.get('/admin/announcements', { params }) +} + +// 创建公告 +export const createAnnouncement = (data) => { + return request.post('/admin/announcements', data) +} + +// 更新公告 +export const updateAnnouncement = (id, data) => { + return request.put(`/admin/announcements/${id}`, data) +} + +// 删除公告 +export const deleteAnnouncement = (id) => { + return request.delete(`/admin/announcements/${id}`) +} + +// 获取系统日志 +export const getSystemLogs = (params) => { + return request.get('/admin/logs', { params }) +} + +// 更新管理员个人信息 +export const updateAdminProfile = (id, data) => { + return request.put(`/admin/profile/${id}`, data) +} + +// 修改管理员密码 +export const changeAdminPassword = (id, data) => { + return request.put(`/admin/profile/${id}/password`, data) +} diff --git a/frontend/src/api/announcement.js b/frontend/src/api/announcement.js new file mode 100644 index 0000000..fef6cfd --- /dev/null +++ b/frontend/src/api/announcement.js @@ -0,0 +1,33 @@ +import request from '../utils/request' + +// 获取已发布的公告列表 +export const getPublishedAnnouncements = (params) => { + return request.get('/announcements/published', { params }) +} + +// 获取最新公告 +export const getLatestAnnouncements = (params) => { + return request.get('/announcements/latest', { params }) +} + +// 获取公告详情 +export const getAnnouncementDetail = (id) => { + return request.get(`/announcements/${id}`) +} + +// 管理员接口 +export const getAnnouncements = (params) => { + return request.get('/api/admin/announcements', { params }) +} + +export const createAnnouncement = (data) => { + return request.post('/api/admin/announcements', data) +} + +export const updateAnnouncement = (id, data) => { + return request.put(`/api/admin/announcements/${id}`, data) +} + +export const deleteAnnouncement = (id) => { + return request.delete(`/api/admin/announcements/${id}`) +} diff --git a/frontend/src/api/cart.js b/frontend/src/api/cart.js new file mode 100644 index 0000000..f076492 --- /dev/null +++ b/frontend/src/api/cart.js @@ -0,0 +1,64 @@ +import request from '../utils/request' + +export default { + // 获取购物车列表 + getCartList() { + return request.get('/cart') + }, + + // 获取购物车详情列表 + getCartItems() { + return request.get('/cart/items') + }, + + // 添加到购物车 + addToCart(productId, quantity = 1) { + return request.post('/cart/add', { + productId, + quantity + }) + }, + + // 更新购物车数量 + updateQuantity(productId, quantity) { + return request.put(`/cart/${productId}/quantity`, { + quantity + }) + }, + + // 删除购物车商品 + removeFromCart(productId) { + return request.delete(`/cart/${productId}`) + }, + + // 批量删除 + batchRemove(productIds) { + return request.delete('/cart/batch', { + data: { productIds } + }) + }, + + // 清空购物车 + clearCart() { + return request.delete('/cart/clear') + }, + + // 获取购物车数量 + getCartCount() { + return request.get('/cart/count') + }, + + // 选择商品 + selectItem(productId, selected) { + return request.put(`/cart/${productId}/select`, { + selected + }) + }, + + // 全选/取消全选 + selectAll(selected) { + return request.put('/cart/select-all', { + selected + }) + } +} diff --git a/frontend/src/api/category.js b/frontend/src/api/category.js new file mode 100644 index 0000000..68c7d65 --- /dev/null +++ b/frontend/src/api/category.js @@ -0,0 +1,21 @@ +import request from '../utils/request' + +// 获取所有分类列表 +export const getCategoryList = () => { + return request.get('/category/list') +} + +// 获取主分类列表 +export const getMainCategories = () => { + return request.get('/category/main') +} + +// 获取分类树形结构 +export const getCategoryTree = () => { + return request.get('/category/tree') +} + +// 根据父ID获取子分类 +export const getChildCategories = (parentId) => { + return request.get(`/category/children/${parentId}`) +} diff --git a/frontend/src/api/chat.js b/frontend/src/api/chat.js new file mode 100644 index 0000000..91ef78c --- /dev/null +++ b/frontend/src/api/chat.js @@ -0,0 +1,45 @@ +import request from '../utils/request' + +// 用户获取聊天会话列表 +export const getUserSessions = () => { + return request.get('/chat/user/sessions') +} + +// 商家获取聊天会话列表 +export const getMerchantSessions = () => { + return request.get('/chat/merchant/sessions') +} + +// 创建或获取聊天会话 +export const createChatSession = (merchantId) => { + return request.post('/chat/session', null, { + params: { merchantId } + }) +} + +// 获取会话消息列表 +export const getSessionMessages = (sessionId, page = 1, size = 20) => { + return request.get(`/chat/session/${sessionId}/messages`, { + params: { page, size } + }) +} + +// 用户发送消息 +export const sendUserMessage = (data) => { + return request.post('/chat/user/send', data) +} + +// 商家发送消息 +export const sendMerchantMessage = (data) => { + return request.post('/chat/merchant/send', data) +} + +// 标记消息为已读 +export const markMessagesAsRead = (sessionId) => { + return request.put(`/chat/session/${sessionId}/read`) +} + +// 获取未读消息数量 +export const getUnreadCount = (sessionId) => { + return request.get(`/chat/session/${sessionId}/unread`) +} diff --git a/frontend/src/api/email.js b/frontend/src/api/email.js new file mode 100644 index 0000000..157727c --- /dev/null +++ b/frontend/src/api/email.js @@ -0,0 +1,11 @@ +import request from '../utils/request' + +// 发送邮箱验证码 +export const sendEmailCode = (email) => { + return request.post('/user/send-email-code', { email }) +} + +// 验证邮箱验证码 +export const verifyEmailCode = (email, code) => { + return request.post('/user/verify-email-code', { email, code }) +} diff --git a/frontend/src/api/favorite.js b/frontend/src/api/favorite.js new file mode 100644 index 0000000..848019e --- /dev/null +++ b/frontend/src/api/favorite.js @@ -0,0 +1,60 @@ +import request from '../utils/request' + +const favoriteApi = { + // 添加收藏 + addFavorite(productId) { + return request({ + url: '/favorite/add', + method: 'post', + data: { productId } + }) + }, + + // 取消收藏 + removeFavorite(productId) { + return request({ + url: '/favorite/remove', + method: 'delete', + data: { productId } + }) + }, + + // 检查是否已收藏 + checkFavorite(productId) { + return request({ + url: `/favorite/check/${productId}`, + method: 'get' + }) + }, + + // 获取收藏列表 + getFavoriteList(params = {}) { + return request({ + url: '/favorite/list', + method: 'get', + params: { + page: params.page || 1, + size: params.size || 10 + } + }) + }, + + // 获取收藏数量 + getFavoriteCount() { + return request({ + url: '/favorite/count', + method: 'get' + }) + }, + + // 批量取消收藏 + batchRemoveFavorite(productIds) { + return request({ + url: '/favorite/batch', + method: 'delete', + data: { productIds } + }) + } +} + +export default favoriteApi diff --git a/frontend/src/api/inventory.js b/frontend/src/api/inventory.js new file mode 100644 index 0000000..59d19fe --- /dev/null +++ b/frontend/src/api/inventory.js @@ -0,0 +1,53 @@ +import request from '../utils/request' + +// 获取库存统计 +export const getInventoryStats = () => { + return request.get('/inventory/stats') +} + +// 分页获取库存列表 +export const getInventoryPage = (params) => { + return request.get('/inventory/page', { params }) +} + +// 更新库存数量 +export const updateStock = (productId, data) => { + return request.put(`/inventory/${productId}/stock`, data) +} + +// 设置预警数量 +export const setWarningQuantity = (productId, data) => { + return request.put(`/inventory/${productId}/warning`, data) +} + +// 批量更新库存 +export const batchUpdateStock = (data) => { + return request.put('/inventory/batch', data) +} + +// ===== 为了兼容现有代码,保留旧的方法名 ===== + +// 获取库存列表(兼容方法) +export const getInventoryList = (params = {}) => { + return getInventoryPage(params) +} + +// 获取库存不足商品(兼容方法) +export const getLowStockProducts = () => { + return getInventoryStats().then(stats => { + return stats.lowStockProducts || [] + }) +} + +// 调整库存(兼容方法) +export const adjustStock = (data) => { + if (data.productId) { + return updateStock(data.productId, { quantity: data.quantity }) + } + return Promise.reject(new Error('缺少productId')) +} + +// 批量调整库存(兼容方法) +export const batchAdjustStock = (data) => { + return batchUpdateStock(data) +} diff --git a/frontend/src/api/merchant.js b/frontend/src/api/merchant.js new file mode 100644 index 0000000..4530012 --- /dev/null +++ b/frontend/src/api/merchant.js @@ -0,0 +1,36 @@ +import request from '../utils/request' + +// 商家注册 +export const merchantRegister = (data) => { + return request.post('/merchant/register', data) +} + +// 商家登录 +export const merchantLogin = (data) => { + return request.post('/merchant/login', data) +} + +// 获取商家信息 +export const getMerchantProfile = () => { + return request.get('/merchant/profile') +} + +// 更新商家信息 +export const updateMerchantProfile = (data) => { + return request.put('/merchant/profile', data) +} + +// 获取商家列表(管理员) +export const getMerchantList = (params) => { + return request.get('/merchant/list', { params }) +} + +// 审核商家(管理员) +export const verifyMerchant = (id, data) => { + return request.put(`/merchant/${id}/verify`, data) +} + +// 获取商家Dashboard数据 +export const getMerchantDashboard = () => { + return request.get('/merchant/dashboard') +} diff --git a/frontend/src/api/order.js b/frontend/src/api/order.js new file mode 100644 index 0000000..d052ef1 --- /dev/null +++ b/frontend/src/api/order.js @@ -0,0 +1,48 @@ +import request from '../utils/request' + +export default { + // 创建订单 + createOrder(data) { + return request.post('/order/create', data) + }, + + // 直接购买 + buyNow(data) { + return request.post('/order/buy-now', data) + }, + + // 获取用户订单列表 + getUserOrders(params) { + return request.get('/order/list', { params }) + }, + + // 获取订单详情 + getOrderDetail(orderId) { + return request.get(`/order/${orderId}`) + }, + + // 支付订单 + payOrder(orderId, payData) { + return request.post(`/order/${orderId}/pay`, payData) + }, + + // 取消订单 + cancelOrder(orderId, reason) { + return request.post(`/order/${orderId}/cancel`, { reason }) + }, + + // 确认收货 + confirmOrder(orderId) { + return request.post(`/order/${orderId}/confirm`) + }, + + // 申请退款 + applyRefund(orderId, reason) { + return request.post(`/order/${orderId}/refund`, { reason }) + }, + + // 删除订单 + deleteOrder(orderId) { + return request.delete(`/order/${orderId}`) + } +} diff --git a/frontend/src/api/payment.js b/frontend/src/api/payment.js new file mode 100644 index 0000000..a011f66 --- /dev/null +++ b/frontend/src/api/payment.js @@ -0,0 +1,35 @@ +import request from '@/utils/request' + +// 创建支付宝支付 +export function createAlipay(data) { + return request({ + url: '/payment/alipay/create', + method: 'post', + data + }) +} + +// 查询支付结果 +export function queryPayResult(paymentNo) { + return request({ + url: `/payment/query/${paymentNo}`, + method: 'get' + }) +} + +// 申请退款 +export function refund(data) { + return request({ + url: '/payment/refund', + method: 'post', + data + }) +} + +// 获取订单支付记录 +export function getOrderPayment(orderId) { + return request({ + url: `/payment/order/${orderId}`, + method: 'get' + }) +} diff --git a/frontend/src/api/product.js b/frontend/src/api/product.js new file mode 100644 index 0000000..244b13e --- /dev/null +++ b/frontend/src/api/product.js @@ -0,0 +1,74 @@ +import request from '../utils/request' + +// =================== 商家端接口 =================== +// 获取商家商品列表 +export const getProductList = (params) => { + return request.get('/merchant/products', { params }) +} + +// 创建商品 +export const createProduct = (data) => { + return request.post('/merchant/products', data) +} + +// 更新商品 +export const updateProduct = (id, data) => { + return request.put(`/merchant/products/${id}`, data) +} + +// 更新商品状态(专门用于上架/下架) +export const updateProductStatus = (id, status) => { + return request.put(`/merchant/products/${id}/status`, { status }) +} + +// 删除商品 +export const deleteProduct = (id) => { + return request.delete(`/merchant/products/${id}`) +} + +// 获取商品详情(商家端) +export const getMerchantProductDetail = (id) => { + return request.get(`/merchant/products/${id}`) +} + +// 上传商品图片 +export const uploadProductImage = (file) => { + const formData = new FormData() + formData.append('file', file) + return request.post('/merchant/products/upload/image', formData, { + headers: { + 'Content-Type': 'multipart/form-data' + } + }) +} + +// =================== 用户端公共接口 =================== +// 获取商品列表(用户端) +export const getPublicProductList = (params) => { + return request.get('/product/list', { params }) +} + +// 获取商品详情(用户端 - 公共接口) +export const getProductDetail = (id) => { + return request.get(`/product/${id}`) +} + +// 获取热门商品 +export const getHotProducts = (limit = 8) => { + return request.get('/product/hot', { params: { limit } }) +} + +// 获取新品推荐 +export const getNewProducts = (limit = 8) => { + return request.get('/product/new', { params: { limit } }) +} + +// 获取分类商品 +export const getCategoryProducts = (categoryId, params) => { + return request.get(`/product/category/${categoryId}`, { params }) +} + +// 搜索商品 +export const searchProducts = (params) => { + return request.get('/product/search', { params }) +} diff --git a/frontend/src/api/review.js b/frontend/src/api/review.js new file mode 100644 index 0000000..1c37317 --- /dev/null +++ b/frontend/src/api/review.js @@ -0,0 +1,53 @@ +import request from '../utils/request' + +// 创建评价 +export const createReview = (data) => { + return request.post('/comment/create', data) +} + +// 获取商品评价列表 +export const getProductReviews = (productId, params = {}) => { + return request.get(`/comment/product/${productId}`, { params }) +} + +// 获取用户评价列表 +export const getUserReviews = (params = {}) => { + return request.get('/comment/user', { params }) +} + +// 获取商品平均评分 +export const getProductRating = (productId) => { + return request.get(`/comment/product/${productId}/rating`) +} + +// 获取商品评价数量 +export const getProductReviewCount = (productId) => { + return request.get(`/comment/product/${productId}/count`) +} + +// 检查用户是否已评价 +export const checkUserReview = (productId, orderId) => { + return request.get('/comment/check', { + params: { productId, orderId } + }) +} + +// 管理员回复评价 +export const replyReview = (reviewId, reply) => { + return request.put(`/comment/${reviewId}/reply`, { reply }) +} + +// 管理员审核评价 +export const auditReview = (reviewId, status) => { + return request.put(`/comment/${reviewId}/audit`, { status }) +} + +// 删除评价 +export const deleteReview = (reviewId) => { + return request.delete(`/comment/${reviewId}`) +} + +// 获取所有评价(管理员) +export const getAllReviews = (params = {}) => { + return request.get('/comment/admin/list', { params }) +} diff --git a/frontend/src/api/user.js b/frontend/src/api/user.js new file mode 100644 index 0000000..398b3fa --- /dev/null +++ b/frontend/src/api/user.js @@ -0,0 +1,31 @@ +import request from '../utils/request' + +// 用户登录 +export const login = (data) => { + return request.post('/user/login', data) +} + +// 用户注册 +export const register = (data) => { + return request.post('/user/register', data) +} + +// 获取用户信息 +export const getUserProfile = () => { + return request.get('/user/profile') +} + +// 更新用户信息 +export const updateUserProfile = (data) => { + return request.put('/user/profile', data) +} + +// 修改密码 +export const changePassword = (data) => { + return request.put('/user/password', data) +} + +// 用户注销 +export const logout = () => { + return request.post('/user/logout') +} diff --git a/frontend/src/components/AnnouncementModal.vue b/frontend/src/components/AnnouncementModal.vue new file mode 100644 index 0000000..63a3098 --- /dev/null +++ b/frontend/src/components/AnnouncementModal.vue @@ -0,0 +1,992 @@ + + + + + diff --git a/frontend/src/components/ChatWindow.vue b/frontend/src/components/ChatWindow.vue new file mode 100644 index 0000000..f725074 --- /dev/null +++ b/frontend/src/components/ChatWindow.vue @@ -0,0 +1,390 @@ + + + diff --git a/frontend/src/components/ReviewModal.vue b/frontend/src/components/ReviewModal.vue new file mode 100644 index 0000000..5a3c66c --- /dev/null +++ b/frontend/src/components/ReviewModal.vue @@ -0,0 +1,206 @@ + + + + + diff --git a/frontend/src/components/RoleSelector.vue b/frontend/src/components/RoleSelector.vue new file mode 100644 index 0000000..b5ddba9 --- /dev/null +++ b/frontend/src/components/RoleSelector.vue @@ -0,0 +1,165 @@ + + + + + diff --git a/frontend/src/layouts/AdminLayout.vue b/frontend/src/layouts/AdminLayout.vue new file mode 100644 index 0000000..7764f8a --- /dev/null +++ b/frontend/src/layouts/AdminLayout.vue @@ -0,0 +1,270 @@ + + + + + diff --git a/frontend/src/layouts/MerchantLayout.vue b/frontend/src/layouts/MerchantLayout.vue new file mode 100644 index 0000000..26e26be --- /dev/null +++ b/frontend/src/layouts/MerchantLayout.vue @@ -0,0 +1,306 @@ + + + + + diff --git a/frontend/src/main.js b/frontend/src/main.js new file mode 100644 index 0000000..78b7169 --- /dev/null +++ b/frontend/src/main.js @@ -0,0 +1,10 @@ +import { createApp } from 'vue' +import App from './App.vue' +import router from './router' +import ElementPlus from 'element-plus' +import 'element-plus/dist/index.css' + +const app = createApp(App) +app.use(router) +app.use(ElementPlus) +app.mount('#app') diff --git a/frontend/src/router/index.js b/frontend/src/router/index.js new file mode 100644 index 0000000..bb6dda3 --- /dev/null +++ b/frontend/src/router/index.js @@ -0,0 +1,240 @@ +import { createRouter, createWebHistory } from 'vue-router' +import { userStore } from '../store/user' +import { merchantStore } from '../store/merchant' +import { adminStore } from '../store/admin' + +const routes = [ + // 首页 + { + path: '/', + name: 'Home', + component: () => import('../views/Home.vue') + }, + // 用户登录注册 + { + path: '/login', + name: 'Login', + component: () => import('../views/Login.vue') + }, + { + path: '/register', + name: 'Register', + component: () => import('../views/Register.vue') + }, + // 商品详情 + { + path: '/product/:id', + name: 'ProductDetail', + component: () => import('../views/product/ProductDetail.vue') + }, + // 用户中心相关 + { + path: '/profile', + name: 'Profile', + component: () => import('../views/user/Profile.vue'), + meta: { requiresAuth: true, role: 'user' } + }, + { + path: '/cart', + name: 'Cart', + component: () => import('../views/user/Cart.vue'), + meta: { requiresAuth: true, role: 'user' } + }, + { + path: '/orders', + name: 'Orders', + component: () => import('../views/user/Orders.vue'), + meta: { requiresAuth: true, role: 'user' } + }, + { + path: '/orders/:id', + name: 'OrderDetail', + component: () => import('../views/user/OrderDetail.vue'), + meta: { requiresAuth: true, role: 'user' } + }, + { + path: '/addresses', + name: 'Addresses', + component: () => import('../views/user/Addresses.vue'), + meta: { requiresAuth: true, role: 'user' } + }, + { + path: '/favorites', + name: 'Favorites', + component: () => import('../views/user/Favorites.vue'), + meta: { requiresAuth: true, role: 'user' } + }, + // 系统公告页面 + { + path: '/announcements', + name: 'Announcements', + component: () => import('../views/user/Announcements.vue') + }, + // 结算页面 + { + path: '/checkout', + name: 'Checkout', + component: () => import('../views/checkout/Checkout.vue'), + meta: { requiresAuth: true, role: 'user' } + }, + // 商家端 + { + path: '/merchant', + redirect: '/merchant/login' + }, + { + path: '/merchant/login', + name: 'MerchantLogin', + component: () => import('../views/merchant/Login.vue') + }, + { + path: '/merchant/register', + name: 'MerchantRegister', + component: () => import('../views/merchant/Register.vue') + }, + { + path: '/merchant/dashboard', + name: 'MerchantDashboard', + component: () => import('../layouts/MerchantLayout.vue'), + meta: { requiresAuth: true, role: 'merchant' }, + children: [ + { + path: '', + name: 'MerchantHome', + component: () => import('../views/merchant/Dashboard.vue') + }, + { + path: 'products', + name: 'MerchantProducts', + component: () => import('../views/merchant/products/ProductManagement.vue') + }, + { + path: 'products/add', + name: 'MerchantProductAdd', + component: () => import('../views/merchant/products/ProductForm.vue') + }, + { + path: 'products/edit/:id', + name: 'MerchantProductEdit', + component: () => import('../views/merchant/products/ProductForm.vue') + }, + { + path: 'inventory', + name: 'MerchantInventory', + component: () => import('../views/merchant/inventory/InventoryManagement.vue') + }, + { + path: 'orders', + name: 'MerchantOrders', + component: () => import('../views/merchant/orders/OrderManagement.vue') + }, + { + path: 'customers', + name: 'MerchantCustomers', + component: () => import('../views/merchant/customers/CustomerManagement.vue') + }, + { + path: 'statistics', + name: 'MerchantStatistics', + component: () => import('../views/merchant/statistics/StatisticsView.vue') + }, + { + path: 'settings', + name: 'MerchantSettings', + component: () => import('../views/merchant/settings/ProfileSettings.vue') + }, + { + path: 'customer-service', + name: 'MerchantCustomerService', + component: () => import('../views/merchant/customers/CustomerService.vue') + } + ] + }, + // 管理员端 + { + path: '/admin', + redirect: '/admin/login' + }, + { + path: '/admin/login', + name: 'AdminLogin', + component: () => import('../views/admin/Login.vue') + }, + { + path: '/admin/dashboard', + name: 'AdminDashboard', + component: () => import('../layouts/AdminLayout.vue'), + meta: { requiresAuth: true, role: 'admin' }, + children: [ + { + path: '', + name: 'AdminHome', + component: () => import('../views/admin/Dashboard.vue') + }, + { + path: 'users', + name: 'AdminUsers', + component: () => import('../views/admin/users/UserManagement.vue') + }, + { + path: 'merchants', + name: 'AdminMerchants', + component: () => import('../views/admin/merchants/MerchantManagement.vue') + }, + { + path: 'products', + name: 'AdminProducts', + component: () => import('../views/admin/products/ProductManagement.vue') + }, + { + path: 'orders', + name: 'AdminOrders', + component: () => import('../views/admin/orders/OrderManagement.vue') + }, + { + path: 'system', + name: 'AdminSystem', + component: () => import('../views/admin/system/SystemManagement.vue') + }, + { + path: 'profile', + name: 'AdminProfile', + component: () => import('../views/admin/profile/ProfileSettings.vue') + } + ] + } +] + +const router = createRouter({ + history: createWebHistory(), + routes +}) + +// 路由守卫 +router.beforeEach((to, from, next) => { + const requiresAuth = to.meta.requiresAuth + const role = to.meta.role + + if (requiresAuth) { + if (role === 'admin') { + if (!adminStore.isLoggedIn) { + next('/admin/login') + return + } + } else if (role === 'merchant') { + if (!merchantStore.isLoggedIn) { + next('/merchant/login') + return + } + } else if (role === 'user') { + if (!userStore.isLoggedIn) { + next('/login') + return + } + } + } + + next() +}) + +export default router diff --git a/frontend/src/store/admin.js b/frontend/src/store/admin.js new file mode 100644 index 0000000..7c5c67a --- /dev/null +++ b/frontend/src/store/admin.js @@ -0,0 +1,54 @@ +import { reactive } from 'vue' +import { adminLogin as adminLoginApi } from '../api/admin' + +export const adminStore = reactive({ + token: localStorage.getItem('admin_token') || '', + currentAdmin: JSON.parse(localStorage.getItem('admin_info') || 'null'), + isLoggedIn: !!localStorage.getItem('admin_token'), + + // 登录 + async login(loginData) { + try { + const result = await adminLoginApi(loginData) + this.token = result.token + this.currentAdmin = result.admin + this.isLoggedIn = true + + localStorage.setItem('admin_token', result.token) + localStorage.setItem('admin_info', JSON.stringify(result.admin)) + + return result + } catch (error) { + throw error + } + }, + + // 注销 + async logout() { + try { + // 管理员注销暂时不需要调用API + // await adminLogoutApi() + } catch (error) { + // 即使接口失败也要清除本地数据 + } finally { + this.token = '' + this.currentAdmin = null + this.isLoggedIn = false + + localStorage.removeItem('admin_token') + localStorage.removeItem('admin_info') + } + }, + + // 更新当前管理员信息 + updateAdmin(adminData) { + this.admin = { ...this.admin, ...adminData } + localStorage.setItem('admin_info', JSON.stringify(this.admin)) + }, + + // 更新当前管理员信息 + updateCurrentAdmin(adminData) { + this.currentAdmin = { ...this.currentAdmin, ...adminData } + localStorage.setItem('admin_info', JSON.stringify(this.currentAdmin)) + } +}) diff --git a/frontend/src/store/merchant.js b/frontend/src/store/merchant.js new file mode 100644 index 0000000..a9d7f73 --- /dev/null +++ b/frontend/src/store/merchant.js @@ -0,0 +1,53 @@ +import { reactive } from 'vue' +import { merchantLogin as merchantLoginApi } from '../api/merchant' + +export const merchantStore = reactive({ + token: localStorage.getItem('merchant_token') || '', + user: JSON.parse(localStorage.getItem('merchant_user') || 'null'), + merchant: JSON.parse(localStorage.getItem('merchant_info') || 'null'), + isLoggedIn: !!localStorage.getItem('merchant_token'), + + // 登录 + async login(loginData) { + try { + const result = await merchantLoginApi(loginData) + this.token = result.token + this.user = result.user + this.merchant = result.merchant + this.isLoggedIn = true + + localStorage.setItem('merchant_token', result.token) + localStorage.setItem('merchant_user', JSON.stringify(result.user)) + localStorage.setItem('merchant_info', JSON.stringify(result.merchant)) + + return result + } catch (error) { + throw error + } + }, + + // 注销 + async logout() { + try { + // 商家注销暂时不需要调用API + // await merchantLogoutApi() + } catch (error) { + // 即使接口失败也要清除本地数据 + } finally { + this.token = '' + this.user = null + this.merchant = null + this.isLoggedIn = false + + localStorage.removeItem('merchant_token') + localStorage.removeItem('merchant_user') + localStorage.removeItem('merchant_info') + } + }, + + // 更新商家信息 + updateMerchant(merchantData) { + this.merchant = { ...this.merchant, ...merchantData } + localStorage.setItem('merchant_info', JSON.stringify(this.merchant)) + } +}) diff --git a/frontend/src/store/user.js b/frontend/src/store/user.js new file mode 100644 index 0000000..0335ea0 --- /dev/null +++ b/frontend/src/store/user.js @@ -0,0 +1,47 @@ +import { reactive } from 'vue' +import { login as loginApi, logout as logoutApi } from '../api/user' + +export const userStore = reactive({ + token: localStorage.getItem('token') || '', + user: JSON.parse(localStorage.getItem('user') || 'null'), + isLoggedIn: !!localStorage.getItem('token'), + + // 登录 + async login(loginData) { + try { + const result = await loginApi(loginData) + this.token = result.token + this.user = result.user + this.isLoggedIn = true + + localStorage.setItem('token', result.token) + localStorage.setItem('user', JSON.stringify(result.user)) + + return result + } catch (error) { + throw error + } + }, + + // 注销 + async logout() { + try { + await logoutApi() + } catch (error) { + // 即使接口失败也要清除本地数据 + } finally { + this.token = '' + this.user = null + this.isLoggedIn = false + + localStorage.removeItem('token') + localStorage.removeItem('user') + } + }, + + // 更新用户信息 + updateUser(userData) { + this.user = { ...this.user, ...userData } + localStorage.setItem('user', JSON.stringify(this.user)) + } +}) diff --git a/frontend/src/utils/request.js b/frontend/src/utils/request.js new file mode 100644 index 0000000..2879fbf --- /dev/null +++ b/frontend/src/utils/request.js @@ -0,0 +1,141 @@ +import axios from 'axios' +import { ElMessage } from 'element-plus' +import { userStore } from '../store/user' +import { adminStore } from '../store/admin' +import { merchantStore } from '../store/merchant' + +const service = axios.create({ + baseURL: import.meta.env.VITE_API_BASE_URL || 'http://localhost:8080/api', + timeout: 30000 +}) + +// 公开接口列表(不需要登录的接口) +const PUBLIC_APIS = [ + '/user/login', + '/user/register', + '/user/send-code', + '/user/send-email-code', + '/user/verify-code', + '/merchant/register', + '/merchant/login', + '/admin/login', + '/category/', + '/product/list', + '/product/hot', + '/product/search', + '/init/', + '/test/', + '/payment/alipay/notify', + '/payment/alipay/return' +] + +// 检查是否是公开接口 +const isPublicAPI = (url) => { + return PUBLIC_APIS.some(api => url.includes(api)) +} + +// 请求拦截器 +service.interceptors.request.use( + config => { + const currentPath = window.location.pathname + let token = null + + if (currentPath.startsWith('/admin')) { + token = adminStore.token + } else if (currentPath.startsWith('/merchant')) { + token = merchantStore.token + } else { + token = userStore.token + } + + if (token) { + config.headers.Authorization = `Bearer ${token}` + } + + return config + }, + error => { + console.error('请求错误:', error) + return Promise.reject(error) + } +) + +// 响应拦截器 +service.interceptors.response.use( + response => { + const res = response.data + console.log('API响应:', response.config.url, res) + + // 如果返回的状态码不是200,说明出错了 + if (res.code !== 200) { + ElMessage.error(res.message || '请求失败') + + // 401表示token过期或无效 - 但要排除公开接口 + if (res.code === 401 && !isPublicAPI(response.config.url)) { + console.warn('Token过期,跳转到登录页面') + const currentPath = window.location.pathname + + if (currentPath.startsWith('/admin')) { + adminStore.logout() + window.location.href = '/admin/login' + } else if (currentPath.startsWith('/merchant')) { + merchantStore.logout() + window.location.href = '/merchant/login' + } else { + userStore.logout() + window.location.href = '/login' + } + } + + return Promise.reject(new Error(res.message || '请求失败')) + } else { + return res.data + } + }, + error => { + console.error('响应错误:', error) + + if (error.response) { + const { status, data } = error.response + const requestUrl = error.config?.url || '' + + if (status === 401) { + // 只有非公开接口的401错误才跳转到登录页 + if (!isPublicAPI(requestUrl)) { + ElMessage.error('登录已过期,请重新登录') + const currentPath = window.location.pathname + + if (currentPath.startsWith('/admin')) { + adminStore.logout() + window.location.href = '/admin/login' + } else if (currentPath.startsWith('/merchant')) { + merchantStore.logout() + window.location.href = '/merchant/login' + } else { + userStore.logout() + window.location.href = '/login' + } + } else { + // 公开接口的401错误,只显示错误消息,不跳转 + ElMessage.error(data?.message || '请求未授权') + } + } else if (status === 403) { + ElMessage.error('没有权限访问') + } else if (status === 404) { + ElMessage.error('请求的资源不存在') + } else if (status === 500) { + ElMessage.error('服务器内部错误') + } else { + ElMessage.error(data?.message || `请求失败 (${status})`) + } + } else if (error.request) { + ElMessage.error('网络错误,请检查网络连接') + } else { + ElMessage.error('请求配置错误') + } + + return Promise.reject(error) + } +) + +export default service diff --git a/frontend/src/views/Home.vue b/frontend/src/views/Home.vue new file mode 100644 index 0000000..5928328 --- /dev/null +++ b/frontend/src/views/Home.vue @@ -0,0 +1,1126 @@ + + + + + \ No newline at end of file diff --git a/frontend/src/views/Login.vue b/frontend/src/views/Login.vue new file mode 100644 index 0000000..438fd6e --- /dev/null +++ b/frontend/src/views/Login.vue @@ -0,0 +1,354 @@ + + + + + diff --git a/frontend/src/views/Register.vue b/frontend/src/views/Register.vue new file mode 100644 index 0000000..730eb92 --- /dev/null +++ b/frontend/src/views/Register.vue @@ -0,0 +1,566 @@ + + + + + diff --git a/frontend/src/views/admin/Dashboard.vue b/frontend/src/views/admin/Dashboard.vue new file mode 100644 index 0000000..5d6fda8 --- /dev/null +++ b/frontend/src/views/admin/Dashboard.vue @@ -0,0 +1,382 @@ + + + + + diff --git a/frontend/src/views/admin/Login.vue b/frontend/src/views/admin/Login.vue new file mode 100644 index 0000000..a0cfd05 --- /dev/null +++ b/frontend/src/views/admin/Login.vue @@ -0,0 +1,197 @@ + + + + + diff --git a/frontend/src/views/admin/MerchantManagement.vue b/frontend/src/views/admin/MerchantManagement.vue new file mode 100644 index 0000000..d8fc121 --- /dev/null +++ b/frontend/src/views/admin/MerchantManagement.vue @@ -0,0 +1,321 @@ + + + + + diff --git a/frontend/src/views/admin/OrderManagement.vue b/frontend/src/views/admin/OrderManagement.vue new file mode 100644 index 0000000..f36e5c8 --- /dev/null +++ b/frontend/src/views/admin/OrderManagement.vue @@ -0,0 +1,391 @@ + + + + + diff --git a/frontend/src/views/admin/ProductManagement.vue b/frontend/src/views/admin/ProductManagement.vue new file mode 100644 index 0000000..51bcef8 --- /dev/null +++ b/frontend/src/views/admin/ProductManagement.vue @@ -0,0 +1,360 @@ + + + + + diff --git a/frontend/src/views/admin/SystemManagement.vue b/frontend/src/views/admin/SystemManagement.vue new file mode 100644 index 0000000..833a9c3 --- /dev/null +++ b/frontend/src/views/admin/SystemManagement.vue @@ -0,0 +1,654 @@ + + + + + diff --git a/frontend/src/views/admin/UserManagement.vue b/frontend/src/views/admin/UserManagement.vue new file mode 100644 index 0000000..4b716a9 --- /dev/null +++ b/frontend/src/views/admin/UserManagement.vue @@ -0,0 +1,288 @@ + + + + + diff --git a/frontend/src/views/admin/merchants/MerchantManagement.vue b/frontend/src/views/admin/merchants/MerchantManagement.vue new file mode 100644 index 0000000..2987f95 --- /dev/null +++ b/frontend/src/views/admin/merchants/MerchantManagement.vue @@ -0,0 +1,444 @@ + + + + + diff --git a/frontend/src/views/admin/orders/OrderManagement.vue b/frontend/src/views/admin/orders/OrderManagement.vue new file mode 100644 index 0000000..f94cd0b --- /dev/null +++ b/frontend/src/views/admin/orders/OrderManagement.vue @@ -0,0 +1,249 @@ + + + + + diff --git a/frontend/src/views/admin/products/ProductManagement.vue b/frontend/src/views/admin/products/ProductManagement.vue new file mode 100644 index 0000000..8111441 --- /dev/null +++ b/frontend/src/views/admin/products/ProductManagement.vue @@ -0,0 +1,272 @@ + + + + + diff --git a/frontend/src/views/admin/profile/ProfileSettings.vue b/frontend/src/views/admin/profile/ProfileSettings.vue new file mode 100644 index 0000000..c0b9db9 --- /dev/null +++ b/frontend/src/views/admin/profile/ProfileSettings.vue @@ -0,0 +1,504 @@ + + + + + diff --git a/frontend/src/views/admin/system/SystemManagement.vue b/frontend/src/views/admin/system/SystemManagement.vue new file mode 100644 index 0000000..29e87cf --- /dev/null +++ b/frontend/src/views/admin/system/SystemManagement.vue @@ -0,0 +1,656 @@ + + + + + diff --git a/frontend/src/views/admin/users/UserManagement.vue b/frontend/src/views/admin/users/UserManagement.vue new file mode 100644 index 0000000..a12613a --- /dev/null +++ b/frontend/src/views/admin/users/UserManagement.vue @@ -0,0 +1,242 @@ + + + + + diff --git a/frontend/src/views/checkout/Checkout.vue b/frontend/src/views/checkout/Checkout.vue new file mode 100644 index 0000000..2ffbcbc --- /dev/null +++ b/frontend/src/views/checkout/Checkout.vue @@ -0,0 +1,780 @@ + + + + + diff --git a/frontend/src/views/merchant/Dashboard.vue b/frontend/src/views/merchant/Dashboard.vue new file mode 100644 index 0000000..6305449 --- /dev/null +++ b/frontend/src/views/merchant/Dashboard.vue @@ -0,0 +1,645 @@ + + + + + diff --git a/frontend/src/views/merchant/Login.vue b/frontend/src/views/merchant/Login.vue new file mode 100644 index 0000000..a6f7eb0 --- /dev/null +++ b/frontend/src/views/merchant/Login.vue @@ -0,0 +1,285 @@ + + + + + diff --git a/frontend/src/views/merchant/Register.vue b/frontend/src/views/merchant/Register.vue new file mode 100644 index 0000000..65d648c --- /dev/null +++ b/frontend/src/views/merchant/Register.vue @@ -0,0 +1,624 @@ + + + + + diff --git a/frontend/src/views/merchant/customers/CustomerManagement.vue b/frontend/src/views/merchant/customers/CustomerManagement.vue new file mode 100644 index 0000000..61d83e7 --- /dev/null +++ b/frontend/src/views/merchant/customers/CustomerManagement.vue @@ -0,0 +1,622 @@ + + + + + diff --git a/frontend/src/views/merchant/customers/CustomerService.vue b/frontend/src/views/merchant/customers/CustomerService.vue new file mode 100644 index 0000000..d4d9cdc --- /dev/null +++ b/frontend/src/views/merchant/customers/CustomerService.vue @@ -0,0 +1,580 @@ + + + + + diff --git a/frontend/src/views/merchant/inventory/InventoryManagement.vue b/frontend/src/views/merchant/inventory/InventoryManagement.vue new file mode 100644 index 0000000..63ee692 --- /dev/null +++ b/frontend/src/views/merchant/inventory/InventoryManagement.vue @@ -0,0 +1,1069 @@ + + + + + diff --git a/frontend/src/views/merchant/orders/OrderManagement.vue b/frontend/src/views/merchant/orders/OrderManagement.vue new file mode 100644 index 0000000..3300827 --- /dev/null +++ b/frontend/src/views/merchant/orders/OrderManagement.vue @@ -0,0 +1,729 @@ + + + + + diff --git a/frontend/src/views/merchant/products/ProductForm.vue b/frontend/src/views/merchant/products/ProductForm.vue new file mode 100644 index 0000000..1f627d9 --- /dev/null +++ b/frontend/src/views/merchant/products/ProductForm.vue @@ -0,0 +1,694 @@ + + + + + diff --git a/frontend/src/views/merchant/products/ProductManagement.vue b/frontend/src/views/merchant/products/ProductManagement.vue new file mode 100644 index 0000000..69f3e1d --- /dev/null +++ b/frontend/src/views/merchant/products/ProductManagement.vue @@ -0,0 +1,347 @@ + + + + + diff --git a/frontend/src/views/merchant/settings/ProfileSettings.vue b/frontend/src/views/merchant/settings/ProfileSettings.vue new file mode 100644 index 0000000..8e9da4e --- /dev/null +++ b/frontend/src/views/merchant/settings/ProfileSettings.vue @@ -0,0 +1,748 @@ + + + + + diff --git a/frontend/src/views/merchant/statistics/StatisticsView.vue b/frontend/src/views/merchant/statistics/StatisticsView.vue new file mode 100644 index 0000000..422a638 --- /dev/null +++ b/frontend/src/views/merchant/statistics/StatisticsView.vue @@ -0,0 +1,909 @@ + + + + + diff --git a/frontend/src/views/payment/PaymentPage.vue b/frontend/src/views/payment/PaymentPage.vue new file mode 100644 index 0000000..2c14579 --- /dev/null +++ b/frontend/src/views/payment/PaymentPage.vue @@ -0,0 +1,179 @@ + + + + + diff --git a/frontend/src/views/product/ProductDetail.vue b/frontend/src/views/product/ProductDetail.vue new file mode 100644 index 0000000..64eb715 --- /dev/null +++ b/frontend/src/views/product/ProductDetail.vue @@ -0,0 +1,1560 @@ + + + + + \ No newline at end of file diff --git a/frontend/src/views/user/Addresses.vue b/frontend/src/views/user/Addresses.vue new file mode 100644 index 0000000..6b6a559 --- /dev/null +++ b/frontend/src/views/user/Addresses.vue @@ -0,0 +1,645 @@ + + + + + diff --git a/frontend/src/views/user/Announcements.vue b/frontend/src/views/user/Announcements.vue new file mode 100644 index 0000000..c452a6d --- /dev/null +++ b/frontend/src/views/user/Announcements.vue @@ -0,0 +1,1194 @@ + + + + + diff --git a/frontend/src/views/user/Cart.vue b/frontend/src/views/user/Cart.vue new file mode 100644 index 0000000..fe11272 --- /dev/null +++ b/frontend/src/views/user/Cart.vue @@ -0,0 +1,725 @@ + + + + + diff --git a/frontend/src/views/user/Favorites.vue b/frontend/src/views/user/Favorites.vue new file mode 100644 index 0000000..9c44542 --- /dev/null +++ b/frontend/src/views/user/Favorites.vue @@ -0,0 +1,922 @@ + + + + + diff --git a/frontend/src/views/user/OrderDetail.vue b/frontend/src/views/user/OrderDetail.vue new file mode 100644 index 0000000..4c49483 --- /dev/null +++ b/frontend/src/views/user/OrderDetail.vue @@ -0,0 +1,1046 @@ + + + + + diff --git a/frontend/src/views/user/Orders.vue b/frontend/src/views/user/Orders.vue new file mode 100644 index 0000000..391c492 --- /dev/null +++ b/frontend/src/views/user/Orders.vue @@ -0,0 +1,714 @@ + + + + + diff --git a/frontend/src/views/user/Profile.vue b/frontend/src/views/user/Profile.vue new file mode 100644 index 0000000..ff88e5b --- /dev/null +++ b/frontend/src/views/user/Profile.vue @@ -0,0 +1,537 @@ + + + + + diff --git a/frontend/vite.config.js b/frontend/vite.config.js new file mode 100644 index 0000000..96024a3 --- /dev/null +++ b/frontend/vite.config.js @@ -0,0 +1,21 @@ +import { defineConfig } from 'vite' +import vue from '@vitejs/plugin-vue' +import { resolve } from 'path' + +export default defineConfig({ + plugins: [vue()], + resolve: { + alias: { + '@': resolve(__dirname, 'src') + } + }, + server: { + port: 3000, + proxy: { + '/api': { + target: 'http://localhost:8080', + changeOrigin: true + } + } + } +}) diff --git a/sql/init.sql b/sql/init.sql new file mode 100644 index 0000000..62ebc50 --- /dev/null +++ b/sql/init.sql @@ -0,0 +1,960 @@ +/* + Navicat Premium Dump SQL + + Source Server : SunnyFarm + Source Server Type : MySQL + Source Server Version : 80035 (8.0.35) + Source Host : 119.91.236.167:3306 + Source Schema : sunnyfarm + + Target Server Type : MySQL + Target Server Version : 80035 (8.0.35) + File Encoding : 65001 + + Date: 25/09/2025 05:47:46 +*/ + +SET NAMES utf8mb4; +SET FOREIGN_KEY_CHECKS = 0; + +-- ---------------------------- +-- Table structure for admins +-- ---------------------------- +DROP TABLE IF EXISTS `admins`; +CREATE TABLE `admins` ( + `id` bigint NOT NULL AUTO_INCREMENT COMMENT '管理员ID', + `username` varchar(50) NOT NULL COMMENT '用户名', + `password` varchar(128) NOT NULL COMMENT '密码', + `email` varchar(100) DEFAULT NULL COMMENT '邮箱', + `phone` varchar(20) DEFAULT NULL COMMENT '电话', + `real_name` varchar(50) DEFAULT NULL COMMENT '真实姓名', + `role_id` bigint DEFAULT NULL COMMENT '角色ID', + `status` tinyint(1) DEFAULT '1' COMMENT '状态:0禁用,1正常', + `last_login_time` datetime DEFAULT NULL COMMENT '最后登录时间', + `created_at` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `updated_at` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + PRIMARY KEY (`id`), + UNIQUE KEY `uk_username` (`username`) +) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='管理员表'; + +-- ---------------------------- +-- Records of admins +-- ---------------------------- +BEGIN; +INSERT INTO `admins` (`id`, `username`, `password`, `email`, `phone`, `real_name`, `role_id`, `status`, `last_login_time`, `created_at`, `updated_at`) VALUES (2, 'admin', '$2a$10$MUkorAzhAFMhp1D8leAAlexNvWUWramuHY40qsD2LSdAIe.ja6q9C', 'admin@sunnyfarm.com', '18888888888', '系统管理员', 1, 1, '2025-09-24 20:33:53', '2025-07-16 13:12:09', '2025-09-24 20:33:52'); +COMMIT; + +-- ---------------------------- +-- Table structure for announcements +-- ---------------------------- +DROP TABLE IF EXISTS `announcements`; +CREATE TABLE `announcements` ( + `id` bigint NOT NULL AUTO_INCREMENT COMMENT '公告ID', + `title` varchar(200) NOT NULL COMMENT '标题', + `content` text NOT NULL COMMENT '内容', + `type` tinyint(1) DEFAULT '1' COMMENT '类型:1系统公告,2活动公告,3维护公告', + `status` tinyint(1) DEFAULT '1' COMMENT '状态:0草稿,1发布', + `publish_time` datetime DEFAULT NULL COMMENT '发布时间', + `creator_id` bigint DEFAULT NULL COMMENT '创建者ID', + `creator_name` varchar(50) DEFAULT NULL COMMENT '创建者名称', + `created_at` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `updated_at` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + PRIMARY KEY (`id`) +) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='公告表'; + +-- ---------------------------- +-- Records of announcements +-- ---------------------------- +BEGIN; +INSERT INTO `announcements` (`id`, `title`, `content`, `type`, `status`, `publish_time`, `creator_id`, `creator_name`, `created_at`, `updated_at`) VALUES (1, '系统维护通知', '系统将于今晚23:00-01:00进行维护,请用户提前做好准备。', 3, 1, '2025-07-18 05:46:23', 1, 'admin', '2025-07-18 05:46:23', '2025-07-18 05:46:23'); +INSERT INTO `announcements` (`id`, `title`, `content`, `type`, `status`, `publish_time`, `creator_id`, `creator_name`, `created_at`, `updated_at`) VALUES (2, '春节活动优惠', '春节期间全场商品8折优惠,欢迎选购!', 2, 1, '2025-07-18 05:46:23', 1, 'admin', '2025-07-18 05:46:23', '2025-07-18 05:46:23'); +INSERT INTO `announcements` (`id`, `title`, `content`, `type`, `status`, `publish_time`, `creator_id`, `creator_name`, `created_at`, `updated_at`) VALUES (3, '新功能上线', '商家评价功能正式上线,欢迎体验!', 1, 1, '2025-09-24 18:27:48', 1, 'admin', '2025-07-18 05:46:23', '2025-07-18 05:46:23'); +COMMIT; + +-- ---------------------------- +-- Table structure for categories +-- ---------------------------- +DROP TABLE IF EXISTS `categories`; +CREATE TABLE `categories` ( + `id` bigint NOT NULL AUTO_INCREMENT COMMENT '分类ID', + `name` varchar(50) NOT NULL COMMENT '分类名称', + `parent_id` bigint DEFAULT '0' COMMENT '父分类ID', + `sort_order` int DEFAULT '0' COMMENT '排序', + `icon` varchar(255) DEFAULT NULL COMMENT '图标', + `description` varchar(200) DEFAULT NULL COMMENT '描述', + `status` tinyint(1) DEFAULT '1' COMMENT '状态:0禁用,1启用', + `created_at` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `updated_at` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + PRIMARY KEY (`id`), + KEY `idx_parent_id` (`parent_id`) +) ENGINE=InnoDB AUTO_INCREMENT=14 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='商品分类表'; + +-- ---------------------------- +-- Records of categories +-- ---------------------------- +BEGIN; +INSERT INTO `categories` (`id`, `name`, `parent_id`, `sort_order`, `icon`, `description`, `status`, `created_at`, `updated_at`) VALUES (1, '蔬菜类', 0, 1, NULL, NULL, 1, '2025-07-16 04:56:56', '2025-07-16 04:56:56'); +INSERT INTO `categories` (`id`, `name`, `parent_id`, `sort_order`, `icon`, `description`, `status`, `created_at`, `updated_at`) VALUES (2, '水果类', 0, 2, NULL, NULL, 1, '2025-07-16 04:56:56', '2025-07-16 04:56:56'); +INSERT INTO `categories` (`id`, `name`, `parent_id`, `sort_order`, `icon`, `description`, `status`, `created_at`, `updated_at`) VALUES (3, '粮食类', 0, 3, NULL, NULL, 1, '2025-07-16 04:56:56', '2025-07-16 04:56:56'); +INSERT INTO `categories` (`id`, `name`, `parent_id`, `sort_order`, `icon`, `description`, `status`, `created_at`, `updated_at`) VALUES (4, '畜牧类', 0, 4, NULL, NULL, 1, '2025-07-16 04:56:56', '2025-07-16 04:56:56'); +INSERT INTO `categories` (`id`, `name`, `parent_id`, `sort_order`, `icon`, `description`, `status`, `created_at`, `updated_at`) VALUES (5, '叶菜类', 1, 1, NULL, NULL, 1, '2025-07-16 04:56:56', '2025-07-16 04:56:56'); +INSERT INTO `categories` (`id`, `name`, `parent_id`, `sort_order`, `icon`, `description`, `status`, `created_at`, `updated_at`) VALUES (6, '根茎类', 1, 2, NULL, NULL, 1, '2025-07-16 04:56:56', '2025-07-16 04:56:56'); +INSERT INTO `categories` (`id`, `name`, `parent_id`, `sort_order`, `icon`, `description`, `status`, `created_at`, `updated_at`) VALUES (7, '瓜果类', 1, 3, NULL, NULL, 1, '2025-07-16 04:56:56', '2025-07-16 04:56:56'); +INSERT INTO `categories` (`id`, `name`, `parent_id`, `sort_order`, `icon`, `description`, `status`, `created_at`, `updated_at`) VALUES (8, '时令水果', 2, 1, NULL, NULL, 1, '2025-07-16 04:56:56', '2025-07-16 04:56:56'); +INSERT INTO `categories` (`id`, `name`, `parent_id`, `sort_order`, `icon`, `description`, `status`, `created_at`, `updated_at`) VALUES (9, '进口水果', 2, 2, NULL, NULL, 1, '2025-07-16 04:56:56', '2025-07-16 04:56:56'); +INSERT INTO `categories` (`id`, `name`, `parent_id`, `sort_order`, `icon`, `description`, `status`, `created_at`, `updated_at`) VALUES (10, '大米', 3, 1, NULL, NULL, 1, '2025-07-16 04:56:56', '2025-07-16 04:56:56'); +INSERT INTO `categories` (`id`, `name`, `parent_id`, `sort_order`, `icon`, `description`, `status`, `created_at`, `updated_at`) VALUES (11, '面粉', 3, 2, NULL, NULL, 1, '2025-07-16 04:56:56', '2025-07-16 04:56:56'); +INSERT INTO `categories` (`id`, `name`, `parent_id`, `sort_order`, `icon`, `description`, `status`, `created_at`, `updated_at`) VALUES (12, '猪肉', 4, 1, NULL, NULL, 1, '2025-07-16 04:56:56', '2025-07-16 04:56:56'); +INSERT INTO `categories` (`id`, `name`, `parent_id`, `sort_order`, `icon`, `description`, `status`, `created_at`, `updated_at`) VALUES (13, '鸡肉', 4, 2, NULL, NULL, 1, '2025-07-16 04:56:56', '2025-07-16 04:56:56'); +COMMIT; + +-- ---------------------------- +-- Table structure for chat_messages +-- ---------------------------- +DROP TABLE IF EXISTS `chat_messages`; +CREATE TABLE `chat_messages` ( + `id` bigint NOT NULL AUTO_INCREMENT COMMENT '消息ID', + `session_id` bigint NOT NULL COMMENT '会话ID', + `sender_id` bigint NOT NULL COMMENT '发送人ID', + `sender_type` tinyint(1) NOT NULL COMMENT '发送人类型:1用户,2商家', + `message_type` tinyint(1) DEFAULT '1' COMMENT '消息类型:1文本,2图片,3文件', + `content` text NOT NULL COMMENT '消息内容', + `is_read` tinyint(1) DEFAULT '0' COMMENT '是否已读:0未读,1已读', + `created_at` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + PRIMARY KEY (`id`), + KEY `idx_session_id` (`session_id`) +) ENGINE=InnoDB AUTO_INCREMENT=8 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='聊天消息表'; + +-- ---------------------------- +-- Records of chat_messages +-- ---------------------------- +BEGIN; +INSERT INTO `chat_messages` (`id`, `session_id`, `sender_id`, `sender_type`, `message_type`, `content`, `is_read`, `created_at`) VALUES (1, 1, 1, 1, 1, '你好!\n这个胡萝卜还有吗?', 1, '2025-09-23 22:15:36'); +INSERT INTO `chat_messages` (`id`, `session_id`, `sender_id`, `sender_type`, `message_type`, `content`, `is_read`, `created_at`) VALUES (2, 1, 1, 2, 1, '你好!有的有的!', 1, '2025-09-23 22:16:00'); +INSERT INTO `chat_messages` (`id`, `session_id`, `sender_id`, `sender_type`, `message_type`, `content`, `is_read`, `created_at`) VALUES (3, 1, 1, 1, 1, '嗯?我想买1000斤!\n', 1, '2025-09-23 22:24:25'); +INSERT INTO `chat_messages` (`id`, `session_id`, `sender_id`, `sender_type`, `message_type`, `content`, `is_read`, `created_at`) VALUES (4, 1, 1, 2, 1, '那也有的呀!兄弟!', 1, '2025-09-23 22:24:44'); +INSERT INTO `chat_messages` (`id`, `session_id`, `sender_id`, `sender_type`, `message_type`, `content`, `is_read`, `created_at`) VALUES (5, 1, 1, 1, 1, '那我明天要可以不?', 1, '2025-09-23 22:32:30'); +INSERT INTO `chat_messages` (`id`, `session_id`, `sender_id`, `sender_type`, `message_type`, `content`, `is_read`, `created_at`) VALUES (6, 1, 1, 2, 1, '可以的可以的!', 1, '2025-09-23 22:32:46'); +INSERT INTO `chat_messages` (`id`, `session_id`, `sender_id`, `sender_type`, `message_type`, `content`, `is_read`, `created_at`) VALUES (7, 1, 1, 1, 1, '好的好的!', 1, '2025-09-24 16:56:39'); +COMMIT; + +-- ---------------------------- +-- Table structure for chat_sessions +-- ---------------------------- +DROP TABLE IF EXISTS `chat_sessions`; +CREATE TABLE `chat_sessions` ( + `id` bigint NOT NULL AUTO_INCREMENT COMMENT '会话ID', + `user_id` bigint NOT NULL COMMENT '用户ID', + `merchant_id` bigint NOT NULL COMMENT '商家ID', + `last_message` text COMMENT '最后一条消息', + `last_message_time` datetime DEFAULT NULL COMMENT '最后消息时间', + `status` tinyint(1) DEFAULT '1' COMMENT '状态:0关闭,1活跃', + `created_at` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `updated_at` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + PRIMARY KEY (`id`), + KEY `idx_user_id` (`user_id`), + KEY `idx_merchant_id` (`merchant_id`) +) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='聊天会话表'; + +-- ---------------------------- +-- Records of chat_sessions +-- ---------------------------- +BEGIN; +INSERT INTO `chat_sessions` (`id`, `user_id`, `merchant_id`, `last_message`, `last_message_time`, `status`, `created_at`, `updated_at`) VALUES (1, 1, 1, '好的好的!', '2025-09-24 16:56:39', 1, '2025-09-23 22:02:25', '2025-09-23 22:02:25'); +COMMIT; + +-- ---------------------------- +-- Table structure for favorites +-- ---------------------------- +DROP TABLE IF EXISTS `favorites`; +CREATE TABLE `favorites` ( + `id` bigint NOT NULL AUTO_INCREMENT COMMENT '收藏ID', + `user_id` bigint NOT NULL COMMENT '用户ID', + `product_id` bigint NOT NULL COMMENT '商品ID', + `created_at` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `updated_at` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + PRIMARY KEY (`id`), + UNIQUE KEY `uk_user_product` (`user_id`,`product_id`), + KEY `idx_user_id` (`user_id`), + KEY `idx_product_id` (`product_id`), + KEY `idx_created_at` (`created_at`) +) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='用户收藏表'; + +-- ---------------------------- +-- Records of favorites +-- ---------------------------- +BEGIN; +INSERT INTO `favorites` (`id`, `user_id`, `product_id`, `created_at`, `updated_at`) VALUES (1, 1, 2, '2025-09-25 03:51:39', '2025-09-25 03:51:39'); +COMMIT; + +-- ---------------------------- +-- Table structure for file_uploads +-- ---------------------------- +DROP TABLE IF EXISTS `file_uploads`; +CREATE TABLE `file_uploads` ( + `id` bigint NOT NULL AUTO_INCREMENT COMMENT '文件ID', + `original_name` varchar(255) NOT NULL COMMENT '原始文件名', + `file_name` varchar(255) NOT NULL COMMENT '存储文件名', + `file_path` varchar(255) NOT NULL COMMENT '文件路径', + `file_size` bigint NOT NULL COMMENT '文件大小', + `file_type` varchar(50) NOT NULL COMMENT '文件类型', + `uploader_id` bigint NOT NULL COMMENT '上传者ID', + `uploader_type` tinyint(1) NOT NULL COMMENT '上传者类型:1用户,2商家,3管理员', + `created_at` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + PRIMARY KEY (`id`), + KEY `idx_uploader` (`uploader_id`,`uploader_type`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='文件上传记录表'; + +-- ---------------------------- +-- Records of file_uploads +-- ---------------------------- +BEGIN; +COMMIT; + +-- ---------------------------- +-- Table structure for inventory +-- ---------------------------- +DROP TABLE IF EXISTS `inventory`; +CREATE TABLE `inventory` ( + `id` bigint NOT NULL AUTO_INCREMENT COMMENT '库存ID', + `product_id` bigint NOT NULL COMMENT '商品ID', + `stock_quantity` int NOT NULL DEFAULT '0' COMMENT '库存数量', + `warning_quantity` int DEFAULT '10' COMMENT '预警数量', + `updated_at` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + PRIMARY KEY (`id`), + UNIQUE KEY `uk_product_id` (`product_id`) +) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='库存管理表'; + +-- ---------------------------- +-- Records of inventory +-- ---------------------------- +BEGIN; +INSERT INTO `inventory` (`id`, `product_id`, `stock_quantity`, `warning_quantity`, `updated_at`) VALUES (1, 1, 36, 10, '2025-09-24 23:20:17'); +INSERT INTO `inventory` (`id`, `product_id`, `stock_quantity`, `warning_quantity`, `updated_at`) VALUES (2, 2, 97, 10, '2025-09-25 04:22:57'); +COMMIT; + +-- ---------------------------- +-- Table structure for logistics +-- ---------------------------- +DROP TABLE IF EXISTS `logistics`; +CREATE TABLE `logistics` ( + `id` bigint NOT NULL AUTO_INCREMENT COMMENT '物流ID', + `order_id` bigint NOT NULL COMMENT '订单ID', + `logistics_company_id` bigint NOT NULL COMMENT '物流公司ID', + `tracking_number` varchar(50) DEFAULT NULL COMMENT '运单号', + `status` tinyint(1) DEFAULT '1' COMMENT '物流状态:1待发货,2已发货,3运输中,4派送中,5已签收', + `current_location` varchar(100) DEFAULT NULL COMMENT '当前位置', + `estimated_delivery` datetime DEFAULT NULL COMMENT '预计送达时间', + `actual_delivery` datetime DEFAULT NULL COMMENT '实际送达时间', + `created_at` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `updated_at` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + PRIMARY KEY (`id`), + KEY `idx_order_id` (`order_id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='物流信息表'; + +-- ---------------------------- +-- Records of logistics +-- ---------------------------- +BEGIN; +COMMIT; + +-- ---------------------------- +-- Table structure for logistics_companies +-- ---------------------------- +DROP TABLE IF EXISTS `logistics_companies`; +CREATE TABLE `logistics_companies` ( + `id` bigint NOT NULL AUTO_INCREMENT COMMENT '物流公司ID', + `name` varchar(50) NOT NULL COMMENT '公司名称', + `code` varchar(20) NOT NULL COMMENT '公司代码', + `phone` varchar(20) DEFAULT NULL COMMENT '客服电话', + `website` varchar(100) DEFAULT NULL COMMENT '官网', + `status` tinyint(1) DEFAULT '1' COMMENT '状态:0禁用,1启用', + `created_at` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `updated_at` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + PRIMARY KEY (`id`), + UNIQUE KEY `uk_code` (`code`) +) ENGINE=InnoDB AUTO_INCREMENT=6 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='物流公司表'; + +-- ---------------------------- +-- Records of logistics_companies +-- ---------------------------- +BEGIN; +INSERT INTO `logistics_companies` (`id`, `name`, `code`, `phone`, `website`, `status`, `created_at`, `updated_at`) VALUES (1, '顺丰速运', 'SF', '95338', 'https://www.sf-express.com', 1, '2025-07-16 04:56:56', '2025-07-16 04:56:56'); +INSERT INTO `logistics_companies` (`id`, `name`, `code`, `phone`, `website`, `status`, `created_at`, `updated_at`) VALUES (2, '圆通快递', 'YTO', '95554', 'https://www.yto.net.cn', 1, '2025-07-16 04:56:56', '2025-07-16 04:56:56'); +INSERT INTO `logistics_companies` (`id`, `name`, `code`, `phone`, `website`, `status`, `created_at`, `updated_at`) VALUES (3, '中通快递', 'ZTO', '95311', 'https://www.zto.com', 1, '2025-07-16 04:56:56', '2025-07-16 04:56:56'); +INSERT INTO `logistics_companies` (`id`, `name`, `code`, `phone`, `website`, `status`, `created_at`, `updated_at`) VALUES (4, '申通快递', 'STO', '95543', 'https://www.sto.cn', 1, '2025-07-16 04:56:56', '2025-07-16 04:56:56'); +INSERT INTO `logistics_companies` (`id`, `name`, `code`, `phone`, `website`, `status`, `created_at`, `updated_at`) VALUES (5, '韵达快递', 'YD', '95546', 'https://www.yunda.com', 1, '2025-07-16 04:56:56', '2025-07-16 04:56:56'); +COMMIT; + +-- ---------------------------- +-- Table structure for merchants +-- ---------------------------- +DROP TABLE IF EXISTS `merchants`; +CREATE TABLE `merchants` ( + `id` bigint NOT NULL AUTO_INCREMENT COMMENT '商家ID', + `user_id` bigint NOT NULL COMMENT '关联用户ID', + `shop_name` varchar(100) NOT NULL COMMENT '店铺名称', + `business_license` varchar(255) DEFAULT NULL COMMENT '营业执照', + `legal_person` varchar(50) DEFAULT NULL COMMENT '法人', + `contact_phone` varchar(20) DEFAULT NULL COMMENT '联系电话', + `business_scope` varchar(500) DEFAULT NULL COMMENT '经营范围', + `address` varchar(200) DEFAULT NULL COMMENT '经营地址', + `status` tinyint(1) DEFAULT '0' COMMENT '状态:0待审核,1通过,2拒绝', + `verify_time` datetime DEFAULT NULL COMMENT '审核时间', + `created_at` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `updated_at` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + PRIMARY KEY (`id`), + KEY `idx_user_id` (`user_id`) +) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='商家信息表'; + +-- ---------------------------- +-- Records of merchants +-- ---------------------------- +BEGIN; +INSERT INTO `merchants` (`id`, `user_id`, `shop_name`, `business_license`, `legal_person`, `contact_phone`, `business_scope`, `address`, `status`, `verify_time`, `created_at`, `updated_at`) VALUES (1, 2, '乐天水果店铺', NULL, '狗王之王', '13823680701', '专营东南亚水果,东南亚水果最大的经销商!', '广东省深圳市南山区萧家大院', 1, '2025-07-17 23:19:06', '2025-07-16 12:55:48', '2025-07-17 23:19:06'); +COMMIT; + +-- ---------------------------- +-- Table structure for notifications +-- ---------------------------- +DROP TABLE IF EXISTS `notifications`; +CREATE TABLE `notifications` ( + `id` bigint NOT NULL AUTO_INCREMENT COMMENT '消息ID', + `user_id` bigint NOT NULL COMMENT '用户ID', + `title` varchar(200) NOT NULL COMMENT '标题', + `content` text NOT NULL COMMENT '内容', + `type` tinyint(1) DEFAULT '1' COMMENT '消息类型:1系统消息,2订单消息,3活动消息', + `is_read` tinyint(1) DEFAULT '0' COMMENT '是否已读:0未读,1已读', + `created_at` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + PRIMARY KEY (`id`), + KEY `idx_user_id` (`user_id`), + KEY `idx_is_read` (`is_read`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='站内消息表'; + +-- ---------------------------- +-- Records of notifications +-- ---------------------------- +BEGIN; +COMMIT; + +-- ---------------------------- +-- Table structure for order_items +-- ---------------------------- +DROP TABLE IF EXISTS `order_items`; +CREATE TABLE `order_items` ( + `id` bigint NOT NULL AUTO_INCREMENT COMMENT '订单项ID', + `order_id` bigint NOT NULL COMMENT '订单ID', + `product_id` bigint NOT NULL COMMENT '商品ID', + `product_name` varchar(200) NOT NULL COMMENT '商品名称', + `product_image` varchar(255) DEFAULT NULL COMMENT '商品图片', + `price` decimal(10,2) NOT NULL COMMENT '单价', + `quantity` int NOT NULL COMMENT '数量', + `total_amount` decimal(10,2) NOT NULL COMMENT '小计', + `created_at` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + PRIMARY KEY (`id`), + KEY `idx_order_id` (`order_id`), + KEY `idx_product_id` (`product_id`) +) ENGINE=InnoDB AUTO_INCREMENT=29 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='订单详情表'; + +-- ---------------------------- +-- Records of order_items +-- ---------------------------- +BEGIN; +INSERT INTO `order_items` (`id`, `order_id`, `product_id`, `product_name`, `product_image`, `price`, `quantity`, `total_amount`, `created_at`) VALUES (1, 9, 1, 'aaa', 'https://sunnyfarm-1328510989.cos.ap-guangzhou.myqcloud.com/uploads/2025/07/18/4eae7138-0760-41bd-b76d-a99630ce84fa.jpg', 30.00, 1, 30.00, '2025-08-26 08:39:36'); +INSERT INTO `order_items` (`id`, `order_id`, `product_id`, `product_name`, `product_image`, `price`, `quantity`, `total_amount`, `created_at`) VALUES (2, 10, 1, 'aaa', 'https://sunnyfarm-1328510989.cos.ap-guangzhou.myqcloud.com/uploads/2025/07/18/4eae7138-0760-41bd-b76d-a99630ce84fa.jpg', 30.00, 1, 30.00, '2025-08-26 08:40:07'); +INSERT INTO `order_items` (`id`, `order_id`, `product_id`, `product_name`, `product_image`, `price`, `quantity`, `total_amount`, `created_at`) VALUES (3, 11, 1, 'aaa', 'https://sunnyfarm-1328510989.cos.ap-guangzhou.myqcloud.com/uploads/2025/07/18/4eae7138-0760-41bd-b76d-a99630ce84fa.jpg', 30.00, 1, 30.00, '2025-08-26 08:40:15'); +INSERT INTO `order_items` (`id`, `order_id`, `product_id`, `product_name`, `product_image`, `price`, `quantity`, `total_amount`, `created_at`) VALUES (4, 12, 1, 'aaa', 'https://sunnyfarm-1328510989.cos.ap-guangzhou.myqcloud.com/uploads/2025/07/18/4eae7138-0760-41bd-b76d-a99630ce84fa.jpg', 30.00, 1, 30.00, '2025-08-26 08:46:04'); +INSERT INTO `order_items` (`id`, `order_id`, `product_id`, `product_name`, `product_image`, `price`, `quantity`, `total_amount`, `created_at`) VALUES (5, 13, 1, 'aaa', 'https://sunnyfarm-1328510989.cos.ap-guangzhou.myqcloud.com/uploads/2025/07/18/4eae7138-0760-41bd-b76d-a99630ce84fa.jpg', 30.00, 1, 30.00, '2025-08-26 08:46:08'); +INSERT INTO `order_items` (`id`, `order_id`, `product_id`, `product_name`, `product_image`, `price`, `quantity`, `total_amount`, `created_at`) VALUES (6, 14, 1, 'aaa', 'https://sunnyfarm-1328510989.cos.ap-guangzhou.myqcloud.com/uploads/2025/07/18/4eae7138-0760-41bd-b76d-a99630ce84fa.jpg', 30.00, 1, 30.00, '2025-08-26 08:47:45'); +INSERT INTO `order_items` (`id`, `order_id`, `product_id`, `product_name`, `product_image`, `price`, `quantity`, `total_amount`, `created_at`) VALUES (7, 15, 1, 'aaa', 'https://sunnyfarm-1328510989.cos.ap-guangzhou.myqcloud.com/uploads/2025/07/18/4eae7138-0760-41bd-b76d-a99630ce84fa.jpg', 30.00, 1, 30.00, '2025-08-26 08:48:35'); +INSERT INTO `order_items` (`id`, `order_id`, `product_id`, `product_name`, `product_image`, `price`, `quantity`, `total_amount`, `created_at`) VALUES (8, 16, 1, 'aaa', 'https://sunnyfarm-1328510989.cos.ap-guangzhou.myqcloud.com/uploads/2025/07/18/4eae7138-0760-41bd-b76d-a99630ce84fa.jpg', 30.00, 1, 30.00, '2025-08-26 09:16:08'); +INSERT INTO `order_items` (`id`, `order_id`, `product_id`, `product_name`, `product_image`, `price`, `quantity`, `total_amount`, `created_at`) VALUES (9, 17, 1, 'aaa', 'https://sunnyfarm-1328510989.cos.ap-guangzhou.myqcloud.com/uploads/2025/07/18/4eae7138-0760-41bd-b76d-a99630ce84fa.jpg', 30.00, 1, 30.00, '2025-08-26 09:21:44'); +INSERT INTO `order_items` (`id`, `order_id`, `product_id`, `product_name`, `product_image`, `price`, `quantity`, `total_amount`, `created_at`) VALUES (10, 18, 1, 'aaa', 'https://sunnyfarm-1328510989.cos.ap-guangzhou.myqcloud.com/uploads/2025/07/18/4eae7138-0760-41bd-b76d-a99630ce84fa.jpg', 30.00, 1, 30.00, '2025-08-26 09:24:37'); +INSERT INTO `order_items` (`id`, `order_id`, `product_id`, `product_name`, `product_image`, `price`, `quantity`, `total_amount`, `created_at`) VALUES (11, 19, 1, 'aaa', 'https://sunnyfarm-1328510989.cos.ap-guangzhou.myqcloud.com/uploads/2025/07/18/4eae7138-0760-41bd-b76d-a99630ce84fa.jpg', 30.00, 1, 30.00, '2025-08-26 09:25:10'); +INSERT INTO `order_items` (`id`, `order_id`, `product_id`, `product_name`, `product_image`, `price`, `quantity`, `total_amount`, `created_at`) VALUES (12, 20, 1, 'aaa', 'https://sunnyfarm-1328510989.cos.ap-guangzhou.myqcloud.com/uploads/2025/07/18/4eae7138-0760-41bd-b76d-a99630ce84fa.jpg', 30.00, 1, 30.00, '2025-08-26 09:26:55'); +INSERT INTO `order_items` (`id`, `order_id`, `product_id`, `product_name`, `product_image`, `price`, `quantity`, `total_amount`, `created_at`) VALUES (13, 21, 1, 'aaa', 'https://sunnyfarm-1328510989.cos.ap-guangzhou.myqcloud.com/uploads/2025/07/18/4eae7138-0760-41bd-b76d-a99630ce84fa.jpg', 30.00, 1, 30.00, '2025-08-26 09:27:32'); +INSERT INTO `order_items` (`id`, `order_id`, `product_id`, `product_name`, `product_image`, `price`, `quantity`, `total_amount`, `created_at`) VALUES (14, 22, 1, 'aaa', 'https://sunnyfarm-1328510989.cos.ap-guangzhou.myqcloud.com/uploads/2025/07/18/4eae7138-0760-41bd-b76d-a99630ce84fa.jpg', 30.00, 1, 30.00, '2025-08-26 09:32:17'); +INSERT INTO `order_items` (`id`, `order_id`, `product_id`, `product_name`, `product_image`, `price`, `quantity`, `total_amount`, `created_at`) VALUES (15, 23, 1, 'aaa', 'https://sunnyfarm-1328510989.cos.ap-guangzhou.myqcloud.com/uploads/2025/07/18/4eae7138-0760-41bd-b76d-a99630ce84fa.jpg', 30.00, 1, 30.00, '2025-08-26 09:45:26'); +INSERT INTO `order_items` (`id`, `order_id`, `product_id`, `product_name`, `product_image`, `price`, `quantity`, `total_amount`, `created_at`) VALUES (16, 24, 1, 'aaa', 'https://sunnyfarm-1328510989.cos.ap-guangzhou.myqcloud.com/uploads/2025/07/18/4eae7138-0760-41bd-b76d-a99630ce84fa.jpg', 30.00, 1, 30.00, '2025-08-26 09:53:15'); +INSERT INTO `order_items` (`id`, `order_id`, `product_id`, `product_name`, `product_image`, `price`, `quantity`, `total_amount`, `created_at`) VALUES (17, 25, 1, 'aaa', 'https://sunnyfarm-1328510989.cos.ap-guangzhou.myqcloud.com/uploads/2025/07/18/4eae7138-0760-41bd-b76d-a99630ce84fa.jpg', 30.00, 1, 30.00, '2025-08-26 10:47:44'); +INSERT INTO `order_items` (`id`, `order_id`, `product_id`, `product_name`, `product_image`, `price`, `quantity`, `total_amount`, `created_at`) VALUES (18, 26, 1, 'aaa', 'https://sunnyfarm-1328510989.cos.ap-guangzhou.myqcloud.com/uploads/2025/07/18/4eae7138-0760-41bd-b76d-a99630ce84fa.jpg', 30.00, 1, 30.00, '2025-08-26 10:59:37'); +INSERT INTO `order_items` (`id`, `order_id`, `product_id`, `product_name`, `product_image`, `price`, `quantity`, `total_amount`, `created_at`) VALUES (19, 27, 1, 'aaa', 'https://sunnyfarm-1328510989.cos.ap-guangzhou.myqcloud.com/uploads/2025/07/18/4eae7138-0760-41bd-b76d-a99630ce84fa.jpg', 30.00, 1, 30.00, '2025-08-26 11:04:08'); +INSERT INTO `order_items` (`id`, `order_id`, `product_id`, `product_name`, `product_image`, `price`, `quantity`, `total_amount`, `created_at`) VALUES (20, 28, 1, 'aaa', 'https://sunnyfarm-1328510989.cos.ap-guangzhou.myqcloud.com/uploads/2025/07/18/4eae7138-0760-41bd-b76d-a99630ce84fa.jpg', 30.00, 1, 30.00, '2025-08-26 11:12:12'); +INSERT INTO `order_items` (`id`, `order_id`, `product_id`, `product_name`, `product_image`, `price`, `quantity`, `total_amount`, `created_at`) VALUES (21, 29, 1, 'aaa', 'https://sunnyfarm-1328510989.cos.ap-guangzhou.myqcloud.com/uploads/2025/07/18/4eae7138-0760-41bd-b76d-a99630ce84fa.jpg', 30.00, 2, 60.00, '2025-09-05 18:34:38'); +INSERT INTO `order_items` (`id`, `order_id`, `product_id`, `product_name`, `product_image`, `price`, `quantity`, `total_amount`, `created_at`) VALUES (22, 30, 1, 'aaa', 'https://sunnyfarm-1328510989.cos.ap-guangzhou.myqcloud.com/uploads/2025/07/18/4eae7138-0760-41bd-b76d-a99630ce84fa.jpg', 30.00, 1, 30.00, '2025-09-17 17:08:51'); +INSERT INTO `order_items` (`id`, `order_id`, `product_id`, `product_name`, `product_image`, `price`, `quantity`, `total_amount`, `created_at`) VALUES (23, 31, 2, '云南有机萝卜', 'https://cdnsunnyfarm.sqai.online/uploads/2025/09/23/42f966e9-d0b4-419d-9362-b75f41a9067b.jpg', 15.00, 1, 15.00, '2025-09-24 15:59:53'); +INSERT INTO `order_items` (`id`, `order_id`, `product_id`, `product_name`, `product_image`, `price`, `quantity`, `total_amount`, `created_at`) VALUES (24, 32, 2, '云南有机萝卜', 'https://cdnsunnyfarm.sqai.online/uploads/2025/09/23/42f966e9-d0b4-419d-9362-b75f41a9067b.jpg', 15.00, 1, 15.00, '2025-09-24 23:10:54'); +INSERT INTO `order_items` (`id`, `order_id`, `product_id`, `product_name`, `product_image`, `price`, `quantity`, `total_amount`, `created_at`) VALUES (25, 33, 1, 'aaa', 'https://sunnyfarm-1328510989.cos.ap-guangzhou.myqcloud.com/uploads/2025/07/18/4eae7138-0760-41bd-b76d-a99630ce84fa.jpg', 30.00, 1, 30.00, '2025-09-24 23:20:19'); +INSERT INTO `order_items` (`id`, `order_id`, `product_id`, `product_name`, `product_image`, `price`, `quantity`, `total_amount`, `created_at`) VALUES (26, 34, 2, '云南有机萝卜', 'https://cdnsunnyfarm.sqai.online/uploads/2025/09/23/42f966e9-d0b4-419d-9362-b75f41a9067b.jpg', 15.00, 1, 15.00, '2025-09-25 04:08:23'); +INSERT INTO `order_items` (`id`, `order_id`, `product_id`, `product_name`, `product_image`, `price`, `quantity`, `total_amount`, `created_at`) VALUES (27, 35, 2, '云南有机萝卜', 'https://cdnsunnyfarm.sqai.online/uploads/2025/09/23/42f966e9-d0b4-419d-9362-b75f41a9067b.jpg', 15.00, 1, 15.00, '2025-09-25 04:14:08'); +INSERT INTO `order_items` (`id`, `order_id`, `product_id`, `product_name`, `product_image`, `price`, `quantity`, `total_amount`, `created_at`) VALUES (28, 36, 2, '云南有机萝卜', 'https://cdnsunnyfarm.sqai.online/uploads/2025/09/23/42f966e9-d0b4-419d-9362-b75f41a9067b.jpg', 15.00, 1, 15.00, '2025-09-25 04:22:57'); +COMMIT; + +-- ---------------------------- +-- Table structure for order_status_logs +-- ---------------------------- +DROP TABLE IF EXISTS `order_status_logs`; +CREATE TABLE `order_status_logs` ( + `id` bigint NOT NULL AUTO_INCREMENT COMMENT '日志ID', + `order_id` bigint NOT NULL COMMENT '订单ID', + `status` tinyint(1) NOT NULL COMMENT '状态', + `remark` varchar(255) DEFAULT NULL COMMENT '备注', + `operator_id` bigint DEFAULT NULL COMMENT '操作人ID', + `operator_type` tinyint(1) DEFAULT '1' COMMENT '操作人类型:1用户,2商家,3管理员', + `created_at` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + PRIMARY KEY (`id`), + KEY `idx_order_id` (`order_id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='订单状态变更日志表'; + +-- ---------------------------- +-- Records of order_status_logs +-- ---------------------------- +BEGIN; +COMMIT; + +-- ---------------------------- +-- Table structure for orders +-- ---------------------------- +DROP TABLE IF EXISTS `orders`; +CREATE TABLE `orders` ( + `id` bigint NOT NULL AUTO_INCREMENT COMMENT '订单ID', + `order_no` varchar(32) NOT NULL COMMENT '订单号', + `user_id` bigint NOT NULL COMMENT '用户ID', + `merchant_id` bigint NOT NULL COMMENT '商家ID', + `total_amount` decimal(10,2) NOT NULL COMMENT '总金额', + `discount_amount` decimal(10,2) DEFAULT '0.00' COMMENT '优惠金额', + `actual_amount` decimal(10,2) NOT NULL COMMENT '实付金额', + `status` tinyint(1) DEFAULT '1' COMMENT '订单状态:1待支付,2已支付,3已发货,4已完成,5已取消,6已退款', + `consignee` varchar(50) NOT NULL COMMENT '收件人', + `phone` varchar(20) NOT NULL COMMENT '联系电话', + `address` varchar(200) NOT NULL COMMENT '收货地址', + `remark` varchar(500) DEFAULT NULL COMMENT '备注', + `created_at` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `updated_at` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + `pay_time` datetime DEFAULT NULL COMMENT '支付时间', + `pay_transaction_id` varchar(64) DEFAULT NULL COMMENT '支付交易ID', + `ship_time` datetime DEFAULT NULL COMMENT '发货时间', + `ship_company` varchar(50) DEFAULT NULL COMMENT '物流公司', + `ship_no` varchar(50) DEFAULT NULL COMMENT '运单号', + `receive_time` datetime DEFAULT NULL COMMENT '收货时间', + `finish_time` datetime DEFAULT NULL COMMENT '完成时间', + `cancel_time` datetime DEFAULT NULL COMMENT '取消时间', + `cancel_reason` varchar(500) DEFAULT NULL COMMENT '取消原因', + `refund_reason` varchar(500) DEFAULT NULL COMMENT '退款原因', + `refund_amount` decimal(10,2) DEFAULT NULL COMMENT '退款金额', + `refund_time` datetime DEFAULT NULL COMMENT '退款时间', + PRIMARY KEY (`id`), + UNIQUE KEY `uk_order_no` (`order_no`), + KEY `idx_user_id` (`user_id`), + KEY `idx_merchant_id` (`merchant_id`), + KEY `idx_status` (`status`) +) ENGINE=InnoDB AUTO_INCREMENT=37 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='订单主表'; + +-- ---------------------------- +-- Records of orders +-- ---------------------------- +BEGIN; +INSERT INTO `orders` (`id`, `order_no`, `user_id`, `merchant_id`, `total_amount`, `discount_amount`, `actual_amount`, `status`, `consignee`, `phone`, `address`, `remark`, `created_at`, `updated_at`, `pay_time`, `pay_transaction_id`, `ship_time`, `ship_company`, `ship_no`, `receive_time`, `finish_time`, `cancel_time`, `cancel_reason`, `refund_reason`, `refund_amount`, `refund_time`) VALUES (9, 'O202508260839360001', 1, 1, 30.00, 0.00, 30.00, 1, '施琦大王', '17816786725', '广东省深圳市南山区政府大院2楼', '', '2025-08-26 08:39:36', '2025-08-26 08:39:36', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL); +INSERT INTO `orders` (`id`, `order_no`, `user_id`, `merchant_id`, `total_amount`, `discount_amount`, `actual_amount`, `status`, `consignee`, `phone`, `address`, `remark`, `created_at`, `updated_at`, `pay_time`, `pay_transaction_id`, `ship_time`, `ship_company`, `ship_no`, `receive_time`, `finish_time`, `cancel_time`, `cancel_reason`, `refund_reason`, `refund_amount`, `refund_time`) VALUES (10, 'O202508260840070002', 1, 1, 30.00, 0.00, 30.00, 1, '施琦大王', '17816786725', '广东省深圳市南山区政府大院2楼', '', '2025-08-26 08:40:07', '2025-08-26 08:40:07', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL); +INSERT INTO `orders` (`id`, `order_no`, `user_id`, `merchant_id`, `total_amount`, `discount_amount`, `actual_amount`, `status`, `consignee`, `phone`, `address`, `remark`, `created_at`, `updated_at`, `pay_time`, `pay_transaction_id`, `ship_time`, `ship_company`, `ship_no`, `receive_time`, `finish_time`, `cancel_time`, `cancel_reason`, `refund_reason`, `refund_amount`, `refund_time`) VALUES (11, 'O202508260840150003', 1, 1, 30.00, 0.00, 30.00, 1, '施琦大王', '17816786725', '广东省深圳市南山区政府大院2楼', '', '2025-08-26 08:40:15', '2025-08-26 08:40:15', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL); +INSERT INTO `orders` (`id`, `order_no`, `user_id`, `merchant_id`, `total_amount`, `discount_amount`, `actual_amount`, `status`, `consignee`, `phone`, `address`, `remark`, `created_at`, `updated_at`, `pay_time`, `pay_transaction_id`, `ship_time`, `ship_company`, `ship_no`, `receive_time`, `finish_time`, `cancel_time`, `cancel_reason`, `refund_reason`, `refund_amount`, `refund_time`) VALUES (12, 'O202508260846030001', 1, 1, 30.00, 0.00, 30.00, 1, '施琦大王', '17816786725', '广东省深圳市南山区政府大院2楼', '', '2025-08-26 08:46:04', '2025-08-26 08:46:04', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL); +INSERT INTO `orders` (`id`, `order_no`, `user_id`, `merchant_id`, `total_amount`, `discount_amount`, `actual_amount`, `status`, `consignee`, `phone`, `address`, `remark`, `created_at`, `updated_at`, `pay_time`, `pay_transaction_id`, `ship_time`, `ship_company`, `ship_no`, `receive_time`, `finish_time`, `cancel_time`, `cancel_reason`, `refund_reason`, `refund_amount`, `refund_time`) VALUES (13, 'O202508260846080002', 1, 1, 30.00, 0.00, 30.00, 1, '施琦大王', '17816786725', '广东省深圳市南山区政府大院2楼', '', '2025-08-26 08:46:08', '2025-08-26 08:46:08', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL); +INSERT INTO `orders` (`id`, `order_no`, `user_id`, `merchant_id`, `total_amount`, `discount_amount`, `actual_amount`, `status`, `consignee`, `phone`, `address`, `remark`, `created_at`, `updated_at`, `pay_time`, `pay_transaction_id`, `ship_time`, `ship_company`, `ship_no`, `receive_time`, `finish_time`, `cancel_time`, `cancel_reason`, `refund_reason`, `refund_amount`, `refund_time`) VALUES (14, 'O202508260847450003', 1, 1, 30.00, 0.00, 30.00, 1, '施琦大王', '17816786725', '广东省深圳市南山区政府大院2楼', '', '2025-08-26 08:47:45', '2025-08-26 08:47:45', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL); +INSERT INTO `orders` (`id`, `order_no`, `user_id`, `merchant_id`, `total_amount`, `discount_amount`, `actual_amount`, `status`, `consignee`, `phone`, `address`, `remark`, `created_at`, `updated_at`, `pay_time`, `pay_transaction_id`, `ship_time`, `ship_company`, `ship_no`, `receive_time`, `finish_time`, `cancel_time`, `cancel_reason`, `refund_reason`, `refund_amount`, `refund_time`) VALUES (15, 'O202508260848350004', 1, 1, 30.00, 0.00, 30.00, 1, '施琦大王', '17816786725', '广东省深圳市南山区政府大院2楼', '', '2025-08-26 08:48:35', '2025-08-26 08:48:35', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL); +INSERT INTO `orders` (`id`, `order_no`, `user_id`, `merchant_id`, `total_amount`, `discount_amount`, `actual_amount`, `status`, `consignee`, `phone`, `address`, `remark`, `created_at`, `updated_at`, `pay_time`, `pay_transaction_id`, `ship_time`, `ship_company`, `ship_no`, `receive_time`, `finish_time`, `cancel_time`, `cancel_reason`, `refund_reason`, `refund_amount`, `refund_time`) VALUES (16, 'O202508260916080001', 1, 1, 30.00, 0.00, 30.00, 1, '施琦大王', '17816786725', '广东省深圳市南山区政府大院2楼', '', '2025-08-26 09:16:08', '2025-08-26 09:16:08', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL); +INSERT INTO `orders` (`id`, `order_no`, `user_id`, `merchant_id`, `total_amount`, `discount_amount`, `actual_amount`, `status`, `consignee`, `phone`, `address`, `remark`, `created_at`, `updated_at`, `pay_time`, `pay_transaction_id`, `ship_time`, `ship_company`, `ship_no`, `receive_time`, `finish_time`, `cancel_time`, `cancel_reason`, `refund_reason`, `refund_amount`, `refund_time`) VALUES (17, 'O202508260921440001', 1, 1, 30.00, 0.00, 30.00, 1, '施琦大王', '17816786725', '广东省深圳市南山区政府大院2楼', '', '2025-08-26 09:21:44', '2025-08-26 09:21:44', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL); +INSERT INTO `orders` (`id`, `order_no`, `user_id`, `merchant_id`, `total_amount`, `discount_amount`, `actual_amount`, `status`, `consignee`, `phone`, `address`, `remark`, `created_at`, `updated_at`, `pay_time`, `pay_transaction_id`, `ship_time`, `ship_company`, `ship_no`, `receive_time`, `finish_time`, `cancel_time`, `cancel_reason`, `refund_reason`, `refund_amount`, `refund_time`) VALUES (18, 'O202508260924370002', 1, 1, 30.00, 0.00, 30.00, 1, '施琦大王', '17816786725', '广东省深圳市南山区政府大院2楼', '', '2025-08-26 09:24:37', '2025-08-26 09:24:37', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL); +INSERT INTO `orders` (`id`, `order_no`, `user_id`, `merchant_id`, `total_amount`, `discount_amount`, `actual_amount`, `status`, `consignee`, `phone`, `address`, `remark`, `created_at`, `updated_at`, `pay_time`, `pay_transaction_id`, `ship_time`, `ship_company`, `ship_no`, `receive_time`, `finish_time`, `cancel_time`, `cancel_reason`, `refund_reason`, `refund_amount`, `refund_time`) VALUES (19, 'O202508260925100001', 1, 1, 30.00, 0.00, 30.00, 1, '施琦大王', '17816786725', '广东省深圳市南山区政府大院2楼', '', '2025-08-26 09:25:10', '2025-08-26 09:25:10', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL); +INSERT INTO `orders` (`id`, `order_no`, `user_id`, `merchant_id`, `total_amount`, `discount_amount`, `actual_amount`, `status`, `consignee`, `phone`, `address`, `remark`, `created_at`, `updated_at`, `pay_time`, `pay_transaction_id`, `ship_time`, `ship_company`, `ship_no`, `receive_time`, `finish_time`, `cancel_time`, `cancel_reason`, `refund_reason`, `refund_amount`, `refund_time`) VALUES (20, 'O202508260926550001', 1, 1, 30.00, 0.00, 30.00, 4, '施琦大王', '17816786725', '广东省深圳市南山区政府大院2楼', '', '2025-08-26 09:26:55', '2025-08-26 09:26:55', '2025-08-26 09:27:01', 'PAY_1756171621066', '2025-09-24 22:18:29', '圆通快递', 'YT34289742332', '2025-09-24 22:18:38', '2025-09-24 22:18:38', NULL, NULL, NULL, NULL, NULL); +INSERT INTO `orders` (`id`, `order_no`, `user_id`, `merchant_id`, `total_amount`, `discount_amount`, `actual_amount`, `status`, `consignee`, `phone`, `address`, `remark`, `created_at`, `updated_at`, `pay_time`, `pay_transaction_id`, `ship_time`, `ship_company`, `ship_no`, `receive_time`, `finish_time`, `cancel_time`, `cancel_reason`, `refund_reason`, `refund_amount`, `refund_time`) VALUES (21, 'O202508260927310002', 1, 1, 30.00, 0.00, 30.00, 4, '施琦大王', '17816786725', '广东省深圳市南山区政府大院2楼', '', '2025-08-26 09:27:32', '2025-08-26 09:27:32', '2025-08-26 09:27:46', 'PAY_1756171665901', '2025-09-24 20:50:56', '顺丰快递', 'sf1234543654645', '2025-09-24 20:51:03', '2025-09-24 20:51:03', NULL, NULL, NULL, NULL, NULL); +INSERT INTO `orders` (`id`, `order_no`, `user_id`, `merchant_id`, `total_amount`, `discount_amount`, `actual_amount`, `status`, `consignee`, `phone`, `address`, `remark`, `created_at`, `updated_at`, `pay_time`, `pay_transaction_id`, `ship_time`, `ship_company`, `ship_no`, `receive_time`, `finish_time`, `cancel_time`, `cancel_reason`, `refund_reason`, `refund_amount`, `refund_time`) VALUES (22, 'O202508260932160001', 1, 1, 30.00, 0.00, 30.00, 5, '施琦大王', '17816786725', '广东省深圳市南山区政府大院2楼', '', '2025-08-26 09:32:16', '2025-08-26 09:32:16', NULL, NULL, NULL, NULL, NULL, NULL, NULL, '2025-09-23 20:25:55', '用户取消', NULL, NULL, NULL); +INSERT INTO `orders` (`id`, `order_no`, `user_id`, `merchant_id`, `total_amount`, `discount_amount`, `actual_amount`, `status`, `consignee`, `phone`, `address`, `remark`, `created_at`, `updated_at`, `pay_time`, `pay_transaction_id`, `ship_time`, `ship_company`, `ship_no`, `receive_time`, `finish_time`, `cancel_time`, `cancel_reason`, `refund_reason`, `refund_amount`, `refund_time`) VALUES (23, 'O202508260945240001', 1, 1, 30.00, 0.00, 30.00, 5, '施琦大王', '17816786725', '广东省深圳市南山区政府大院2楼', '', '2025-08-26 09:45:26', '2025-08-26 09:45:26', NULL, NULL, NULL, NULL, NULL, NULL, NULL, '2025-09-23 20:25:51', '用户取消', NULL, NULL, NULL); +INSERT INTO `orders` (`id`, `order_no`, `user_id`, `merchant_id`, `total_amount`, `discount_amount`, `actual_amount`, `status`, `consignee`, `phone`, `address`, `remark`, `created_at`, `updated_at`, `pay_time`, `pay_transaction_id`, `ship_time`, `ship_company`, `ship_no`, `receive_time`, `finish_time`, `cancel_time`, `cancel_reason`, `refund_reason`, `refund_amount`, `refund_time`) VALUES (24, 'O202508260953130001', 1, 1, 30.00, 0.00, 30.00, 5, '施琦大王', '17816786725', '广东省深圳市南山区政府大院2楼', '', '2025-08-26 09:53:15', '2025-08-26 09:53:15', NULL, NULL, NULL, NULL, NULL, NULL, NULL, '2025-09-23 20:25:49', '用户取消', NULL, NULL, NULL); +INSERT INTO `orders` (`id`, `order_no`, `user_id`, `merchant_id`, `total_amount`, `discount_amount`, `actual_amount`, `status`, `consignee`, `phone`, `address`, `remark`, `created_at`, `updated_at`, `pay_time`, `pay_transaction_id`, `ship_time`, `ship_company`, `ship_no`, `receive_time`, `finish_time`, `cancel_time`, `cancel_reason`, `refund_reason`, `refund_amount`, `refund_time`) VALUES (25, 'O202508261047420001', 1, 1, 30.00, 0.00, 30.00, 5, '施琦大王', '17816786725', '广东省深圳市南山区政府大院2楼', '', '2025-08-26 10:47:44', '2025-08-26 10:47:44', NULL, NULL, NULL, NULL, NULL, NULL, NULL, '2025-09-23 20:25:52', '用户取消', NULL, NULL, NULL); +INSERT INTO `orders` (`id`, `order_no`, `user_id`, `merchant_id`, `total_amount`, `discount_amount`, `actual_amount`, `status`, `consignee`, `phone`, `address`, `remark`, `created_at`, `updated_at`, `pay_time`, `pay_transaction_id`, `ship_time`, `ship_company`, `ship_no`, `receive_time`, `finish_time`, `cancel_time`, `cancel_reason`, `refund_reason`, `refund_amount`, `refund_time`) VALUES (26, 'O202508261059350001', 1, 1, 30.00, 0.00, 30.00, 5, '施琦大王', '17816786725', '广东省深圳市南山区政府大院2楼', '', '2025-08-26 10:59:37', '2025-08-26 10:59:37', NULL, NULL, NULL, NULL, NULL, NULL, NULL, '2025-09-23 20:25:46', '用户取消', NULL, NULL, NULL); +INSERT INTO `orders` (`id`, `order_no`, `user_id`, `merchant_id`, `total_amount`, `discount_amount`, `actual_amount`, `status`, `consignee`, `phone`, `address`, `remark`, `created_at`, `updated_at`, `pay_time`, `pay_transaction_id`, `ship_time`, `ship_company`, `ship_no`, `receive_time`, `finish_time`, `cancel_time`, `cancel_reason`, `refund_reason`, `refund_amount`, `refund_time`) VALUES (27, 'O202508261104060001', 1, 1, 30.00, 0.00, 30.00, 5, '施琦大王', '17816786725', '广东省深圳市南山区政府大院2楼', '', '2025-08-26 11:04:08', '2025-08-26 11:04:08', NULL, NULL, NULL, NULL, NULL, NULL, NULL, '2025-09-23 20:25:44', '用户取消', NULL, NULL, NULL); +INSERT INTO `orders` (`id`, `order_no`, `user_id`, `merchant_id`, `total_amount`, `discount_amount`, `actual_amount`, `status`, `consignee`, `phone`, `address`, `remark`, `created_at`, `updated_at`, `pay_time`, `pay_transaction_id`, `ship_time`, `ship_company`, `ship_no`, `receive_time`, `finish_time`, `cancel_time`, `cancel_reason`, `refund_reason`, `refund_amount`, `refund_time`) VALUES (28, 'O202508261112090001', 1, 1, 30.00, 0.00, 30.00, 5, '施琦大王', '17816786725', '广东省深圳市南山区政府大院2楼', '', '2025-08-26 11:12:12', '2025-08-26 11:12:12', NULL, NULL, NULL, NULL, NULL, NULL, NULL, '2025-09-23 20:25:41', '用户取消', NULL, NULL, NULL); +INSERT INTO `orders` (`id`, `order_no`, `user_id`, `merchant_id`, `total_amount`, `discount_amount`, `actual_amount`, `status`, `consignee`, `phone`, `address`, `remark`, `created_at`, `updated_at`, `pay_time`, `pay_transaction_id`, `ship_time`, `ship_company`, `ship_no`, `receive_time`, `finish_time`, `cancel_time`, `cancel_reason`, `refund_reason`, `refund_amount`, `refund_time`) VALUES (29, 'O202509051834590001', 1, 1, 60.00, 0.00, 60.00, 5, '施琦大王', '17816786725', '广东省深圳市南山区政府大院2楼', '', '2025-09-05 18:34:38', '2025-09-05 18:34:38', NULL, NULL, NULL, NULL, NULL, NULL, NULL, '2025-09-23 20:25:39', '用户取消', NULL, NULL, NULL); +INSERT INTO `orders` (`id`, `order_no`, `user_id`, `merchant_id`, `total_amount`, `discount_amount`, `actual_amount`, `status`, `consignee`, `phone`, `address`, `remark`, `created_at`, `updated_at`, `pay_time`, `pay_transaction_id`, `ship_time`, `ship_company`, `ship_no`, `receive_time`, `finish_time`, `cancel_time`, `cancel_reason`, `refund_reason`, `refund_amount`, `refund_time`) VALUES (30, 'O202509171708500001', 1, 1, 30.00, 0.00, 30.00, 5, '施琦大王', '17816786725', '广东省深圳市南山区政府大院2楼', '', '2025-09-17 17:08:51', '2025-09-17 17:08:51', NULL, NULL, NULL, NULL, NULL, NULL, NULL, '2025-09-23 20:25:36', '用户取消', NULL, NULL, NULL); +INSERT INTO `orders` (`id`, `order_no`, `user_id`, `merchant_id`, `total_amount`, `discount_amount`, `actual_amount`, `status`, `consignee`, `phone`, `address`, `remark`, `created_at`, `updated_at`, `pay_time`, `pay_transaction_id`, `ship_time`, `ship_company`, `ship_no`, `receive_time`, `finish_time`, `cancel_time`, `cancel_reason`, `refund_reason`, `refund_amount`, `refund_time`) VALUES (31, 'O202509241559530001', 1, 1, 15.00, 0.00, 15.00, 2, '施琦大王', '17816786725', '广东省深圳市南山区政府大院2楼', '', '2025-09-24 15:59:53', '2025-09-24 23:01:47', '2025-09-24 23:01:47', '2025092422001418170507460642', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL); +INSERT INTO `orders` (`id`, `order_no`, `user_id`, `merchant_id`, `total_amount`, `discount_amount`, `actual_amount`, `status`, `consignee`, `phone`, `address`, `remark`, `created_at`, `updated_at`, `pay_time`, `pay_transaction_id`, `ship_time`, `ship_company`, `ship_no`, `receive_time`, `finish_time`, `cancel_time`, `cancel_reason`, `refund_reason`, `refund_amount`, `refund_time`) VALUES (32, 'O202509242310530001', 1, 1, 15.00, 0.00, 15.00, 2, '施琦大王', '17816786725', '广东省深圳市南山区政府大院2楼', '', '2025-09-24 23:10:54', '2025-09-24 23:19:12', '2025-09-24 23:19:12', '待补充支付宝交易号', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL); +INSERT INTO `orders` (`id`, `order_no`, `user_id`, `merchant_id`, `total_amount`, `discount_amount`, `actual_amount`, `status`, `consignee`, `phone`, `address`, `remark`, `created_at`, `updated_at`, `pay_time`, `pay_transaction_id`, `ship_time`, `ship_company`, `ship_no`, `receive_time`, `finish_time`, `cancel_time`, `cancel_reason`, `refund_reason`, `refund_amount`, `refund_time`) VALUES (33, 'O202509242320170001', 1, 1, 30.00, 0.00, 30.00, 1, '施琦大王', '17816786725', '广东省深圳市南山区政府大院2楼', '', '2025-09-24 23:20:19', '2025-09-24 23:20:19', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL); +INSERT INTO `orders` (`id`, `order_no`, `user_id`, `merchant_id`, `total_amount`, `discount_amount`, `actual_amount`, `status`, `consignee`, `phone`, `address`, `remark`, `created_at`, `updated_at`, `pay_time`, `pay_transaction_id`, `ship_time`, `ship_company`, `ship_no`, `receive_time`, `finish_time`, `cancel_time`, `cancel_reason`, `refund_reason`, `refund_amount`, `refund_time`) VALUES (34, 'O202509250408230001', 1, 1, 15.00, 0.00, 15.00, 5, '施琦大王', '17816786725', '广东省深圳市南山区政府大院2楼', '', '2025-09-25 04:08:23', '2025-09-25 04:08:23', NULL, NULL, NULL, NULL, NULL, NULL, NULL, '2025-09-25 04:14:01', '用户取消', NULL, NULL, NULL); +INSERT INTO `orders` (`id`, `order_no`, `user_id`, `merchant_id`, `total_amount`, `discount_amount`, `actual_amount`, `status`, `consignee`, `phone`, `address`, `remark`, `created_at`, `updated_at`, `pay_time`, `pay_transaction_id`, `ship_time`, `ship_company`, `ship_no`, `receive_time`, `finish_time`, `cancel_time`, `cancel_reason`, `refund_reason`, `refund_amount`, `refund_time`) VALUES (35, 'O202509250414080002', 1, 1, 15.00, 0.00, 15.00, 5, '施琦大王', '17816786725', '广东省深圳市南山区政府大院2楼', '', '2025-09-25 04:14:08', '2025-09-25 04:14:08', NULL, NULL, NULL, NULL, NULL, NULL, NULL, '2025-09-25 04:22:50', '用户取消', NULL, NULL, NULL); +INSERT INTO `orders` (`id`, `order_no`, `user_id`, `merchant_id`, `total_amount`, `discount_amount`, `actual_amount`, `status`, `consignee`, `phone`, `address`, `remark`, `created_at`, `updated_at`, `pay_time`, `pay_transaction_id`, `ship_time`, `ship_company`, `ship_no`, `receive_time`, `finish_time`, `cancel_time`, `cancel_reason`, `refund_reason`, `refund_amount`, `refund_time`) VALUES (36, 'O202509250422570003', 1, 1, 15.00, 0.00, 15.00, 2, '施琦大王', '17816786725', '广东省深圳市南山区政府大院2楼', '', '2025-09-25 04:22:57', '2025-09-25 05:17:26', '2025-09-25 05:17:26', 'TEST_TRADE_SUCCESS', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL); +COMMIT; + +-- ---------------------------- +-- Table structure for payments +-- ---------------------------- +DROP TABLE IF EXISTS `payments`; +CREATE TABLE `payments` ( + `id` bigint NOT NULL AUTO_INCREMENT COMMENT '支付ID', + `order_id` bigint NOT NULL COMMENT '订单ID', + `payment_no` varchar(32) NOT NULL COMMENT '支付单号', + `payment_method` varchar(20) NOT NULL COMMENT '支付方式', + `amount` decimal(10,2) NOT NULL COMMENT '支付金额', + `status` tinyint(1) DEFAULT '1' COMMENT '支付状态:1待支付,2已支付,3支付失败,4已退款', + `trade_no` varchar(64) DEFAULT NULL COMMENT '第三方交易号', + `alipay_trade_no` varchar(64) DEFAULT NULL COMMENT '支付宝交易号', + `buyer_pay_amount` varchar(20) DEFAULT NULL COMMENT '买家实付金额', + `buyer_logon_id` varchar(100) DEFAULT NULL COMMENT '买家支付宝账号', + `refund_amount` decimal(10,2) DEFAULT NULL COMMENT '退款金额', + `refund_time` datetime DEFAULT NULL COMMENT '退款时间', + `refund_reason` varchar(500) DEFAULT NULL COMMENT '退款原因', + `paid_at` datetime DEFAULT NULL COMMENT '支付时间', + `created_at` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `updated_at` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + PRIMARY KEY (`id`), + UNIQUE KEY `uk_payment_no` (`payment_no`), + KEY `idx_order_id` (`order_id`) +) ENGINE=InnoDB AUTO_INCREMENT=27 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='支付记录表'; + +-- ---------------------------- +-- Records of payments +-- ---------------------------- +BEGIN; +INSERT INTO `payments` (`id`, `order_id`, `payment_no`, `payment_method`, `amount`, `status`, `trade_no`, `alipay_trade_no`, `buyer_pay_amount`, `buyer_logon_id`, `refund_amount`, `refund_time`, `refund_reason`, `paid_at`, `created_at`, `updated_at`) VALUES (1, 22, 'P202508260932190002', 'alipay', 30.00, 1, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, '2025-08-26 09:32:19', '2025-08-26 09:32:19'); +INSERT INTO `payments` (`id`, `order_id`, `payment_no`, `payment_method`, `amount`, `status`, `trade_no`, `alipay_trade_no`, `buyer_pay_amount`, `buyer_logon_id`, `refund_amount`, `refund_time`, `refund_reason`, `paid_at`, `created_at`, `updated_at`) VALUES (2, 25, 'P202508261047450002', 'alipay', 30.00, 1, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, '2025-08-26 10:47:47', '2025-08-26 10:47:47'); +INSERT INTO `payments` (`id`, `order_id`, `payment_no`, `payment_method`, `amount`, `status`, `trade_no`, `alipay_trade_no`, `buyer_pay_amount`, `buyer_logon_id`, `refund_amount`, `refund_time`, `refund_reason`, `paid_at`, `created_at`, `updated_at`) VALUES (3, 25, 'P202508261049370003', 'alipay', 30.00, 1, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, '2025-08-26 10:49:39', '2025-08-26 10:49:39'); +INSERT INTO `payments` (`id`, `order_id`, `payment_no`, `payment_method`, `amount`, `status`, `trade_no`, `alipay_trade_no`, `buyer_pay_amount`, `buyer_logon_id`, `refund_amount`, `refund_time`, `refund_reason`, `paid_at`, `created_at`, `updated_at`) VALUES (4, 26, 'P202508261059380002', 'alipay', 30.00, 1, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, '2025-08-26 10:59:40', '2025-08-26 10:59:40'); +INSERT INTO `payments` (`id`, `order_id`, `payment_no`, `payment_method`, `amount`, `status`, `trade_no`, `alipay_trade_no`, `buyer_pay_amount`, `buyer_logon_id`, `refund_amount`, `refund_time`, `refund_reason`, `paid_at`, `created_at`, `updated_at`) VALUES (5, 27, 'P202508261104090002', 'alipay', 30.00, 1, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, '2025-08-26 11:04:11', '2025-08-26 11:04:11'); +INSERT INTO `payments` (`id`, `order_id`, `payment_no`, `payment_method`, `amount`, `status`, `trade_no`, `alipay_trade_no`, `buyer_pay_amount`, `buyer_logon_id`, `refund_amount`, `refund_time`, `refund_reason`, `paid_at`, `created_at`, `updated_at`) VALUES (6, 28, 'P202508261112120002', 'alipay', 30.00, 1, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, '2025-08-26 11:12:14', '2025-08-26 11:12:14'); +INSERT INTO `payments` (`id`, `order_id`, `payment_no`, `payment_method`, `amount`, `status`, `trade_no`, `alipay_trade_no`, `buyer_pay_amount`, `buyer_logon_id`, `refund_amount`, `refund_time`, `refund_reason`, `paid_at`, `created_at`, `updated_at`) VALUES (7, 29, 'P202509051835040002', 'alipay', 60.00, 1, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, '2025-09-05 18:34:43', '2025-09-05 18:34:43'); +INSERT INTO `payments` (`id`, `order_id`, `payment_no`, `payment_method`, `amount`, `status`, `trade_no`, `alipay_trade_no`, `buyer_pay_amount`, `buyer_logon_id`, `refund_amount`, `refund_time`, `refund_reason`, `paid_at`, `created_at`, `updated_at`) VALUES (8, 30, 'P202509171708540002', 'alipay', 30.00, 1, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, '2025-09-17 17:08:54', '2025-09-17 17:08:54'); +INSERT INTO `payments` (`id`, `order_id`, `payment_no`, `payment_method`, `amount`, `status`, `trade_no`, `alipay_trade_no`, `buyer_pay_amount`, `buyer_logon_id`, `refund_amount`, `refund_time`, `refund_reason`, `paid_at`, `created_at`, `updated_at`) VALUES (9, 30, 'P202509232021240001', 'alipay', 30.00, 1, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, '2025-09-23 20:21:24', '2025-09-23 20:21:24'); +INSERT INTO `payments` (`id`, `order_id`, `payment_no`, `payment_method`, `amount`, `status`, `trade_no`, `alipay_trade_no`, `buyer_pay_amount`, `buyer_logon_id`, `refund_amount`, `refund_time`, `refund_reason`, `paid_at`, `created_at`, `updated_at`) VALUES (10, 31, 'P202509242230110001', 'alipay', 15.00, 1, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, '2025-09-24 22:30:12', '2025-09-24 22:30:12'); +INSERT INTO `payments` (`id`, `order_id`, `payment_no`, `payment_method`, `amount`, `status`, `trade_no`, `alipay_trade_no`, `buyer_pay_amount`, `buyer_logon_id`, `refund_amount`, `refund_time`, `refund_reason`, `paid_at`, `created_at`, `updated_at`) VALUES (11, 31, 'P202509242231350002', 'alipay', 15.00, 1, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, '2025-09-24 22:31:35', '2025-09-24 22:31:35'); +INSERT INTO `payments` (`id`, `order_id`, `payment_no`, `payment_method`, `amount`, `status`, `trade_no`, `alipay_trade_no`, `buyer_pay_amount`, `buyer_logon_id`, `refund_amount`, `refund_time`, `refund_reason`, `paid_at`, `created_at`, `updated_at`) VALUES (12, 31, 'P202509242233560003', 'alipay', 15.00, 1, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, '2025-09-24 22:33:56', '2025-09-24 22:33:56'); +INSERT INTO `payments` (`id`, `order_id`, `payment_no`, `payment_method`, `amount`, `status`, `trade_no`, `alipay_trade_no`, `buyer_pay_amount`, `buyer_logon_id`, `refund_amount`, `refund_time`, `refund_reason`, `paid_at`, `created_at`, `updated_at`) VALUES (13, 31, 'P202509242235460001', 'alipay', 15.00, 1, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, '2025-09-24 22:35:46', '2025-09-24 22:35:46'); +INSERT INTO `payments` (`id`, `order_id`, `payment_no`, `payment_method`, `amount`, `status`, `trade_no`, `alipay_trade_no`, `buyer_pay_amount`, `buyer_logon_id`, `refund_amount`, `refund_time`, `refund_reason`, `paid_at`, `created_at`, `updated_at`) VALUES (14, 31, 'P202509242237320002', 'alipay', 15.00, 1, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, '2025-09-24 22:37:32', '2025-09-24 22:37:32'); +INSERT INTO `payments` (`id`, `order_id`, `payment_no`, `payment_method`, `amount`, `status`, `trade_no`, `alipay_trade_no`, `buyer_pay_amount`, `buyer_logon_id`, `refund_amount`, `refund_time`, `refund_reason`, `paid_at`, `created_at`, `updated_at`) VALUES (15, 31, 'P202509242245360003', 'alipay', 15.00, 1, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, '2025-09-24 22:45:36', '2025-09-24 22:45:36'); +INSERT INTO `payments` (`id`, `order_id`, `payment_no`, `payment_method`, `amount`, `status`, `trade_no`, `alipay_trade_no`, `buyer_pay_amount`, `buyer_logon_id`, `refund_amount`, `refund_time`, `refund_reason`, `paid_at`, `created_at`, `updated_at`) VALUES (16, 31, 'P202509242247450001', 'alipay', 15.00, 1, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, '2025-09-24 22:47:45', '2025-09-24 22:47:45'); +INSERT INTO `payments` (`id`, `order_id`, `payment_no`, `payment_method`, `amount`, `status`, `trade_no`, `alipay_trade_no`, `buyer_pay_amount`, `buyer_logon_id`, `refund_amount`, `refund_time`, `refund_reason`, `paid_at`, `created_at`, `updated_at`) VALUES (17, 31, 'P202509242248550002', 'alipay', 15.00, 1, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, '2025-09-24 22:48:55', '2025-09-24 22:48:55'); +INSERT INTO `payments` (`id`, `order_id`, `payment_no`, `payment_method`, `amount`, `status`, `trade_no`, `alipay_trade_no`, `buyer_pay_amount`, `buyer_logon_id`, `refund_amount`, `refund_time`, `refund_reason`, `paid_at`, `created_at`, `updated_at`) VALUES (18, 31, 'P202509242256180003', 'alipay', 15.00, 1, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, '2025-09-24 22:56:18', '2025-09-24 22:56:18'); +INSERT INTO `payments` (`id`, `order_id`, `payment_no`, `payment_method`, `amount`, `status`, `trade_no`, `alipay_trade_no`, `buyer_pay_amount`, `buyer_logon_id`, `refund_amount`, `refund_time`, `refund_reason`, `paid_at`, `created_at`, `updated_at`) VALUES (19, 31, 'P202509242258180001', 'alipay', 15.00, 2, '2025092422001418170507460642', '2025092422001418170507460642', NULL, NULL, NULL, NULL, NULL, '2025-09-24 23:01:47', '2025-09-24 22:58:18', '2025-09-24 23:01:47'); +INSERT INTO `payments` (`id`, `order_id`, `payment_no`, `payment_method`, `amount`, `status`, `trade_no`, `alipay_trade_no`, `buyer_pay_amount`, `buyer_logon_id`, `refund_amount`, `refund_time`, `refund_reason`, `paid_at`, `created_at`, `updated_at`) VALUES (21, 32, 'P202509242311480003', 'alipay', 15.00, 2, '待补充支付宝交易号', NULL, NULL, NULL, NULL, NULL, NULL, '2025-09-24 23:19:12', '2025-09-24 23:11:48', '2025-09-24 23:19:12'); +INSERT INTO `payments` (`id`, `order_id`, `payment_no`, `payment_method`, `amount`, `status`, `trade_no`, `alipay_trade_no`, `buyer_pay_amount`, `buyer_logon_id`, `refund_amount`, `refund_time`, `refund_reason`, `paid_at`, `created_at`, `updated_at`) VALUES (24, 36, 'P202509250447160002', 'alipay', 15.00, 2, 'TEST_TRADE_SUCCESS', NULL, '10.00', NULL, NULL, NULL, NULL, '2025-09-25 05:17:26', '2025-09-25 04:47:16', '2025-09-25 05:17:26'); +INSERT INTO `payments` (`id`, `order_id`, `payment_no`, `payment_method`, `amount`, `status`, `trade_no`, `alipay_trade_no`, `buyer_pay_amount`, `buyer_logon_id`, `refund_amount`, `refund_time`, `refund_reason`, `paid_at`, `created_at`, `updated_at`) VALUES (25, 33, 'P202509250519010001', 'alipay', 30.00, 1, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, '2025-09-25 05:19:01', '2025-09-25 05:19:01'); +INSERT INTO `payments` (`id`, `order_id`, `payment_no`, `payment_method`, `amount`, `status`, `trade_no`, `alipay_trade_no`, `buyer_pay_amount`, `buyer_logon_id`, `refund_amount`, `refund_time`, `refund_reason`, `paid_at`, `created_at`, `updated_at`) VALUES (26, 19, 'P202509250520120002', 'alipay', 30.00, 1, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, '2025-09-25 05:20:12', '2025-09-25 05:20:12'); +COMMIT; + +-- ---------------------------- +-- Table structure for permissions +-- ---------------------------- +DROP TABLE IF EXISTS `permissions`; +CREATE TABLE `permissions` ( + `id` bigint NOT NULL AUTO_INCREMENT COMMENT '权限ID', + `name` varchar(50) NOT NULL COMMENT '权限名称', + `code` varchar(50) NOT NULL COMMENT '权限代码', + `type` tinyint(1) DEFAULT '1' COMMENT '权限类型:1菜单,2按钮,3接口', + `parent_id` bigint DEFAULT '0' COMMENT '父权限ID', + `path` varchar(100) DEFAULT NULL COMMENT '路径', + `description` varchar(200) DEFAULT NULL COMMENT '描述', + `created_at` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `updated_at` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + PRIMARY KEY (`id`), + UNIQUE KEY `uk_code` (`code`) +) ENGINE=InnoDB AUTO_INCREMENT=7 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='权限表'; + +-- ---------------------------- +-- Records of permissions +-- ---------------------------- +BEGIN; +INSERT INTO `permissions` (`id`, `name`, `code`, `type`, `parent_id`, `path`, `description`, `created_at`, `updated_at`) VALUES (1, '用户管理', 'USER_MANAGE', 1, 0, '/admin/users', '用户管理菜单', '2025-07-16 12:50:43', '2025-07-16 12:50:43'); +INSERT INTO `permissions` (`id`, `name`, `code`, `type`, `parent_id`, `path`, `description`, `created_at`, `updated_at`) VALUES (2, '商家管理', 'MERCHANT_MANAGE', 1, 0, '/admin/merchants', '商家管理菜单', '2025-07-16 12:50:43', '2025-07-16 12:50:43'); +INSERT INTO `permissions` (`id`, `name`, `code`, `type`, `parent_id`, `path`, `description`, `created_at`, `updated_at`) VALUES (3, '商品管理', 'PRODUCT_MANAGE', 1, 0, '/admin/products', '商品管理菜单', '2025-07-16 12:50:43', '2025-07-16 12:50:43'); +INSERT INTO `permissions` (`id`, `name`, `code`, `type`, `parent_id`, `path`, `description`, `created_at`, `updated_at`) VALUES (4, '订单管理', 'ORDER_MANAGE', 1, 0, '/admin/orders', '订单管理菜单', '2025-07-16 12:50:43', '2025-07-16 12:50:43'); +INSERT INTO `permissions` (`id`, `name`, `code`, `type`, `parent_id`, `path`, `description`, `created_at`, `updated_at`) VALUES (5, '数据统计', 'STATISTICS', 1, 0, '/admin/statistics', '数据统计菜单', '2025-07-16 12:50:43', '2025-07-16 12:50:43'); +INSERT INTO `permissions` (`id`, `name`, `code`, `type`, `parent_id`, `path`, `description`, `created_at`, `updated_at`) VALUES (6, '系统设置', 'SYSTEM_SETTING', 1, 0, '/admin/settings', '系统设置菜单', '2025-07-16 12:50:43', '2025-07-16 12:50:43'); +COMMIT; + +-- ---------------------------- +-- Table structure for product_images +-- ---------------------------- +DROP TABLE IF EXISTS `product_images`; +CREATE TABLE `product_images` ( + `id` bigint NOT NULL AUTO_INCREMENT COMMENT '图片ID', + `product_id` bigint NOT NULL COMMENT '商品ID', + `image_url` varchar(255) NOT NULL COMMENT '图片URL', + `is_main` tinyint(1) DEFAULT '0' COMMENT '是否主图:0否,1是', + `sort_order` int DEFAULT '0' COMMENT '排序', + `created_at` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + PRIMARY KEY (`id`), + KEY `idx_product_id` (`product_id`) +) ENGINE=InnoDB AUTO_INCREMENT=10 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='商品图片表'; + +-- ---------------------------- +-- Records of product_images +-- ---------------------------- +BEGIN; +INSERT INTO `product_images` (`id`, `product_id`, `image_url`, `is_main`, `sort_order`, `created_at`) VALUES (3, 1, 'https://sunnyfarm-1328510989.cos.ap-guangzhou.myqcloud.com/uploads/2025/07/18/4eae7138-0760-41bd-b76d-a99630ce84fa.jpg', 1, 0, '2025-09-23 16:33:59'); +INSERT INTO `product_images` (`id`, `product_id`, `image_url`, `is_main`, `sort_order`, `created_at`) VALUES (7, 2, 'https://cdnsunnyfarm.sqai.online/uploads/2025/09/23/42f966e9-d0b4-419d-9362-b75f41a9067b.jpg', 1, 0, '2025-09-23 17:47:32'); +INSERT INTO `product_images` (`id`, `product_id`, `image_url`, `is_main`, `sort_order`, `created_at`) VALUES (8, 2, 'https://cdnsunnyfarm.sqai.online/uploads/2025/09/23/016ca434-a729-4ba8-bdfb-f35429a9c1e1.jpg', 0, 1, '2025-09-23 17:47:32'); +INSERT INTO `product_images` (`id`, `product_id`, `image_url`, `is_main`, `sort_order`, `created_at`) VALUES (9, 2, 'https://cdnsunnyfarm.sqai.online/uploads/2025/09/23/e1a1b427-c248-4385-b9a9-72e4cee5e58b.jpg', 0, 2, '2025-09-23 17:47:32'); +COMMIT; + +-- ---------------------------- +-- Table structure for products +-- ---------------------------- +DROP TABLE IF EXISTS `products`; +CREATE TABLE `products` ( + `id` bigint NOT NULL AUTO_INCREMENT COMMENT '商品ID', + `merchant_id` bigint NOT NULL COMMENT '商家ID', + `category_id` bigint NOT NULL COMMENT '分类ID', + `name` varchar(200) NOT NULL COMMENT '商品名称', + `description` text COMMENT '商品描述', + `price` decimal(10,2) NOT NULL COMMENT '价格', + `origin_price` decimal(10,2) DEFAULT NULL COMMENT '原价', + `origin` varchar(100) DEFAULT NULL COMMENT '产地', + `unit` varchar(20) DEFAULT NULL COMMENT '单位', + `weight` varchar(50) DEFAULT NULL COMMENT '重量', + `shelf_life` varchar(50) DEFAULT NULL COMMENT '保质期', + `storage_method` varchar(100) DEFAULT NULL COMMENT '储存方式', + `status` tinyint(1) DEFAULT '0' COMMENT '状态:0审核中,1上架,2下架', + `sort_order` int DEFAULT '0' COMMENT '排序', + `sales_count` int DEFAULT '0' COMMENT '销量', + `view_count` int DEFAULT '0' COMMENT '浏览量', + `created_at` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `updated_at` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + PRIMARY KEY (`id`), + KEY `idx_merchant_id` (`merchant_id`), + KEY `idx_category_id` (`category_id`), + KEY `idx_status` (`status`) +) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='商品信息表'; + +-- ---------------------------- +-- Records of products +-- ---------------------------- +BEGIN; +INSERT INTO `products` (`id`, `merchant_id`, `category_id`, `name`, `description`, `price`, `origin_price`, `origin`, `unit`, `weight`, `shelf_life`, `storage_method`, `status`, `sort_order`, `sales_count`, `view_count`, `created_at`, `updated_at`) VALUES (1, 1, 5, 'aaa', 'test', 30.00, 40.00, '深圳', '斤', '500g/斤', '6天', '常温', 1, 0, 0, 118, '2025-07-18 07:39:30', '2025-09-23 17:48:00'); +INSERT INTO `products` (`id`, `merchant_id`, `category_id`, `name`, `description`, `price`, `origin_price`, `origin`, `unit`, `weight`, `shelf_life`, `storage_method`, `status`, `sort_order`, `sales_count`, `view_count`, `created_at`, `updated_at`) VALUES (2, 1, 6, '云南有机萝卜', '云南有机大萝卜,吃的更放心!', 15.00, 15.00, '云南', '斤', '500g', '7 天', '常温保存', 1, 0, 0, 121, '2025-09-23 17:47:20', '2025-09-23 17:48:03'); +COMMIT; + +-- ---------------------------- +-- Table structure for reviews +-- ---------------------------- +DROP TABLE IF EXISTS `reviews`; +CREATE TABLE `reviews` ( + `id` bigint NOT NULL AUTO_INCREMENT COMMENT '评价ID', + `order_id` bigint NOT NULL COMMENT '订单ID', + `user_id` bigint NOT NULL COMMENT '用户ID', + `product_id` bigint NOT NULL COMMENT '商品ID', + `merchant_id` bigint NOT NULL COMMENT '商家ID', + `rating` tinyint(1) NOT NULL COMMENT '评分:1-5星', + `content` text COMMENT '评价内容', + `reply` text COMMENT '商家回复', + `reply_time` datetime DEFAULT NULL COMMENT '回复时间', + `status` tinyint(1) DEFAULT '1' COMMENT '状态:0隐藏,1显示', + `created_at` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `updated_at` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + PRIMARY KEY (`id`), + KEY `idx_order_id` (`order_id`), + KEY `idx_user_id` (`user_id`), + KEY `idx_product_id` (`product_id`), + KEY `idx_merchant_id` (`merchant_id`) +) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='评价表'; + +-- ---------------------------- +-- Records of reviews +-- ---------------------------- +BEGIN; +INSERT INTO `reviews` (`id`, `order_id`, `user_id`, `product_id`, `merchant_id`, `rating`, `content`, `reply`, `reply_time`, `status`, `created_at`, `updated_at`) VALUES (1, 21, 1, 1, 1, 5, '书本非常好吃!我太爱了,太好吃了!哈哈哈哈!', NULL, NULL, 1, '2025-09-24 21:23:28', '2025-09-24 21:23:28'); +INSERT INTO `reviews` (`id`, `order_id`, `user_id`, `product_id`, `merchant_id`, `rating`, `content`, `reply`, `reply_time`, `status`, `created_at`, `updated_at`) VALUES (2, 20, 1, 1, 1, 5, '书本非常好看!孩子每天都看!', NULL, NULL, 1, '2025-09-24 22:28:02', '2025-09-24 22:28:02'); +COMMIT; + +-- ---------------------------- +-- Table structure for role_permissions +-- ---------------------------- +DROP TABLE IF EXISTS `role_permissions`; +CREATE TABLE `role_permissions` ( + `id` bigint NOT NULL AUTO_INCREMENT COMMENT '关联ID', + `role_id` bigint NOT NULL COMMENT '角色ID', + `permission_id` bigint NOT NULL COMMENT '权限ID', + `created_at` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + PRIMARY KEY (`id`), + KEY `idx_role_id` (`role_id`), + KEY `idx_permission_id` (`permission_id`) +) ENGINE=InnoDB AUTO_INCREMENT=7 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='角色权限关联表'; + +-- ---------------------------- +-- Records of role_permissions +-- ---------------------------- +BEGIN; +INSERT INTO `role_permissions` (`id`, `role_id`, `permission_id`, `created_at`) VALUES (1, 1, 1, '2025-07-16 12:50:43'); +INSERT INTO `role_permissions` (`id`, `role_id`, `permission_id`, `created_at`) VALUES (2, 1, 2, '2025-07-16 12:50:43'); +INSERT INTO `role_permissions` (`id`, `role_id`, `permission_id`, `created_at`) VALUES (3, 1, 3, '2025-07-16 12:50:43'); +INSERT INTO `role_permissions` (`id`, `role_id`, `permission_id`, `created_at`) VALUES (4, 1, 4, '2025-07-16 12:50:43'); +INSERT INTO `role_permissions` (`id`, `role_id`, `permission_id`, `created_at`) VALUES (5, 1, 5, '2025-07-16 12:50:43'); +INSERT INTO `role_permissions` (`id`, `role_id`, `permission_id`, `created_at`) VALUES (6, 1, 6, '2025-07-16 12:50:43'); +COMMIT; + +-- ---------------------------- +-- Table structure for roles +-- ---------------------------- +DROP TABLE IF EXISTS `roles`; +CREATE TABLE `roles` ( + `id` bigint NOT NULL AUTO_INCREMENT COMMENT '角色ID', + `name` varchar(50) NOT NULL COMMENT '角色名称', + `code` varchar(50) NOT NULL COMMENT '角色代码', + `description` varchar(200) DEFAULT NULL COMMENT '描述', + `status` tinyint(1) DEFAULT '1' COMMENT '状态:0禁用,1启用', + `created_at` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `updated_at` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + PRIMARY KEY (`id`), + UNIQUE KEY `uk_code` (`code`) +) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='角色表'; + +-- ---------------------------- +-- Records of roles +-- ---------------------------- +BEGIN; +INSERT INTO `roles` (`id`, `name`, `code`, `description`, `status`, `created_at`, `updated_at`) VALUES (1, '超级管理员', 'SUPER_ADMIN', '拥有所有权限的超级管理员', 1, '2025-07-16 12:50:39', '2025-07-16 12:50:39'); +INSERT INTO `roles` (`id`, `name`, `code`, `description`, `status`, `created_at`, `updated_at`) VALUES (2, '运营管理员', 'OPERATION_ADMIN', '负责平台运营管理', 1, '2025-07-16 12:50:39', '2025-07-16 12:50:39'); +INSERT INTO `roles` (`id`, `name`, `code`, `description`, `status`, `created_at`, `updated_at`) VALUES (3, '客服管理员', 'SERVICE_ADMIN', '负责客服相关工作', 1, '2025-07-16 12:50:39', '2025-07-16 12:50:39'); +COMMIT; + +-- ---------------------------- +-- Table structure for shopping_cart +-- ---------------------------- +DROP TABLE IF EXISTS `shopping_cart`; +CREATE TABLE `shopping_cart` ( + `id` bigint NOT NULL AUTO_INCREMENT COMMENT '购物车ID', + `user_id` bigint NOT NULL COMMENT '用户ID', + `product_id` bigint NOT NULL COMMENT '商品ID', + `quantity` int NOT NULL DEFAULT '1' COMMENT '数量', + `selected` tinyint(1) DEFAULT '0' COMMENT '是否选中:0否,1是', + `created_at` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `updated_at` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + PRIMARY KEY (`id`), + KEY `idx_user_id` (`user_id`), + KEY `idx_product_id` (`product_id`) +) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='购物车表'; + +-- ---------------------------- +-- Records of shopping_cart +-- ---------------------------- +BEGIN; +COMMIT; + +-- ---------------------------- +-- Table structure for statistics_daily +-- ---------------------------- +DROP TABLE IF EXISTS `statistics_daily`; +CREATE TABLE `statistics_daily` ( + `id` bigint NOT NULL AUTO_INCREMENT COMMENT '统计ID', + `date` date NOT NULL COMMENT '日期', + `total_users` int DEFAULT '0' COMMENT '总用户数', + `new_users` int DEFAULT '0' COMMENT '新增用户数', + `total_orders` int DEFAULT '0' COMMENT '总订单数', + `total_sales` decimal(15,2) DEFAULT '0.00' COMMENT '总销售额', + `created_at` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + PRIMARY KEY (`id`), + UNIQUE KEY `uk_date` (`date`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='每日统计表'; + +-- ---------------------------- +-- Records of statistics_daily +-- ---------------------------- +BEGIN; +COMMIT; + +-- ---------------------------- +-- Table structure for system_configs +-- ---------------------------- +DROP TABLE IF EXISTS `system_configs`; +CREATE TABLE `system_configs` ( + `id` bigint NOT NULL AUTO_INCREMENT COMMENT '配置ID', + `config_key` varchar(100) NOT NULL COMMENT '配置键', + `config_value` text COMMENT '配置值', + `config_type` varchar(20) DEFAULT 'string' COMMENT '配置类型', + `description` varchar(200) DEFAULT NULL COMMENT '描述', + `created_at` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `updated_at` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + PRIMARY KEY (`id`), + UNIQUE KEY `uk_config_key` (`config_key`) +) ENGINE=InnoDB AUTO_INCREMENT=8 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='系统配置表'; + +-- ---------------------------- +-- Records of system_configs +-- ---------------------------- +BEGIN; +INSERT INTO `system_configs` (`id`, `config_key`, `config_value`, `config_type`, `description`, `created_at`, `updated_at`) VALUES (1, 'site_name', '农产品直销平台', 'string', '网站名称', '2025-07-16 04:56:56', '2025-07-16 04:56:56'); +INSERT INTO `system_configs` (`id`, `config_key`, `config_value`, `config_type`, `description`, `created_at`, `updated_at`) VALUES (2, 'site_logo', '', 'string', '网站Logo', '2025-07-16 04:56:56', '2025-07-16 04:56:56'); +INSERT INTO `system_configs` (`id`, `config_key`, `config_value`, `config_type`, `description`, `created_at`, `updated_at`) VALUES (3, 'site_description', '新鲜农产品直销平台', 'string', '网站描述', '2025-07-16 04:56:56', '2025-07-16 04:56:56'); +INSERT INTO `system_configs` (`id`, `config_key`, `config_value`, `config_type`, `description`, `created_at`, `updated_at`) VALUES (4, 'contact_phone', '400-888-8888', 'string', '客服电话', '2025-07-16 04:56:56', '2025-07-16 04:56:56'); +INSERT INTO `system_configs` (`id`, `config_key`, `config_value`, `config_type`, `description`, `created_at`, `updated_at`) VALUES (5, 'contact_email', 'service@farm.com', 'string', '客服邮箱', '2025-07-16 04:56:56', '2025-07-16 04:56:56'); +INSERT INTO `system_configs` (`id`, `config_key`, `config_value`, `config_type`, `description`, `created_at`, `updated_at`) VALUES (6, 'auto_confirm_days', '7', 'number', '自动确认收货天数', '2025-07-16 04:56:56', '2025-07-16 04:56:56'); +INSERT INTO `system_configs` (`id`, `config_key`, `config_value`, `config_type`, `description`, `created_at`, `updated_at`) VALUES (7, 'max_cart_items', '50', 'number', '购物车最大商品数', '2025-07-16 04:56:56', '2025-07-16 04:56:56'); +COMMIT; + +-- ---------------------------- +-- Table structure for system_logs +-- ---------------------------- +DROP TABLE IF EXISTS `system_logs`; +CREATE TABLE `system_logs` ( + `id` bigint NOT NULL AUTO_INCREMENT COMMENT '日志ID', + `operation_type` varchar(50) NOT NULL COMMENT '操作类型', + `operation_desc` varchar(500) NOT NULL COMMENT '操作描述', + `operator_id` bigint DEFAULT NULL COMMENT '操作者ID', + `operator_name` varchar(50) DEFAULT NULL COMMENT '操作者名称', + `operator_type` tinyint(1) DEFAULT '1' COMMENT '操作者类型:1普通用户,2商家,3管理员,4系统', + `target_type` varchar(50) DEFAULT NULL COMMENT '操作目标类型', + `target_id` bigint DEFAULT NULL COMMENT '操作目标ID', + `ip_address` varchar(45) DEFAULT NULL COMMENT 'IP地址', + `user_agent` varchar(500) DEFAULT NULL COMMENT '用户代理', + `result` tinyint(1) DEFAULT '1' COMMENT '操作结果:0失败,1成功', + `error_msg` varchar(1000) DEFAULT NULL COMMENT '错误信息', + `created_at` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `updated_at` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + PRIMARY KEY (`id`), + KEY `idx_operation_type` (`operation_type`), + KEY `idx_operator` (`operator_id`,`operator_type`), + KEY `idx_created_at` (`created_at`) +) ENGINE=InnoDB AUTO_INCREMENT=82 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='系统操作日志表'; + +-- ---------------------------- +-- Records of system_logs +-- ---------------------------- +BEGIN; +INSERT INTO `system_logs` (`id`, `operation_type`, `operation_desc`, `operator_id`, `operator_name`, `operator_type`, `target_type`, `target_id`, `ip_address`, `user_agent`, `result`, `error_msg`, `created_at`, `updated_at`) VALUES (6, 'user_register', '用户张三完成注册', 1, '张三', 1, NULL, NULL, NULL, NULL, 1, NULL, '2025-07-18 05:51:27', '2025-07-18 05:56:27'); +INSERT INTO `system_logs` (`id`, `operation_type`, `operation_desc`, `operator_id`, `operator_name`, `operator_type`, `target_type`, `target_id`, `ip_address`, `user_agent`, `result`, `error_msg`, `created_at`, `updated_at`) VALUES (7, 'merchant_register', '商家绿色农场提交入驻申请', 2, '绿色农场', 2, NULL, NULL, NULL, NULL, 1, NULL, '2025-07-18 05:41:27', '2025-07-18 05:56:27'); +INSERT INTO `system_logs` (`id`, `operation_type`, `operation_desc`, `operator_id`, `operator_name`, `operator_type`, `target_type`, `target_id`, `ip_address`, `user_agent`, `result`, `error_msg`, `created_at`, `updated_at`) VALUES (8, 'admin_login', '管理员登录成功', 1, 'admin', 3, NULL, NULL, NULL, NULL, 1, NULL, '2025-07-18 05:26:27', '2025-07-18 05:56:27'); +INSERT INTO `system_logs` (`id`, `operation_type`, `operation_desc`, `operator_id`, `operator_name`, `operator_type`, `target_type`, `target_id`, `ip_address`, `user_agent`, `result`, `error_msg`, `created_at`, `updated_at`) VALUES (9, 'product_create', '商品有机西红柿创建成功', 2, '绿色农场', 2, NULL, NULL, NULL, NULL, 1, NULL, '2025-07-18 05:11:27', '2025-07-18 05:56:27'); +INSERT INTO `system_logs` (`id`, `operation_type`, `operation_desc`, `operator_id`, `operator_name`, `operator_type`, `target_type`, `target_id`, `ip_address`, `user_agent`, `result`, `error_msg`, `created_at`, `updated_at`) VALUES (10, 'merchant_audit', '商家有机蔬菜合作社审核通过', 1, 'admin', 3, NULL, NULL, NULL, NULL, 1, NULL, '2025-07-18 04:56:27', '2025-07-18 05:56:27'); +INSERT INTO `system_logs` (`id`, `operation_type`, `operation_desc`, `operator_id`, `operator_name`, `operator_type`, `target_type`, `target_id`, `ip_address`, `user_agent`, `result`, `error_msg`, `created_at`, `updated_at`) VALUES (11, 'product_audit', '商品有机黄瓜审核通过', 1, 'admin', 3, NULL, NULL, NULL, NULL, 1, NULL, '2025-07-18 04:41:27', '2025-07-18 05:56:27'); +INSERT INTO `system_logs` (`id`, `operation_type`, `operation_desc`, `operator_id`, `operator_name`, `operator_type`, `target_type`, `target_id`, `ip_address`, `user_agent`, `result`, `error_msg`, `created_at`, `updated_at`) VALUES (12, 'order_create', '订单ORDER20241218001创建成功', 3, '李四', 1, NULL, NULL, NULL, NULL, 1, NULL, '2025-07-18 04:26:27', '2025-07-18 05:56:27'); +INSERT INTO `system_logs` (`id`, `operation_type`, `operation_desc`, `operator_id`, `operator_name`, `operator_type`, `target_type`, `target_id`, `ip_address`, `user_agent`, `result`, `error_msg`, `created_at`, `updated_at`) VALUES (13, 'system_config', '更新系统配置', 1, 'admin', 3, NULL, NULL, NULL, NULL, 1, NULL, '2025-07-18 04:11:27', '2025-07-18 05:56:27'); +INSERT INTO `system_logs` (`id`, `operation_type`, `operation_desc`, `operator_id`, `operator_name`, `operator_type`, `target_type`, `target_id`, `ip_address`, `user_agent`, `result`, `error_msg`, `created_at`, `updated_at`) VALUES (14, 'admin_login', '管理员登录成功', 2, 'admin', 3, NULL, NULL, NULL, NULL, 1, NULL, '2025-07-18 06:15:05', '2025-07-18 06:15:05'); +INSERT INTO `system_logs` (`id`, `operation_type`, `operation_desc`, `operator_id`, `operator_name`, `operator_type`, `target_type`, `target_id`, `ip_address`, `user_agent`, `result`, `error_msg`, `created_at`, `updated_at`) VALUES (15, 'product_audit', '商品审核通过: aaa', 2, 'admin', 3, NULL, NULL, NULL, NULL, 1, NULL, '2025-07-18 07:42:23', '2025-07-18 07:42:23'); +INSERT INTO `system_logs` (`id`, `operation_type`, `operation_desc`, `operator_id`, `operator_name`, `operator_type`, `target_type`, `target_id`, `ip_address`, `user_agent`, `result`, `error_msg`, `created_at`, `updated_at`) VALUES (16, 'product_audit', '商品审核通过: aaa', 2, 'admin', 3, NULL, NULL, NULL, NULL, 1, NULL, '2025-07-18 07:43:03', '2025-07-18 07:43:03'); +INSERT INTO `system_logs` (`id`, `operation_type`, `operation_desc`, `operator_id`, `operator_name`, `operator_type`, `target_type`, `target_id`, `ip_address`, `user_agent`, `result`, `error_msg`, `created_at`, `updated_at`) VALUES (17, 'product_audit', '商品审核通过: aaa', 2, 'admin', 3, NULL, NULL, NULL, NULL, 1, NULL, '2025-07-18 07:47:39', '2025-07-18 07:47:39'); +INSERT INTO `system_logs` (`id`, `operation_type`, `operation_desc`, `operator_id`, `operator_name`, `operator_type`, `target_type`, `target_id`, `ip_address`, `user_agent`, `result`, `error_msg`, `created_at`, `updated_at`) VALUES (18, 'product_audit', '商品审核通过: aaa', 2, 'admin', 3, NULL, NULL, NULL, NULL, 1, NULL, '2025-07-18 07:54:38', '2025-07-18 07:54:38'); +INSERT INTO `system_logs` (`id`, `operation_type`, `operation_desc`, `operator_id`, `operator_name`, `operator_type`, `target_type`, `target_id`, `ip_address`, `user_agent`, `result`, `error_msg`, `created_at`, `updated_at`) VALUES (19, 'product_audit', '商品审核通过: aaa', 2, 'admin', 3, NULL, NULL, NULL, NULL, 1, NULL, '2025-07-18 08:39:42', '2025-07-18 08:39:42'); +INSERT INTO `system_logs` (`id`, `operation_type`, `operation_desc`, `operator_id`, `operator_name`, `operator_type`, `target_type`, `target_id`, `ip_address`, `user_agent`, `result`, `error_msg`, `created_at`, `updated_at`) VALUES (20, 'product_audit', '商品审核通过: aaa', 2, 'admin', 3, NULL, NULL, NULL, NULL, 1, NULL, '2025-07-18 08:40:00', '2025-07-18 08:40:00'); +INSERT INTO `system_logs` (`id`, `operation_type`, `operation_desc`, `operator_id`, `operator_name`, `operator_type`, `target_type`, `target_id`, `ip_address`, `user_agent`, `result`, `error_msg`, `created_at`, `updated_at`) VALUES (21, 'admin_login', '管理员登录成功', 2, 'admin', 3, NULL, NULL, NULL, NULL, 1, NULL, '2025-07-18 08:48:29', '2025-07-18 08:48:29'); +INSERT INTO `system_logs` (`id`, `operation_type`, `operation_desc`, `operator_id`, `operator_name`, `operator_type`, `target_type`, `target_id`, `ip_address`, `user_agent`, `result`, `error_msg`, `created_at`, `updated_at`) VALUES (22, 'product_audit', '商品审核通过: aaa', 2, 'admin', 3, NULL, NULL, NULL, NULL, 1, NULL, '2025-07-18 08:49:51', '2025-07-18 08:49:51'); +INSERT INTO `system_logs` (`id`, `operation_type`, `operation_desc`, `operator_id`, `operator_name`, `operator_type`, `target_type`, `target_id`, `ip_address`, `user_agent`, `result`, `error_msg`, `created_at`, `updated_at`) VALUES (23, 'admin_login', '管理员登录成功', 2, 'admin', 3, NULL, NULL, NULL, NULL, 1, NULL, '2025-08-22 06:41:14', '2025-08-22 06:41:14'); +INSERT INTO `system_logs` (`id`, `operation_type`, `operation_desc`, `operator_id`, `operator_name`, `operator_type`, `target_type`, `target_id`, `ip_address`, `user_agent`, `result`, `error_msg`, `created_at`, `updated_at`) VALUES (24, 'product_audit', '商品审核通过: aaa', 2, 'admin', 3, NULL, NULL, NULL, NULL, 1, NULL, '2025-08-22 06:41:37', '2025-08-22 06:41:37'); +INSERT INTO `system_logs` (`id`, `operation_type`, `operation_desc`, `operator_id`, `operator_name`, `operator_type`, `target_type`, `target_id`, `ip_address`, `user_agent`, `result`, `error_msg`, `created_at`, `updated_at`) VALUES (25, 'admin_login', '管理员登录成功', 2, 'admin', 3, NULL, NULL, NULL, NULL, 1, NULL, '2025-08-24 18:35:09', '2025-08-24 18:35:09'); +INSERT INTO `system_logs` (`id`, `operation_type`, `operation_desc`, `operator_id`, `operator_name`, `operator_type`, `target_type`, `target_id`, `ip_address`, `user_agent`, `result`, `error_msg`, `created_at`, `updated_at`) VALUES (26, 'admin_login', '管理员登录成功', 2, 'admin', 3, NULL, NULL, NULL, NULL, 1, NULL, '2025-08-24 21:30:24', '2025-08-24 21:30:24'); +INSERT INTO `system_logs` (`id`, `operation_type`, `operation_desc`, `operator_id`, `operator_name`, `operator_type`, `target_type`, `target_id`, `ip_address`, `user_agent`, `result`, `error_msg`, `created_at`, `updated_at`) VALUES (27, 'admin_login', '管理员登录成功', 2, 'admin', 3, NULL, NULL, NULL, NULL, 1, NULL, '2025-08-24 21:30:24', '2025-08-24 21:30:24'); +INSERT INTO `system_logs` (`id`, `operation_type`, `operation_desc`, `operator_id`, `operator_name`, `operator_type`, `target_type`, `target_id`, `ip_address`, `user_agent`, `result`, `error_msg`, `created_at`, `updated_at`) VALUES (28, 'admin_login', '管理员登录成功', 2, 'admin', 3, NULL, NULL, NULL, NULL, 1, NULL, '2025-08-25 23:59:07', '2025-08-25 23:59:07'); +INSERT INTO `system_logs` (`id`, `operation_type`, `operation_desc`, `operator_id`, `operator_name`, `operator_type`, `target_type`, `target_id`, `ip_address`, `user_agent`, `result`, `error_msg`, `created_at`, `updated_at`) VALUES (29, 'order_create', '订单创建成功:O202508260839360001', 1, NULL, 1, NULL, NULL, NULL, NULL, 1, NULL, '2025-08-26 08:39:37', '2025-08-26 08:39:37'); +INSERT INTO `system_logs` (`id`, `operation_type`, `operation_desc`, `operator_id`, `operator_name`, `operator_type`, `target_type`, `target_id`, `ip_address`, `user_agent`, `result`, `error_msg`, `created_at`, `updated_at`) VALUES (30, 'order_create', '订单创建成功:O202508260840070002', 1, NULL, 1, NULL, NULL, NULL, NULL, 1, NULL, '2025-08-26 08:40:08', '2025-08-26 08:40:08'); +INSERT INTO `system_logs` (`id`, `operation_type`, `operation_desc`, `operator_id`, `operator_name`, `operator_type`, `target_type`, `target_id`, `ip_address`, `user_agent`, `result`, `error_msg`, `created_at`, `updated_at`) VALUES (31, 'order_create', '订单创建成功:O202508260840150003', 1, NULL, 1, NULL, NULL, NULL, NULL, 1, NULL, '2025-08-26 08:40:16', '2025-08-26 08:40:16'); +INSERT INTO `system_logs` (`id`, `operation_type`, `operation_desc`, `operator_id`, `operator_name`, `operator_type`, `target_type`, `target_id`, `ip_address`, `user_agent`, `result`, `error_msg`, `created_at`, `updated_at`) VALUES (32, 'order_create', '订单创建成功:O202508260846030001', 1, NULL, 1, NULL, NULL, NULL, NULL, 1, NULL, '2025-08-26 08:46:04', '2025-08-26 08:46:04'); +INSERT INTO `system_logs` (`id`, `operation_type`, `operation_desc`, `operator_id`, `operator_name`, `operator_type`, `target_type`, `target_id`, `ip_address`, `user_agent`, `result`, `error_msg`, `created_at`, `updated_at`) VALUES (33, 'order_create', '订单创建成功:O202508260846080002', 1, NULL, 1, NULL, NULL, NULL, NULL, 1, NULL, '2025-08-26 08:46:09', '2025-08-26 08:46:09'); +INSERT INTO `system_logs` (`id`, `operation_type`, `operation_desc`, `operator_id`, `operator_name`, `operator_type`, `target_type`, `target_id`, `ip_address`, `user_agent`, `result`, `error_msg`, `created_at`, `updated_at`) VALUES (34, 'order_create', '订单创建成功:O202508260847450003', 1, NULL, 1, NULL, NULL, NULL, NULL, 1, NULL, '2025-08-26 08:47:45', '2025-08-26 08:47:45'); +INSERT INTO `system_logs` (`id`, `operation_type`, `operation_desc`, `operator_id`, `operator_name`, `operator_type`, `target_type`, `target_id`, `ip_address`, `user_agent`, `result`, `error_msg`, `created_at`, `updated_at`) VALUES (35, 'order_create', '订单创建成功:O202508260848350004', 1, NULL, 1, NULL, NULL, NULL, NULL, 1, NULL, '2025-08-26 08:48:35', '2025-08-26 08:48:35'); +INSERT INTO `system_logs` (`id`, `operation_type`, `operation_desc`, `operator_id`, `operator_name`, `operator_type`, `target_type`, `target_id`, `ip_address`, `user_agent`, `result`, `error_msg`, `created_at`, `updated_at`) VALUES (36, 'order_create', '订单创建成功:O202508260916080001', 1, NULL, 1, NULL, NULL, NULL, NULL, 1, NULL, '2025-08-26 09:16:09', '2025-08-26 09:16:09'); +INSERT INTO `system_logs` (`id`, `operation_type`, `operation_desc`, `operator_id`, `operator_name`, `operator_type`, `target_type`, `target_id`, `ip_address`, `user_agent`, `result`, `error_msg`, `created_at`, `updated_at`) VALUES (37, 'order_create', '订单创建成功:O202508260921440001', 1, NULL, 1, NULL, NULL, NULL, NULL, 1, NULL, '2025-08-26 09:21:45', '2025-08-26 09:21:45'); +INSERT INTO `system_logs` (`id`, `operation_type`, `operation_desc`, `operator_id`, `operator_name`, `operator_type`, `target_type`, `target_id`, `ip_address`, `user_agent`, `result`, `error_msg`, `created_at`, `updated_at`) VALUES (38, 'order_create', '订单创建成功:O202508260924370002', 1, NULL, 1, NULL, NULL, NULL, NULL, 1, NULL, '2025-08-26 09:24:38', '2025-08-26 09:24:38'); +INSERT INTO `system_logs` (`id`, `operation_type`, `operation_desc`, `operator_id`, `operator_name`, `operator_type`, `target_type`, `target_id`, `ip_address`, `user_agent`, `result`, `error_msg`, `created_at`, `updated_at`) VALUES (39, 'order_create', '订单创建成功:O202508260925100001', 1, NULL, 1, NULL, NULL, NULL, NULL, 1, NULL, '2025-08-26 09:25:10', '2025-08-26 09:25:10'); +INSERT INTO `system_logs` (`id`, `operation_type`, `operation_desc`, `operator_id`, `operator_name`, `operator_type`, `target_type`, `target_id`, `ip_address`, `user_agent`, `result`, `error_msg`, `created_at`, `updated_at`) VALUES (40, 'order_create', '订单创建成功:O202508260926550001', 1, NULL, 1, NULL, NULL, NULL, NULL, 1, NULL, '2025-08-26 09:26:56', '2025-08-26 09:26:56'); +INSERT INTO `system_logs` (`id`, `operation_type`, `operation_desc`, `operator_id`, `operator_name`, `operator_type`, `target_type`, `target_id`, `ip_address`, `user_agent`, `result`, `error_msg`, `created_at`, `updated_at`) VALUES (41, 'order_pay', '订单支付成功:O202508260926550001', 1, NULL, 1, NULL, NULL, NULL, NULL, 1, NULL, '2025-08-26 09:27:01', '2025-08-26 09:27:01'); +INSERT INTO `system_logs` (`id`, `operation_type`, `operation_desc`, `operator_id`, `operator_name`, `operator_type`, `target_type`, `target_id`, `ip_address`, `user_agent`, `result`, `error_msg`, `created_at`, `updated_at`) VALUES (42, 'order_create', '订单创建成功:O202508260927310002', 1, NULL, 1, NULL, NULL, NULL, NULL, 1, NULL, '2025-08-26 09:27:32', '2025-08-26 09:27:32'); +INSERT INTO `system_logs` (`id`, `operation_type`, `operation_desc`, `operator_id`, `operator_name`, `operator_type`, `target_type`, `target_id`, `ip_address`, `user_agent`, `result`, `error_msg`, `created_at`, `updated_at`) VALUES (43, 'order_pay', '订单支付成功:O202508260927310002', 1, NULL, 1, NULL, NULL, NULL, NULL, 1, NULL, '2025-08-26 09:27:46', '2025-08-26 09:27:46'); +INSERT INTO `system_logs` (`id`, `operation_type`, `operation_desc`, `operator_id`, `operator_name`, `operator_type`, `target_type`, `target_id`, `ip_address`, `user_agent`, `result`, `error_msg`, `created_at`, `updated_at`) VALUES (44, 'order_create', '订单创建成功:O202508260932160001', 1, NULL, 1, NULL, NULL, NULL, NULL, 1, NULL, '2025-08-26 09:32:17', '2025-08-26 09:32:17'); +INSERT INTO `system_logs` (`id`, `operation_type`, `operation_desc`, `operator_id`, `operator_name`, `operator_type`, `target_type`, `target_id`, `ip_address`, `user_agent`, `result`, `error_msg`, `created_at`, `updated_at`) VALUES (45, 'order_create', '订单创建成功:O202508260945240001', 1, NULL, 1, NULL, NULL, NULL, NULL, 1, NULL, '2025-08-26 09:45:25', '2025-08-26 09:45:25'); +INSERT INTO `system_logs` (`id`, `operation_type`, `operation_desc`, `operator_id`, `operator_name`, `operator_type`, `target_type`, `target_id`, `ip_address`, `user_agent`, `result`, `error_msg`, `created_at`, `updated_at`) VALUES (46, 'order_create', '订单创建成功:O202508260953130001', 1, NULL, 1, NULL, NULL, NULL, NULL, 1, NULL, '2025-08-26 09:53:13', '2025-08-26 09:53:13'); +INSERT INTO `system_logs` (`id`, `operation_type`, `operation_desc`, `operator_id`, `operator_name`, `operator_type`, `target_type`, `target_id`, `ip_address`, `user_agent`, `result`, `error_msg`, `created_at`, `updated_at`) VALUES (47, 'order_create', '订单创建成功:O202508261047420001', 1, NULL, 1, NULL, NULL, NULL, NULL, 1, NULL, '2025-08-26 10:47:43', '2025-08-26 10:47:43'); +INSERT INTO `system_logs` (`id`, `operation_type`, `operation_desc`, `operator_id`, `operator_name`, `operator_type`, `target_type`, `target_id`, `ip_address`, `user_agent`, `result`, `error_msg`, `created_at`, `updated_at`) VALUES (48, 'order_create', '订单创建成功:O202508261059350001', 1, NULL, 1, NULL, NULL, NULL, NULL, 1, NULL, '2025-08-26 10:59:36', '2025-08-26 10:59:36'); +INSERT INTO `system_logs` (`id`, `operation_type`, `operation_desc`, `operator_id`, `operator_name`, `operator_type`, `target_type`, `target_id`, `ip_address`, `user_agent`, `result`, `error_msg`, `created_at`, `updated_at`) VALUES (49, 'order_create', '订单创建成功:O202508261104060001', 1, NULL, 1, NULL, NULL, NULL, NULL, 1, NULL, '2025-08-26 11:04:07', '2025-08-26 11:04:07'); +INSERT INTO `system_logs` (`id`, `operation_type`, `operation_desc`, `operator_id`, `operator_name`, `operator_type`, `target_type`, `target_id`, `ip_address`, `user_agent`, `result`, `error_msg`, `created_at`, `updated_at`) VALUES (50, 'order_create', '订单创建成功:O202508261112090001', 1, NULL, 1, NULL, NULL, NULL, NULL, 1, NULL, '2025-08-26 11:12:10', '2025-08-26 11:12:10'); +INSERT INTO `system_logs` (`id`, `operation_type`, `operation_desc`, `operator_id`, `operator_name`, `operator_type`, `target_type`, `target_id`, `ip_address`, `user_agent`, `result`, `error_msg`, `created_at`, `updated_at`) VALUES (51, 'order_create', '订单创建成功:O202509051834590001', 1, NULL, 1, NULL, NULL, NULL, NULL, 1, NULL, '2025-09-05 18:34:59', '2025-09-05 18:34:59'); +INSERT INTO `system_logs` (`id`, `operation_type`, `operation_desc`, `operator_id`, `operator_name`, `operator_type`, `target_type`, `target_id`, `ip_address`, `user_agent`, `result`, `error_msg`, `created_at`, `updated_at`) VALUES (52, 'admin_login', '管理员登录成功', 2, 'admin', 3, NULL, NULL, NULL, NULL, 1, NULL, '2025-09-07 23:36:55', '2025-09-07 23:36:55'); +INSERT INTO `system_logs` (`id`, `operation_type`, `operation_desc`, `operator_id`, `operator_name`, `operator_type`, `target_type`, `target_id`, `ip_address`, `user_agent`, `result`, `error_msg`, `created_at`, `updated_at`) VALUES (53, 'order_create', '订单创建成功:O202509171708500001', 1, NULL, 1, NULL, NULL, NULL, NULL, 1, NULL, '2025-09-17 17:08:51', '2025-09-17 17:08:51'); +INSERT INTO `system_logs` (`id`, `operation_type`, `operation_desc`, `operator_id`, `operator_name`, `operator_type`, `target_type`, `target_id`, `ip_address`, `user_agent`, `result`, `error_msg`, `created_at`, `updated_at`) VALUES (54, 'admin_login', '管理员登录成功', 2, 'admin', 3, NULL, NULL, NULL, NULL, 1, NULL, '2025-09-23 17:47:53', '2025-09-23 17:47:53'); +INSERT INTO `system_logs` (`id`, `operation_type`, `operation_desc`, `operator_id`, `operator_name`, `operator_type`, `target_type`, `target_id`, `ip_address`, `user_agent`, `result`, `error_msg`, `created_at`, `updated_at`) VALUES (55, 'product_audit', '商品审核通过: aaa', 2, 'admin', 3, NULL, NULL, NULL, NULL, 1, NULL, '2025-09-23 17:48:00', '2025-09-23 17:48:00'); +INSERT INTO `system_logs` (`id`, `operation_type`, `operation_desc`, `operator_id`, `operator_name`, `operator_type`, `target_type`, `target_id`, `ip_address`, `user_agent`, `result`, `error_msg`, `created_at`, `updated_at`) VALUES (56, 'product_audit', '商品审核通过: 云南有机萝卜', 2, 'admin', 3, NULL, NULL, NULL, NULL, 1, NULL, '2025-09-23 17:48:04', '2025-09-23 17:48:04'); +INSERT INTO `system_logs` (`id`, `operation_type`, `operation_desc`, `operator_id`, `operator_name`, `operator_type`, `target_type`, `target_id`, `ip_address`, `user_agent`, `result`, `error_msg`, `created_at`, `updated_at`) VALUES (57, 'order_cancel', '订单取消成功:O202509171708500001', 1, NULL, 1, NULL, NULL, NULL, NULL, 1, NULL, '2025-09-23 20:25:36', '2025-09-23 20:25:36'); +INSERT INTO `system_logs` (`id`, `operation_type`, `operation_desc`, `operator_id`, `operator_name`, `operator_type`, `target_type`, `target_id`, `ip_address`, `user_agent`, `result`, `error_msg`, `created_at`, `updated_at`) VALUES (58, 'order_cancel', '订单取消成功:O202509051834590001', 1, NULL, 1, NULL, NULL, NULL, NULL, 1, NULL, '2025-09-23 20:25:39', '2025-09-23 20:25:39'); +INSERT INTO `system_logs` (`id`, `operation_type`, `operation_desc`, `operator_id`, `operator_name`, `operator_type`, `target_type`, `target_id`, `ip_address`, `user_agent`, `result`, `error_msg`, `created_at`, `updated_at`) VALUES (59, 'order_cancel', '订单取消成功:O202508261112090001', 1, NULL, 1, NULL, NULL, NULL, NULL, 1, NULL, '2025-09-23 20:25:42', '2025-09-23 20:25:42'); +INSERT INTO `system_logs` (`id`, `operation_type`, `operation_desc`, `operator_id`, `operator_name`, `operator_type`, `target_type`, `target_id`, `ip_address`, `user_agent`, `result`, `error_msg`, `created_at`, `updated_at`) VALUES (60, 'order_cancel', '订单取消成功:O202508261104060001', 1, NULL, 1, NULL, NULL, NULL, NULL, 1, NULL, '2025-09-23 20:25:44', '2025-09-23 20:25:44'); +INSERT INTO `system_logs` (`id`, `operation_type`, `operation_desc`, `operator_id`, `operator_name`, `operator_type`, `target_type`, `target_id`, `ip_address`, `user_agent`, `result`, `error_msg`, `created_at`, `updated_at`) VALUES (61, 'order_cancel', '订单取消成功:O202508261059350001', 1, NULL, 1, NULL, NULL, NULL, NULL, 1, NULL, '2025-09-23 20:25:46', '2025-09-23 20:25:46'); +INSERT INTO `system_logs` (`id`, `operation_type`, `operation_desc`, `operator_id`, `operator_name`, `operator_type`, `target_type`, `target_id`, `ip_address`, `user_agent`, `result`, `error_msg`, `created_at`, `updated_at`) VALUES (62, 'order_cancel', '订单取消成功:O202508260953130001', 1, NULL, 1, NULL, NULL, NULL, NULL, 1, NULL, '2025-09-23 20:25:49', '2025-09-23 20:25:49'); +INSERT INTO `system_logs` (`id`, `operation_type`, `operation_desc`, `operator_id`, `operator_name`, `operator_type`, `target_type`, `target_id`, `ip_address`, `user_agent`, `result`, `error_msg`, `created_at`, `updated_at`) VALUES (63, 'order_cancel', '订单取消成功:O202508260945240001', 1, NULL, 1, NULL, NULL, NULL, NULL, 1, NULL, '2025-09-23 20:25:51', '2025-09-23 20:25:51'); +INSERT INTO `system_logs` (`id`, `operation_type`, `operation_desc`, `operator_id`, `operator_name`, `operator_type`, `target_type`, `target_id`, `ip_address`, `user_agent`, `result`, `error_msg`, `created_at`, `updated_at`) VALUES (64, 'order_cancel', '订单取消成功:O202508261047420001', 1, NULL, 1, NULL, NULL, NULL, NULL, 1, NULL, '2025-09-23 20:25:52', '2025-09-23 20:25:52'); +INSERT INTO `system_logs` (`id`, `operation_type`, `operation_desc`, `operator_id`, `operator_name`, `operator_type`, `target_type`, `target_id`, `ip_address`, `user_agent`, `result`, `error_msg`, `created_at`, `updated_at`) VALUES (65, 'order_cancel', '订单取消成功:O202508260932160001', 1, NULL, 1, NULL, NULL, NULL, NULL, 1, NULL, '2025-09-23 20:25:55', '2025-09-23 20:25:55'); +INSERT INTO `system_logs` (`id`, `operation_type`, `operation_desc`, `operator_id`, `operator_name`, `operator_type`, `target_type`, `target_id`, `ip_address`, `user_agent`, `result`, `error_msg`, `created_at`, `updated_at`) VALUES (66, 'order_create', '订单创建成功:O202509241559530001', 1, NULL, 1, NULL, NULL, NULL, NULL, 1, NULL, '2025-09-24 15:59:54', '2025-09-24 15:59:54'); +INSERT INTO `system_logs` (`id`, `operation_type`, `operation_desc`, `operator_id`, `operator_name`, `operator_type`, `target_type`, `target_id`, `ip_address`, `user_agent`, `result`, `error_msg`, `created_at`, `updated_at`) VALUES (67, 'announcement_update', '更新公告: 新功能上线', 2, 'admin', 3, NULL, NULL, NULL, NULL, 1, NULL, '2025-09-24 18:27:37', '2025-09-24 18:27:37'); +INSERT INTO `system_logs` (`id`, `operation_type`, `operation_desc`, `operator_id`, `operator_name`, `operator_type`, `target_type`, `target_id`, `ip_address`, `user_agent`, `result`, `error_msg`, `created_at`, `updated_at`) VALUES (68, 'announcement_update', '更新公告: 新功能上线', 2, 'admin', 3, NULL, NULL, NULL, NULL, 1, NULL, '2025-09-24 18:27:48', '2025-09-24 18:27:48'); +INSERT INTO `system_logs` (`id`, `operation_type`, `operation_desc`, `operator_id`, `operator_name`, `operator_type`, `target_type`, `target_id`, `ip_address`, `user_agent`, `result`, `error_msg`, `created_at`, `updated_at`) VALUES (69, 'admin_login', '管理员登录成功', 2, 'admin', 3, NULL, NULL, NULL, NULL, 1, NULL, '2025-09-24 20:33:53', '2025-09-24 20:33:53'); +INSERT INTO `system_logs` (`id`, `operation_type`, `operation_desc`, `operator_id`, `operator_name`, `operator_type`, `target_type`, `target_id`, `ip_address`, `user_agent`, `result`, `error_msg`, `created_at`, `updated_at`) VALUES (70, 'order_ship', '订单发货成功:O202508260927310002', 2, NULL, 2, NULL, NULL, NULL, NULL, 1, NULL, '2025-09-24 20:50:56', '2025-09-24 20:50:56'); +INSERT INTO `system_logs` (`id`, `operation_type`, `operation_desc`, `operator_id`, `operator_name`, `operator_type`, `target_type`, `target_id`, `ip_address`, `user_agent`, `result`, `error_msg`, `created_at`, `updated_at`) VALUES (71, 'order_complete', '订单完成:O202508260927310002', 1, NULL, 1, NULL, NULL, NULL, NULL, 1, NULL, '2025-09-24 20:51:03', '2025-09-24 20:51:03'); +INSERT INTO `system_logs` (`id`, `operation_type`, `operation_desc`, `operator_id`, `operator_name`, `operator_type`, `target_type`, `target_id`, `ip_address`, `user_agent`, `result`, `error_msg`, `created_at`, `updated_at`) VALUES (72, 'order_ship', '订单发货成功:O202508260926550001', 2, NULL, 2, NULL, NULL, NULL, NULL, 1, NULL, '2025-09-24 22:18:29', '2025-09-24 22:18:29'); +INSERT INTO `system_logs` (`id`, `operation_type`, `operation_desc`, `operator_id`, `operator_name`, `operator_type`, `target_type`, `target_id`, `ip_address`, `user_agent`, `result`, `error_msg`, `created_at`, `updated_at`) VALUES (73, 'order_complete', '订单完成:O202508260926550001', 1, NULL, 1, NULL, NULL, NULL, NULL, 1, NULL, '2025-09-24 22:18:38', '2025-09-24 22:18:38'); +INSERT INTO `system_logs` (`id`, `operation_type`, `operation_desc`, `operator_id`, `operator_name`, `operator_type`, `target_type`, `target_id`, `ip_address`, `user_agent`, `result`, `error_msg`, `created_at`, `updated_at`) VALUES (74, 'order_create', '订单创建成功:O202509242310530001', 1, NULL, 1, NULL, NULL, NULL, NULL, 1, NULL, '2025-09-24 23:10:54', '2025-09-24 23:10:54'); +INSERT INTO `system_logs` (`id`, `operation_type`, `operation_desc`, `operator_id`, `operator_name`, `operator_type`, `target_type`, `target_id`, `ip_address`, `user_agent`, `result`, `error_msg`, `created_at`, `updated_at`) VALUES (75, 'manual_fix', '手动修复订单O202509242310530001支付状态', 1, NULL, 4, NULL, NULL, NULL, NULL, 1, NULL, '2025-09-24 23:19:12', '2025-09-24 23:19:12'); +INSERT INTO `system_logs` (`id`, `operation_type`, `operation_desc`, `operator_id`, `operator_name`, `operator_type`, `target_type`, `target_id`, `ip_address`, `user_agent`, `result`, `error_msg`, `created_at`, `updated_at`) VALUES (76, 'order_create', '订单创建成功:O202509242320170001', 1, NULL, 1, NULL, NULL, NULL, NULL, 1, NULL, '2025-09-24 23:20:17', '2025-09-24 23:20:17'); +INSERT INTO `system_logs` (`id`, `operation_type`, `operation_desc`, `operator_id`, `operator_name`, `operator_type`, `target_type`, `target_id`, `ip_address`, `user_agent`, `result`, `error_msg`, `created_at`, `updated_at`) VALUES (77, 'order_create', '订单创建成功:O202509250408230001', 1, NULL, 1, NULL, NULL, NULL, NULL, 1, NULL, '2025-09-25 04:08:23', '2025-09-25 04:08:23'); +INSERT INTO `system_logs` (`id`, `operation_type`, `operation_desc`, `operator_id`, `operator_name`, `operator_type`, `target_type`, `target_id`, `ip_address`, `user_agent`, `result`, `error_msg`, `created_at`, `updated_at`) VALUES (78, 'order_cancel', '订单取消成功:O202509250408230001', 1, NULL, 1, NULL, NULL, NULL, NULL, 1, NULL, '2025-09-25 04:14:02', '2025-09-25 04:14:02'); +INSERT INTO `system_logs` (`id`, `operation_type`, `operation_desc`, `operator_id`, `operator_name`, `operator_type`, `target_type`, `target_id`, `ip_address`, `user_agent`, `result`, `error_msg`, `created_at`, `updated_at`) VALUES (79, 'order_create', '订单创建成功:O202509250414080002', 1, NULL, 1, NULL, NULL, NULL, NULL, 1, NULL, '2025-09-25 04:14:08', '2025-09-25 04:14:08'); +INSERT INTO `system_logs` (`id`, `operation_type`, `operation_desc`, `operator_id`, `operator_name`, `operator_type`, `target_type`, `target_id`, `ip_address`, `user_agent`, `result`, `error_msg`, `created_at`, `updated_at`) VALUES (80, 'order_cancel', '订单取消成功:O202509250414080002', 1, NULL, 1, NULL, NULL, NULL, NULL, 1, NULL, '2025-09-25 04:22:50', '2025-09-25 04:22:50'); +INSERT INTO `system_logs` (`id`, `operation_type`, `operation_desc`, `operator_id`, `operator_name`, `operator_type`, `target_type`, `target_id`, `ip_address`, `user_agent`, `result`, `error_msg`, `created_at`, `updated_at`) VALUES (81, 'order_create', '订单创建成功:O202509250422570003', 1, NULL, 1, NULL, NULL, NULL, NULL, 1, NULL, '2025-09-25 04:22:57', '2025-09-25 04:22:57'); +COMMIT; + +-- ---------------------------- +-- Table structure for user_addresses +-- ---------------------------- +DROP TABLE IF EXISTS `user_addresses`; +CREATE TABLE `user_addresses` ( + `id` bigint NOT NULL AUTO_INCREMENT COMMENT '地址ID', + `user_id` bigint NOT NULL COMMENT '用户ID', + `consignee` varchar(50) NOT NULL COMMENT '收件人', + `phone` varchar(20) NOT NULL COMMENT '联系电话', + `province` varchar(20) NOT NULL COMMENT '省份', + `city` varchar(20) NOT NULL COMMENT '城市', + `district` varchar(20) NOT NULL COMMENT '区县', + `address` varchar(200) NOT NULL COMMENT '详细地址', + `is_default` tinyint(1) DEFAULT '0' COMMENT '是否默认地址:0否,1是', + `created_at` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `updated_at` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + PRIMARY KEY (`id`), + KEY `idx_user_id` (`user_id`) +) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='用户收货地址表'; + +-- ---------------------------- +-- Records of user_addresses +-- ---------------------------- +BEGIN; +INSERT INTO `user_addresses` (`id`, `user_id`, `consignee`, `phone`, `province`, `city`, `district`, `address`, `is_default`, `created_at`, `updated_at`) VALUES (1, 1, '施琦大王', '17816786725', '广东省', '深圳市', '南山区', '政府大院2楼', 0, '2025-08-24 09:46:07', '2025-08-24 09:46:07'); +COMMIT; + +-- ---------------------------- +-- Table structure for users +-- ---------------------------- +DROP TABLE IF EXISTS `users`; +CREATE TABLE `users` ( + `id` bigint NOT NULL AUTO_INCREMENT COMMENT '用户ID', + `username` varchar(50) NOT NULL COMMENT '用户名', + `email` varchar(100) DEFAULT NULL COMMENT '邮箱', + `phone` varchar(20) NOT NULL COMMENT '手机号', + `password` varchar(128) NOT NULL COMMENT '密码(MD5加密)', + `nickname` varchar(50) DEFAULT NULL COMMENT '昵称', + `avatar` varchar(255) DEFAULT NULL COMMENT '头像', + `gender` tinyint(1) DEFAULT '0' COMMENT '性别:0未知,1男,2女', + `birthday` date DEFAULT NULL COMMENT '生日', + `real_name` varchar(50) DEFAULT NULL COMMENT '真实姓名', + `id_card` varchar(18) DEFAULT NULL COMMENT '身份证号', + `is_verified` tinyint(1) DEFAULT '0' COMMENT '是否实名认证:0否,1是', + `status` tinyint(1) DEFAULT '1' COMMENT '状态:0禁用,1正常', + `last_login_time` datetime DEFAULT NULL COMMENT '最后登录时间', + `created_at` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `updated_at` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + PRIMARY KEY (`id`), + UNIQUE KEY `uk_username` (`username`), + UNIQUE KEY `uk_phone` (`phone`), + KEY `idx_email` (`email`) +) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='用户信息表'; + +-- ---------------------------- +-- Records of users +-- ---------------------------- +BEGIN; +INSERT INTO `users` (`id`, `username`, `email`, `phone`, `password`, `nickname`, `avatar`, `gender`, `birthday`, `real_name`, `id_card`, `is_verified`, `status`, `last_login_time`, `created_at`, `updated_at`) VALUES (1, 'qin', '852326703@qq.com', '13823680701', '$2a$10$/D/inxCXuf9Le73K0T.u2uYvvDe0iR1QknLGyDlZbiZr9EsOrBwSO', '嗷呜呜汪汪汪钦', NULL, 1, '2002-07-14', '狗王之王', NULL, 0, 1, '2025-09-25 03:49:41', '2025-07-16 09:30:09', '2025-08-24 17:54:07'); +INSERT INTO `users` (`id`, `username`, `email`, `phone`, `password`, `nickname`, `avatar`, `gender`, `birthday`, `real_name`, `id_card`, `is_verified`, `status`, `last_login_time`, `created_at`, `updated_at`) VALUES (2, 'kim', '3399560459@qq.com', '13823680702', '$2a$10$ehcw4gToUJ9Nsa90I.zH4uY8isngL2mRK3b04eC4nql0rzlKKGbdy', NULL, NULL, 0, NULL, NULL, NULL, 0, 1, '2025-09-23 15:51:53', '2025-07-16 12:55:48', '2025-07-16 12:55:48'); +COMMIT; + +SET FOREIGN_KEY_CHECKS = 1; diff --git a/vite.config.js b/vite.config.js new file mode 100644 index 0000000..480eb74 --- /dev/null +++ b/vite.config.js @@ -0,0 +1,35 @@ +import { defineConfig } from 'vite' +import vue from '@vitejs/plugin-vue' +import AutoImport from 'unplugin-auto-import/vite' +import Components from 'unplugin-vue-components/vite' +import { ElementPlusResolver } from 'unplugin-vue-components/resolvers' + +export default defineConfig({ + plugins: [ + vue(), + AutoImport({ + resolvers: [ElementPlusResolver()], + imports: ['vue', 'vue-router', 'pinia'], + dts: true + }), + Components({ + resolvers: [ElementPlusResolver()], + dts: true + }) + ], + resolve: { + alias: { + '@': '/src' + } + }, + server: { + port: 3000, + proxy: { + '/api': { + target: 'http://localhost:8080', + changeOrigin: true, + rewrite: (path) => path.replace(/^\/api/, '') + } + } + } +})