农产品直销平台完整项目

This commit is contained in:
superlishunqin 2025-09-25 05:52:22 +08:00
commit 51e2791a49
241 changed files with 42386 additions and 0 deletions

204
.gitignore vendored Normal file
View File

@ -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/

351
README.md Normal file
View File

@ -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
## 🙏 致谢
感谢所有为本项目做出贡献的开发者们!
---
**注意**: 本项目仅供学习和研究使用,请勿用于商业用途。使用前请确保遵守相关法律法规。

0
backend/README.md Normal file
View File

133
backend/pom.xml Normal file
View File

@ -0,0 +1,133 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.7.14</version>
<relativePath/>
</parent>
<groupId>com.sunnyfarm</groupId>
<artifactId>sunny-farm</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>sunny-farm</name>
<description>Agricultural Product Direct Sales Platform</description>
<properties>
<java.version>8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
</dependency>
<!-- Spring Boot Web Starter -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- Spring Security -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<!-- Spring Boot Mail Starter -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-mail</artifactId>
</dependency>
<!-- MyBatis Plus -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.5.3</version>
</dependency>
<!-- MySQL Driver -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.33</version>
</dependency>
<!-- Redis Starter -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!-- Lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<!-- JWT -->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.1</version>
</dependency>
<!-- JSON处理 -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
</dependency>
<!-- Apache Commons Lang3 -->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.12.0</version>
</dependency>
<!-- 腾讯云COS -->
<dependency>
<groupId>com.qcloud</groupId>
<artifactId>cos_api</artifactId>
<version>5.6.155</version>
</dependency>
<!-- Validation -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
<!-- Test -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!-- 支付宝SDK -->
<dependency>
<groupId>com.alipay.sdk</groupId>
<artifactId>alipay-sdk-java</artifactId>
<version>4.38.10.ALL</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<excludes>
<exclude>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</exclude>
</excludes>
</configuration>
</plugin>
</plugins>
</build>
</project>

206
backend/pom.xml.backup2 Normal file
View File

@ -0,0 +1,206 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.sunnyfarm</groupId>
<artifactId>sunny-farm</artifactId>
<version>1.0.0</version>
<packaging>jar</packaging>
<name>SunnyFarm</name>
<description>农产品直销平台</description>
<properties>
<!-- 核心修改将Java版本统一为17 -->
<java.version>17</java.version>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
<spring.boot.version>2.7.14</spring.boot.version>
<mybatis.plus.version>3.5.3</mybatis.plus.version>
<mysql.version>8.0.33</mysql.version>
<redis.version>2.7.14</redis.version>
<jwt.version>0.11.5</jwt.version>
<cos.version>5.6.89</cos.version>
<alipay.version>4.35.79.ALL</alipay.version>
<hutool.version>5.8.20</hutool.version>
<fastjson.version>2.0.31</fastjson.version>
<lombok.version>1.18.32</lombok.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>${spring.boot.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<!-- Spring Boot Starters -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-mail</artifactId>
</dependency>
<!-- Database -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>${mybatis.plus.version}</version>
</dependency>
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<version>${mysql.version}</version>
</dependency>
<!-- JWT -->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-api</artifactId>
<version>${jwt.version}</version>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-impl</artifactId>
<version>${jwt.version}</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-jackson</artifactId>
<version>${jwt.version}</version>
<scope>runtime</scope>
</dependency>
<!-- Tencent COS -->
<dependency>
<groupId>com.qcloud</groupId>
<artifactId>cos_api</artifactId>
<version>${cos.version}</version>
</dependency>
<!-- Alipay -->
<dependency>
<groupId>com.alipay.sdk</groupId>
<artifactId>alipay-sdk-java</artifactId>
<version>${alipay.version}</version>
</dependency>
<!-- Utilities -->
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>${hutool.version}</version>
</dependency>
<dependency>
<groupId>com.alibaba.fastjson2</groupId>
<artifactId>fastjson2</artifactId>
<version>${fastjson.version}</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
</dependency>
<!-- Lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>${lombok.version}</version>
<scope>provided</scope>
</dependency>
<!-- Test -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!-- Configuration Processor -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>${spring.boot.version}</version>
<configuration>
<excludes>
<exclude>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
</exclude>
<exclude>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</exclude>
</excludes>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<!-- 建议修改升级插件版本以更好地支持Java 17 -->
<version>3.11.0</version>
<configuration>
<!-- 建议修改:引用属性,保持统一 -->
<source>${java.version}</source>
<target>${java.version}</target>
<encoding>UTF-8</encoding>
<annotationProcessorPaths>
<path>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>${lombok.version}</version>
</path>
<path>
<groupId>org.projectlombok</groupId>
<artifactId>lombok-mapstruct-binding</artifactId>
<version>0.2.0</version>
</path>
</annotationProcessorPaths>
</configuration>
</plugin>
</plugins>
</build>
</project>

122
backend/pom.xml.backup3 Normal file
View File

@ -0,0 +1,122 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.7.14</version>
<relativePath/>
</parent>
<groupId>com.sunnyfarm</groupId>
<artifactId>sunny-farm</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>sunny-farm</name>
<description>Agricultural Product Direct Sales Platform</description>
<properties>
<java.version>8</java.version>
</properties>
<dependencies>
<!-- Spring Boot Web Starter -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- Spring Security -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<!-- Spring Boot Mail Starter -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-mail</artifactId>
</dependency>
<!-- MyBatis Plus -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.5.3</version>
</dependency>
<!-- MySQL Driver -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.33</version>
</dependency>
<!-- Redis Starter -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!-- Lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<!-- JWT -->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.1</version>
</dependency>
<!-- JSON处理 -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
</dependency>
<!-- Apache Commons Lang3 -->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.12.0</version>
</dependency>
<!-- 腾讯云COS -->
<dependency>
<groupId>com.qcloud</groupId>
<artifactId>cos_api</artifactId>
<version>5.6.155</version>
</dependency>
<!-- Validation -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
<!-- Test -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<excludes>
<exclude>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</exclude>
</excludes>
</configuration>
</plugin>
</plugins>
</build>
</project>

View File

@ -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);
}
}

View File

@ -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<T> {
private Long total;
private Long pages;
private Long current;
private Long size;
private List<T> records;
public static <T> PageResult<T> of(Long total, Long pages, Long current, Long size, List<T> records) {
return new PageResult<>(total, pages, current, size, records);
}
}

View File

@ -0,0 +1,42 @@
package com.sunnyfarm.common;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Result<T> {
private Integer code;
private String message;
private T data;
public static <T> Result<T> success() {
return new Result<>(200, "操作成功", null);
}
public static <T> Result<T> success(T data) {
return new Result<>(200, "操作成功", data);
}
public static <T> Result<T> success(String message, T data) {
return new Result<>(200, message, data);
}
public static <T> Result<T> error() {
return new Result<>(500, "操作失败", null);
}
public static <T> Result<T> error(String message) {
return new Result<>(500, message, null);
}
public static <T> Result<T> error(Integer code, String message) {
return new Result<>(code, message, null);
}
public static <T> Result<T> error(String message, T data) {
return new Result<>(500, message, data);
}
}

View File

@ -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()
);
}
}

View File

@ -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 {
}

View File

@ -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);
}
}

View File

@ -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;
}
}

View File

@ -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();
}
}

View File

@ -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"
);
}
}

View File

@ -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;
}

View File

@ -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;
/**
* 获取实际使用的URLCDN优先
*/
public String getActualUrl() {
return (cdnEnabled != null && cdnEnabled && cdnUrl != null) ? cdnUrl : baseUrl;
}
}

View File

@ -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;
}

View File

@ -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<List<Address>> getAddressList() {
Long userId = UserContext.getCurrentUserId();
List<Address> addresses = addressService.getUserAddresses(userId);
return Result.success(addresses);
}
/**
* 获取用户默认地址
*/
@GetMapping("/default")
public Result<Address> getDefaultAddress() {
Long userId = UserContext.getCurrentUserId();
Address address = addressService.getUserDefaultAddress(userId);
return Result.success(address);
}
/**
* 获取地址详情
*/
@GetMapping("/{id}")
public Result<Address> getAddressDetail(@PathVariable Long id) {
Address address = addressService.getById(id);
if (address != null) {
return Result.success(address);
} else {
return Result.error("地址不存在");
}
}
/**
* 创建地址
*/
@PostMapping("/create")
public Result<Address> 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<Address> 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<String> 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<String> 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<Long> getAddressCount() {
Long userId = UserContext.getCurrentUserId();
Long count = addressService.getUserAddressCount(userId);
return Result.success(count);
}
}

View File

@ -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<Map<String, Object>> 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<String, Object> 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<Admin> getProfile(@RequestParam Long adminId) {
Admin admin = adminService.getAdminWithRole(adminId);
return Result.success(admin);
}
/**
* 管理员注销
*/
@PostMapping("/logout")
public Result<String> logout() {
return Result.success("注销成功");
}
/**
* 获取仪表盘统计数据
*/
@GetMapping("/dashboard/stats")
public Result<Map<String, Object>> getDashboardStats() {
Map<String, Object> 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<User>()
.between("created_at", todayStart, todayEnd)
);
// 商家统计
long totalMerchants = merchantService.count();
long pendingMerchants = merchantService.count(
new QueryWrapper<Merchant>().eq("status", 0)
);
// 订单统计
long totalOrders = orderService.count();
long newOrdersToday = orderService.count(
new QueryWrapper<Order>()
.between("created_at", todayStart, todayEnd)
);
// 商品统计
long totalProducts = productService.count();
long pendingProducts = productService.count(
new QueryWrapper<Product>().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<PageResult<User>> getUsers(
@RequestParam(defaultValue = "1") int page,
@RequestParam(defaultValue = "10") int size,
@RequestParam(required = false) String keyword,
@RequestParam(required = false) Integer status) {
Page<User> pageParam = new Page<>(page, size);
QueryWrapper<User> 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<User> result = userService.page(pageParam, queryWrapper);
PageResult<User> 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<String> 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<PageResult<Merchant>> getMerchants(
@RequestParam(defaultValue = "1") int page,
@RequestParam(defaultValue = "10") int size,
@RequestParam(required = false) String keyword,
@RequestParam(required = false) Integer status) {
Page<Merchant> pageParam = new Page<>(page, size);
QueryWrapper<Merchant> 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<Merchant> result = merchantService.page(pageParam, queryWrapper);
PageResult<Merchant> 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<String> 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<PageResult<Product>> getProducts(
@RequestParam(defaultValue = "1") int page,
@RequestParam(defaultValue = "10") int size,
@RequestParam(required = false) String keyword,
@RequestParam(required = false) Integer status) {
Page<Product> pageParam = new Page<>(page, size);
QueryWrapper<Product> 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<Product> result = productService.page(pageParam, queryWrapper);
PageResult<Product> 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<String> 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<PageResult<Order>> getOrders(
@RequestParam(defaultValue = "1") int page,
@RequestParam(defaultValue = "10") int size,
@RequestParam(required = false) String keyword,
@RequestParam(required = false) Integer status) {
Page<Order> pageParam = new Page<>(page, size);
QueryWrapper<Order> 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<Order> result = orderService.page(pageParam, queryWrapper);
PageResult<Order> 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<List<Map<String, Object>>> getRecentActivities() {
List<Map<String, Object>> activities = systemLogService.getRecentActivities(10);
return Result.success(activities);
}
/**
* 获取系统配置
*/
@GetMapping("/config")
public Result<Map<String, Object>> getSystemConfig() {
Map<String, Object> config = systemConfigService.getSystemConfig();
return Result.success(config);
}
/**
* 更新系统配置
*/
@PutMapping("/config")
public Result<String> updateSystemConfig(@RequestBody Map<String, Object> config) {
boolean success = systemConfigService.updateSystemConfig(
config,
UserContext.getCurrentUserId(),
UserContext.getCurrentUsername()
);
if (success) {
return Result.success("系统配置更新成功");
} else {
return Result.error("系统配置更新失败");
}
}
/**
* 获取公告列表
*/
@GetMapping("/announcements")
public Result<PageResult<Announcement>> getAnnouncements(
@RequestParam(defaultValue = "1") int page,
@RequestParam(defaultValue = "10") int size,
@RequestParam(required = false) String keyword,
@RequestParam(required = false) Integer status) {
Page<Announcement> pageParam = new Page<>(page, size);
QueryWrapper<Announcement> 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<Announcement> result = announcementService.page(pageParam, queryWrapper);
PageResult<Announcement> 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<String> 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<String> 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<String> 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<PageResult<SystemLog>> getSystemLogs(
@RequestParam(defaultValue = "1") int page,
@RequestParam(defaultValue = "10") int size,
@RequestParam(required = false) String keyword,
@RequestParam(required = false) String operationType) {
Page<SystemLog> pageParam = new Page<>(page, size);
QueryWrapper<SystemLog> 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<SystemLog> result = systemLogService.page(pageParam, queryWrapper);
PageResult<SystemLog> 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<String> 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<String> 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());
}
}
}

View File

@ -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<PageResult<Announcement>> getPublishedAnnouncements(
@RequestParam(defaultValue = "1") int page,
@RequestParam(defaultValue = "10") int size,
@RequestParam(required = false) Integer type) {
Page<Announcement> pageParam = new Page<>(page, size);
QueryWrapper<Announcement> 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<Announcement> result = announcementService.page(pageParam, queryWrapper);
PageResult<Announcement> 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<List<Announcement>> getLatestAnnouncements(
@RequestParam(defaultValue = "5") int limit) {
QueryWrapper<Announcement> queryWrapper = new QueryWrapper<>();
// 只查询已发布的公告
queryWrapper.eq("status", 1);
// 确保publish_time不为NULL然后按发布时间倒序限制数量
queryWrapper.isNotNull("publish_time")
.orderByDesc("publish_time")
.last("LIMIT " + limit);
List<Announcement> announcements = announcementService.list(queryWrapper);
return Result.success(announcements);
}
/**
* 获取公告详情
*/
@GetMapping("/{id}")
public Result<Announcement> getAnnouncementDetail(@PathVariable Long id) {
QueryWrapper<Announcement> 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);
}
}

View File

@ -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<List<Cart>> getCartList() {
Long userId = UserContext.getCurrentUserId();
List<Cart> cartList = cartService.getUserCart(userId);
return Result.success(cartList);
}
/**
* 获取购物车详细信息列表
*/
@GetMapping("/items")
public Result<List<CartItemDTO>> getCartItems() {
Long userId = UserContext.getCurrentUserId();
List<CartItemDTO> cartItems = cartService.getUserCartItems(userId);
return Result.success(cartItems);
}
/**
* 添加到购物车
*/
@PostMapping("/add")
public Result<String> addToCart(@RequestBody Map<String, Object> 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<String> updateQuantity(@PathVariable Long productId, @RequestBody Map<String, Integer> 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<String> 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<String> 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<Long> getCartCount() {
Long userId = UserContext.getCurrentUserId();
Long count = cartService.getCartCount(userId);
return Result.success(count);
}
/**
* 批量删除购物车商品
*/
@DeleteMapping("/batch")
public Result<String> batchRemoveFromCart(@RequestBody Map<String, List<Long>> data) {
try {
Long userId = UserContext.getCurrentUserId();
List<Long> 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<String> toggleSelect(@PathVariable Long productId, @RequestBody Map<String, Boolean> 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<String> toggleSelectAll(@RequestBody Map<String, Boolean> 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());
}
}
}

View File

@ -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<List<Category>> getCategoryList() {
try {
List<Category> categories = categoryService.getAllCategories();
return Result.success(categories);
} catch (Exception e) {
return Result.error("获取分类列表失败: " + e.getMessage());
}
}
/**
* 获取主分类列表用户端公共接口
*/
@GetMapping("/main")
public Result<List<Category>> getMainCategories() {
try {
List<Category> categories = categoryService.getMainCategories();
return Result.success(categories);
} catch (Exception e) {
return Result.error("获取主分类列表失败: " + e.getMessage());
}
}
/**
* 获取分类树形结构用户端公共接口
*/
@GetMapping("/tree")
public Result<List<Category>> getCategoryTree() {
try {
List<Category> categories = categoryService.getCategoryTree();
return Result.success(categories);
} catch (Exception e) {
return Result.error("获取分类树失败: " + e.getMessage());
}
}
/**
* 根据父ID获取子分类用户端公共接口
*/
@GetMapping("/children/{parentId}")
public Result<List<Category>> getChildCategories(@PathVariable Long parentId) {
try {
List<Category> categories = categoryService.getChildCategories(parentId);
return Result.success(categories);
} catch (Exception e) {
return Result.error("获取子分类失败: " + e.getMessage());
}
}
}

View File

@ -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<List<ChatSession>> getUserSessions() {
Long userId = UserContext.getCurrentUserId();
List<ChatSession> sessions = chatService.getUserSessions(userId);
return Result.success(sessions);
}
/**
* 商家获取聊天会话列表
*/
@GetMapping("/merchant/sessions")
public Result<List<ChatSession>> getMerchantSessions() {
Long userId = UserContext.getCurrentUserId();
Long merchantId = merchantService.getMerchantIdByUserId(userId);
if (merchantId == null) {
return Result.error("商家信息不存在");
}
List<ChatSession> sessions = chatService.getMerchantSessions(merchantId);
return Result.success(sessions);
}
/**
* 获取或创建聊天会话
*/
@PostMapping("/session")
public Result<ChatSession> createSession(@RequestParam Long merchantId) {
Long userId = UserContext.getCurrentUserId();
ChatSession session = chatService.getOrCreateSession(userId, merchantId);
return Result.success(session);
}
/**
* 获取会话消息列表
*/
@GetMapping("/session/{sessionId}/messages")
public Result<List<ChatMessage>> getSessionMessages(
@PathVariable Long sessionId,
@RequestParam(defaultValue = "1") Integer page,
@RequestParam(defaultValue = "20") Integer size) {
List<ChatMessage> messages = chatService.getSessionMessages(sessionId, page, size);
return Result.success(messages);
}
/**
* 用户发送消息
*/
@PostMapping("/user/send")
public Result<ChatMessage> sendUserMessage(@Valid @RequestBody SendMessageRequest request) {
Long userId = UserContext.getCurrentUserId();
ChatMessage message = chatService.sendUserMessage(userId, request);
return Result.success(message);
}
/**
* 商家发送消息
*/
@PostMapping("/merchant/send")
public Result<ChatMessage> 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<String> 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<Integer> getUnreadCount(@PathVariable Long sessionId) {
Long userId = UserContext.getCurrentUserId();
Integer count = chatService.getUnreadCount(sessionId, userId);
return Result.success(count);
}
}

View File

@ -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<Comment> 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<PageResult<Comment>> getProductComments(
@PathVariable Long productId,
@RequestParam(defaultValue = "1") Integer page,
@RequestParam(defaultValue = "10") Integer size) {
Page<Comment> pageObj = new Page<>(page, size);
IPage<Comment> result = commentService.getProductComments(pageObj, productId);
PageResult<Comment> pageResult = PageResult.of(
result.getTotal(),
result.getPages(),
result.getCurrent(),
result.getSize(),
result.getRecords()
);
return Result.success(pageResult);
}
/**
* 获取用户评论列表
*/
@GetMapping("/user")
public Result<PageResult<Comment>> getUserComments(
@RequestParam(defaultValue = "1") Integer page,
@RequestParam(defaultValue = "10") Integer size) {
Long userId = UserContext.getCurrentUserId();
Page<Comment> pageObj = new Page<>(page, size);
IPage<Comment> result = commentService.getUserComments(pageObj, userId);
PageResult<Comment> pageResult = PageResult.of(
result.getTotal(),
result.getPages(),
result.getCurrent(),
result.getSize(),
result.getRecords()
);
return Result.success(pageResult);
}
/**
* 获取产品平均评分
*/
@GetMapping("/product/{productId}/rating")
public Result<BigDecimal> getProductRating(@PathVariable Long productId) {
BigDecimal rating = commentService.getProductAverageRating(productId);
return Result.success(rating);
}
/**
* 获取产品评论数量
*/
@GetMapping("/product/{productId}/count")
public Result<Long> getProductCommentCount(@PathVariable Long productId) {
Long count = commentService.getProductCommentCount(productId);
return Result.success(count);
}
/**
* 检查用户是否已评论
*/
@GetMapping("/check")
public Result<Boolean> 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<String> replyComment(@PathVariable Long id, @RequestBody Map<String, String> 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<String> auditComment(@PathVariable Long id, @RequestBody Map<String, Integer> 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<String> 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<PageResult<Comment>> getAllComments(
@RequestParam(defaultValue = "1") Integer page,
@RequestParam(defaultValue = "10") Integer size,
@RequestParam(required = false) String keyword,
@RequestParam(required = false) Integer status) {
Page<Comment> pageObj = new Page<>(page, size);
IPage<Comment> result = commentService.getAllComments(pageObj, keyword, status);
PageResult<Comment> pageResult = PageResult.of(
result.getTotal(),
result.getPages(),
result.getCurrent(),
result.getSize(),
result.getRecords()
);
return Result.success(pageResult);
}
}

View File

@ -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<String, Object> testAlipayConfig() {
Map<String, Object> 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;
}
}

View File

@ -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<String> addFavorite(@RequestBody Map<String, Long> 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<String> removeFavorite(@RequestBody Map<String, Long> 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<Boolean> checkFavorite(@PathVariable Long productId) {
Long userId = UserContext.getCurrentUserId();
boolean isFavorited = favoriteService.isFavorited(userId, productId);
return Result.success(isFavorited);
}
/**
* 获取用户收藏列表
*/
@GetMapping("/list")
public Result<PageResult<Favorite>> getFavoriteList(
@RequestParam(defaultValue = "1") Integer page,
@RequestParam(defaultValue = "10") Integer size) {
Long userId = UserContext.getCurrentUserId();
Page<Favorite> pageObj = new Page<>(page, size);
IPage<Favorite> result = favoriteService.getUserFavorites(pageObj, userId);
PageResult<Favorite> pageResult = PageResult.of(
result.getTotal(),
result.getPages(),
result.getCurrent(),
result.getSize(),
result.getRecords()
);
return Result.success(pageResult);
}
/**
* 获取用户收藏数量
*/
@GetMapping("/count")
public Result<Long> getFavoriteCount() {
Long userId = UserContext.getCurrentUserId();
Long count = favoriteService.getUserFavoriteCount(userId);
return Result.success(count);
}
/**
* 批量取消收藏
*/
@DeleteMapping("/batch")
public Result<String> batchRemoveFavorite(@RequestBody Map<String, List<Long>> data) {
try {
Long userId = UserContext.getCurrentUserId();
List<Long> 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());
}
}
}

View File

@ -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<String> 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<List<String>> uploadFiles(@RequestParam("files") MultipartFile[] files) {
try {
List<String> 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<String> 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());
}
}
}

View File

@ -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<String> 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<String> 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<String> generatePassword(@PathVariable String password) {
String encodedPassword = PasswordUtil.encode(password);
return Result.success("加密后的密码: " + encodedPassword);
}
}

View File

@ -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<InventoryService.InventoryStatistics> 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<PageResult<InventoryWithProduct>> 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<InventoryWithProduct> pageObj = new Page<>(page, size);
IPage<InventoryWithProduct> result = inventoryService.getMerchantInventoryPage(pageObj, merchantId, keyword);
PageResult<InventoryWithProduct> pageResult = PageResult.of(
result.getTotal(),
result.getPages(),
result.getCurrent(),
result.getSize(),
result.getRecords()
);
return Result.success(pageResult);
}
/**
* 更新库存数量
*/
@PutMapping("/{productId}/stock")
public Result<String> updateStock(@PathVariable Long productId, @RequestBody Map<String, Integer> 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<String> setWarningQuantity(@PathVariable Long productId, @RequestBody Map<String, Integer> 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<String> batchUpdateStock(@RequestBody Map<String, Object> data) {
try {
@SuppressWarnings("unchecked")
List<Long> productIds = (List<Long>) data.get("productIds");
@SuppressWarnings("unchecked")
List<Integer> quantities = (List<Integer>) 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());
}
}
}

View File

@ -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<String> 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<Map<String, Object>> login(@RequestBody Map<String, String> 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<String, Object> 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<Merchant> getProfile() {
Long userId = UserContext.getCurrentUserId();
Merchant merchant = merchantService.getByUserId(userId);
return Result.success(merchant);
}
/**
* 更新商家信息
*/
@PutMapping("/profile")
public Result<Merchant> 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<PageResult<Merchant>> getMerchantList(
@RequestParam(defaultValue = "1") Integer page,
@RequestParam(defaultValue = "10") Integer size,
@RequestParam(required = false) String keyword,
@RequestParam(required = false) Integer status) {
Page<Merchant> pageObj = new Page<>(page, size);
IPage<Merchant> result = merchantService.getMerchantPage(pageObj, keyword, status);
PageResult<Merchant> pageResult = PageResult.of(
result.getTotal(),
result.getPages(),
result.getCurrent(),
result.getSize(),
result.getRecords()
);
return Result.success(pageResult);
}
/**
* 审核商家管理员
*/
@PutMapping("/{id}/verify")
public Result<String> verifyMerchant(@PathVariable Long id, @RequestBody Map<String, Object> 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<MerchantDashboardDTO> 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<PageResult<Product>> 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<Product> pageObj = new Page<>(page, size);
IPage<Product> result = productService.getMerchantProductPage(pageObj, merchant.getId(), keyword, status);
PageResult<Product> 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<Product> 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<Product> 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<String> 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<String> updateMerchantProductStatus(@PathVariable Long id, @RequestBody Map<String, Integer> 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<Product> 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<Merchant> 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<String> 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<String> changePassword(@RequestBody Map<String, String> 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<String> uploadProductImage(@RequestParam("file") MultipartFile file) {
try {
String imageUrl = fileUploadUtil.uploadFile(file);
return Result.success("图片上传成功", imageUrl);
} catch (Exception e) {
return Result.error("图片上传失败: " + e.getMessage());
}
}
}

View File

@ -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<Order> 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<Order> buyNow(@RequestBody Map<String, Object> 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<PageResult<Order>> getOrderList(
@RequestParam(defaultValue = "1") Integer page,
@RequestParam(defaultValue = "10") Integer size,
@RequestParam(required = false) Integer status) {
Long userId = UserContext.getCurrentUserId();
Page<Order> pageObj = new Page<>(page, size);
IPage<Order> result = orderService.getUserOrders(pageObj, userId, status);
PageResult<Order> pageResult = PageResult.of(
result.getTotal(),
result.getPages(),
result.getCurrent(),
result.getSize(),
result.getRecords()
);
return Result.success(pageResult);
}
/**
* 获取订单详情
*/
@GetMapping("/{orderId}")
public Result<Order> 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<String> payOrder(@PathVariable Long orderId, @RequestBody Map<String, String> 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<String> cancelOrder(@PathVariable Long orderId, @RequestBody Map<String, String> 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<String> 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<String> 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<PageResult<Order>> getMerchantOrderList(
@RequestParam(defaultValue = "1") Integer page,
@RequestParam(defaultValue = "10") Integer size,
@RequestParam(required = false) Integer status) {
Long merchantId = UserContext.getMerchantId();
Page<Order> pageObj = new Page<>(page, size);
IPage<Order> result = orderService.getMerchantOrders(pageObj, merchantId, status);
PageResult<Order> pageResult = PageResult.of(
result.getTotal(),
result.getPages(),
result.getCurrent(),
result.getSize(),
result.getRecords()
);
return Result.success(pageResult);
}
/**
* 商家发货
*/
@PostMapping("/{orderId}/ship")
public Result<String> shipOrder(@PathVariable Long orderId, @RequestBody Map<String, String> 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());
}
}
}

View File

@ -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<String> createAlipay(@RequestBody Map<String, Object> 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<String, String> params = new HashMap<>();
Map<String, String[]> 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<String, String> params = new HashMap<>();
Map<String, String[]> 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<Map<String, Object>> queryPayResult(@PathVariable String paymentNo) {
try {
Map<String, Object> result = alipayService.queryPayResult(paymentNo);
return Result.success("查询成功", result);
} catch (Exception e) {
log.error("查询支付结果异常", e);
return Result.error("查询失败: " + e.getMessage());
}
}
/**
* 申请退款
*/
@PostMapping("/refund")
public Result<Map<String, Object>> refund(@RequestBody Map<String, Object> refundData) {
try {
String paymentNo = (String) refundData.get("paymentNo");
BigDecimal refundAmount = new BigDecimal(refundData.get("refundAmount").toString());
String refundReason = (String) refundData.get("refundReason");
Map<String, Object> 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<Payment> 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());
}
}
}

View File

@ -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<Map<String, Object>> checkPaymentStatus(@PathVariable Long orderId) {
try {
Map<String, Object> 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<String, Object> alipayResult = alipayService.queryPayResult(payment.getPaymentNo());
if ((Boolean) alipayResult.get("success")) {
String tradeStatus = (String) alipayResult.get("tradeStatus");
if ("TRADE_SUCCESS".equals(tradeStatus)) {
log.info("🔄 发现支付成功但状态未更新,手动同步状态");
// 手动同步支付状态
Map<String, String> 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 "未知状态";
}
}
}

View File

@ -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<PageResult<Product>> 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<Product> pageObj = new Page<>(page, size);
IPage<Product> result = productService.getProductList(pageObj, keyword, categoryId, minPrice, maxPrice, sort);
PageResult<Product> pageResult = PageResult.of(
result.getTotal(),
result.getPages(),
result.getCurrent(),
result.getSize(),
result.getRecords()
);
return Result.success(pageResult);
}
/**
* 获取产品详情
*/
@GetMapping("/{id}")
public Result<Product> getProductDetail(@PathVariable Long id) {
Product product = productService.getProductDetail(id);
if (product != null) {
return Result.success(product);
} else {
return Result.error("产品不存在");
}
}
/**
* 获取热门产品
*/
@GetMapping("/hot")
public Result<List<Product>> getHotProducts(@RequestParam(defaultValue = "8") Integer limit) {
List<Product> products = productService.getHotProducts(limit);
return Result.success(products);
}
/**
* 获取新品推荐
*/
@GetMapping("/new")
public Result<List<Product>> getNewProducts(@RequestParam(defaultValue = "8") Integer limit) {
List<Product> products = productService.getNewProducts(limit);
return Result.success(products);
}
/**
* 获取分类产品
*/
@GetMapping("/category/{categoryId}")
public Result<PageResult<Product>> getCategoryProducts(
@PathVariable Long categoryId,
@RequestParam(defaultValue = "1") Integer page,
@RequestParam(defaultValue = "12") Integer size,
@RequestParam(required = false) String sort) {
Page<Product> pageObj = new Page<>(page, size);
IPage<Product> result = productService.getCategoryProducts(pageObj, categoryId, sort);
PageResult<Product> pageResult = PageResult.of(
result.getTotal(),
result.getPages(),
result.getCurrent(),
result.getSize(),
result.getRecords()
);
return Result.success(pageResult);
}
/**
* 搜索产品
*/
@GetMapping("/search")
public Result<PageResult<Product>> searchProducts(
@RequestParam String keyword,
@RequestParam(defaultValue = "1") Integer page,
@RequestParam(defaultValue = "12") Integer size,
@RequestParam(required = false) String sort) {
Page<Product> pageObj = new Page<>(page, size);
IPage<Product> result = productService.searchProducts(pageObj, keyword, sort);
PageResult<Product> pageResult = PageResult.of(
result.getTotal(),
result.getPages(),
result.getCurrent(),
result.getSize(),
result.getRecords()
);
return Result.success(pageResult);
}
/**
* 创建产品管理员
*/
@PostMapping
public Result<Product> 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<Product> 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<String> 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<String> 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<String> updateStock(@PathVariable Long id, @RequestBody Map<String, Integer> 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<String> updateStatus(@PathVariable Long id, @RequestBody Map<String, Integer> 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());
}
}
}

View File

@ -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<Map<String, Object>> getOverviewStatistics() {
Map<String, Object> statistics = statisticsService.getOverviewStatistics();
return Result.success(statistics);
}
/**
* 获取销售统计数据管理员
*/
@GetMapping("/sales")
public Result<Map<String, Object>> getSalesStatistics(
@RequestParam(required = false) String startDate,
@RequestParam(required = false) String endDate) {
Map<String, Object> statistics = statisticsService.getSalesStatistics(startDate, endDate);
return Result.success(statistics);
}
/**
* 获取产品统计数据管理员
*/
@GetMapping("/product")
public Result<Map<String, Object>> getProductStatistics() {
Map<String, Object> statistics = statisticsService.getProductStatistics();
return Result.success(statistics);
}
/**
* 获取用户统计数据管理员
*/
@GetMapping("/user")
public Result<Map<String, Object>> getUserStatistics() {
Map<String, Object> statistics = statisticsService.getUserStatistics();
return Result.success(statistics);
}
}

View File

@ -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<String> 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<Map<String, Object>> login(@RequestBody Map<String, String> 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<String, Object> 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<User> getProfile() {
Long userId = UserContext.getCurrentUserId();
User user = userService.getById(userId);
return Result.success(user);
}
/**
* 更新用户信息
*/
@PutMapping("/profile")
public Result<User> updateProfile(@RequestBody User user) {
Long userId = UserContext.getCurrentUserId();
user.setId(userId);
User updatedUser = userService.updateProfile(user);
return Result.success("更新成功", updatedUser);
}
/**
* 修改密码
*/
@PutMapping("/password")
public Result<String> changePassword(@RequestBody Map<String, String> 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<String> logout() {
// JWT是无状态的客户端删除token即可
return Result.success("注销成功");
}
/**
* 发送邮箱验证码
*/
@PostMapping("/send-email-code")
public Result<String> 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<String> 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<PageResult<User>> getUserList(
@RequestParam(defaultValue = "1") Integer page,
@RequestParam(defaultValue = "10") Integer size,
@RequestParam(required = false) String keyword,
@RequestParam(required = false) Integer status) {
Page<User> pageObj = new Page<>(page, size);
IPage<User> result = userService.getUserList(pageObj, keyword, status);
PageResult<User> pageResult = PageResult.of(
result.getTotal(),
result.getPages(),
result.getCurrent(),
result.getSize(),
result.getRecords()
);
return Result.success(pageResult);
}
/**
* 更新用户状态管理员
*/
@PutMapping("/{id}/status")
public Result<String> updateUserStatus(@PathVariable Long id, @RequestBody Map<String, Integer> statusData) {
Integer status = statusData.get("status");
boolean success = userService.updateUserStatus(id, status);
if (success) {
return Result.success("状态更新成功");
} else {
return Result.error("状态更新失败");
}
}
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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;
}
}

View File

@ -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;
}

View File

@ -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<RecentOrderDTO> recentOrders;
/**
* 销售趋势数据
*/
private List<SalesTrendDTO> salesTrend;
/**
* 商品销量排行
*/
private List<ProductSalesDTO> 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;
}
}

View File

@ -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;
}

View File

@ -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<Long> cartIds;
/**
* 商品信息列表立即购买时使用
*/
private List<OrderItemRequest> 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());
}
}

View File

@ -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<OrderItemDTO> orderItems;
// 物流信息
private String logisticsCompany;
private String trackingNumber;
private Integer logisticsStatus;
private String logisticsStatusName;
}

View File

@ -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;
}

View File

@ -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;
}
}

View File

@ -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;
}

View File

@ -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;
}
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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<Category> 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;
}
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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<OrderDetail> orderDetails;
@TableField(exist = false)
private String userName;
@TableField(exist = false)
private String merchantName;
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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<String> 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;
}
}

View File

@ -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; // 排序
}

View File

@ -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<Permission> permissions;
}

View File

@ -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";
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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;
}
}

View File

@ -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<String> handleBusinessException(BusinessException e) {
log.error("业务异常: {}", e.getMessage());
return Result.error(e.getMessage());
}
/**
* 处理参数校验异常
*/
@ExceptionHandler(MethodArgumentNotValidException.class)
@ResponseStatus(HttpStatus.BAD_REQUEST)
public Result<String> handleValidException(MethodArgumentNotValidException e) {
List<FieldError> 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<String> handleBindException(BindException e) {
List<FieldError> 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<String> handleConstraintViolationException(ConstraintViolationException e) {
Set<ConstraintViolation<?>> 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<String> handleRuntimeException(RuntimeException e) {
log.error("运行时异常", e);
return Result.error("系统异常,请稍后重试");
}
/**
* 处理其他异常
*/
@ExceptionHandler(Exception.class)
@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
public Result<String> handleException(Exception e) {
log.error("系统异常", e);
return Result.error("系统异常,请稍后重试");
}
}

View File

@ -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;
}
}

View File

@ -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<Address> {
@Select("SELECT * FROM addresses WHERE user_id = #{userId} AND deleted = 0 ORDER BY is_default DESC, create_time DESC")
List<Address> 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);
}

View File

@ -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<Admin> {
/**
* 根据用户名查询管理员
*/
@Select("SELECT * FROM admins WHERE username = #{username}")
Admin selectByUsername(@Param("username") String username);
/**
* 查询管理员详情包含角色信息
*/
Admin selectAdminWithRole(@Param("id") Long id);
}

View File

@ -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<Announcement> {
}

View File

@ -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<Cart> {
/**
* 获取用户购物车详情包含商品和商家信息
*/
List<CartItemDTO> getUserCartItems(@Param("userId") Long userId);
}

View File

@ -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<Category> {
@Select("SELECT * FROM categories WHERE parent_id = #{parentId} AND deleted = 0 ORDER BY sort ASC")
List<Category> findByParentId(@Param("parentId") Long parentId);
@Select("SELECT * FROM categories WHERE status = 1 AND deleted = 0 ORDER BY sort ASC")
List<Category> findActiveCategories();
@Select("SELECT COUNT(*) FROM categories WHERE deleted = 0")
Long countCategories();
}

View File

@ -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<ChatMessage> {
/**
* 获取会话消息列表
*/
List<ChatMessage> 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);
}

View File

@ -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<ChatSession> {
/**
* 获取用户的聊天会话列表
*/
List<ChatSession> getUserSessions(@Param("userId") Long userId);
/**
* 获取商家的聊天会话列表
*/
List<ChatSession> getMerchantSessions(@Param("merchantId") Long merchantId);
/**
* 获取或创建聊天会话
*/
ChatSession getOrCreateSession(@Param("userId") Long userId, @Param("merchantId") Long merchantId);
}

View File

@ -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<Comment> {
@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<Comment> 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<Comment> 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);
}

View File

@ -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<Favorite> {
/**
* 查询用户收藏列表包含商品信息
*/
IPage<Favorite> getUserFavoritesWithProduct(Page<Favorite> 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<Favorite> 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);
}

View File

@ -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<Inventory> {
/**
* 获取商家库存分页数据包含商品信息
*/
IPage<InventoryWithProduct> getMerchantInventoryPage(Page<InventoryWithProduct> page,
@Param("merchantId") Long merchantId,
@Param("keyword") String keyword);
/**
* 获取商家库存列表包含商品信息
*/
List<InventoryWithProduct> getMerchantInventoryList(@Param("merchantId") Long merchantId);
/**
* 获取商家库存列表不分页
*/
/**
* 获取库存不足商品
*/
List<InventoryWithProduct> getLowStockProducts(@Param("merchantId") Long merchantId);
/**
* 获取缺货商品
*/
List<InventoryWithProduct> getOutOfStockProducts(@Param("merchantId") Long merchantId);
/**
* 按商品ID删除库存
*/
int deleteByProductId(@Param("productId") Long productId);
/**
* 批量插入库存记录
*/
int batchInsert(@Param("list") List<Inventory> inventoryList);
}

View File

@ -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<Merchant> {
/**
* 分页查询商家列表包含用户信息
*/
IPage<Merchant> selectMerchantPage(Page<Merchant> 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<MerchantDashboardDTO.RecentOrderDTO> getRecentOrdersByMerchant(@Param("merchantId") Long merchantId);
List<MerchantDashboardDTO.SalesTrendDTO> getSalesTrendByMerchant(@Param("merchantId") Long merchantId);
List<MerchantDashboardDTO.ProductSalesDTO> getTopProductsByMerchant(@Param("merchantId") Long merchantId);
}

View File

@ -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<OrderDetail> {
/**
* 获取订单详情包含商家ID
*/
List<OrderDetail> 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<OrderDetail> 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);
}

View File

@ -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<Order> {
@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<Order> 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<Order> 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);
}

View File

@ -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<Payment> {
}

View File

@ -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<Permission> {
/**
* 根据角色ID查询权限列表
*/
List<Permission> selectPermissionsByRoleId(@Param("roleId") Long roleId);
}

View File

@ -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<ProductImage> {
/**
* 根据商品ID获取图片列表
*/
@Select("SELECT * FROM product_images WHERE product_id = #{productId} ORDER BY sort_order ASC, id ASC")
List<ProductImage> 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);
}

View File

@ -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<Product> {
@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<Product> 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<Product> 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<Product> 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();
}

View File

@ -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<Role> {
/**
* 根据角色ID查询角色及权限
*/
Role selectRoleWithPermissions(@Param("roleId") Long roleId);
}

Some files were not shown because too many files have changed in this diff Show More