初始化v1
This commit is contained in:
8
.idea/.gitignore
generated
vendored
Normal file
8
.idea/.gitignore
generated
vendored
Normal file
@@ -0,0 +1,8 @@
|
||||
# 默认忽略的文件
|
||||
/shelf/
|
||||
/workspace.xml
|
||||
# 基于编辑器的 HTTP 客户端请求
|
||||
/httpRequests/
|
||||
# Datasource local storage ignored files
|
||||
/dataSources/
|
||||
/dataSources.local.xml
|
||||
18
.idea/compiler.xml
generated
Normal file
18
.idea/compiler.xml
generated
Normal file
@@ -0,0 +1,18 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="CompilerConfiguration">
|
||||
<annotationProcessing>
|
||||
<profile name="Maven default annotation processors profile" enabled="true">
|
||||
<sourceOutputDir name="target/generated-sources/annotations" />
|
||||
<sourceTestOutputDir name="target/generated-test-sources/test-annotations" />
|
||||
<outputRelativeToContentRoot value="true" />
|
||||
<module name="nl-shop" />
|
||||
</profile>
|
||||
</annotationProcessing>
|
||||
</component>
|
||||
<component name="JavacSettings">
|
||||
<option name="ADDITIONAL_OPTIONS_OVERRIDE">
|
||||
<module name="nl-shop" options="-parameters" />
|
||||
</option>
|
||||
</component>
|
||||
</project>
|
||||
6
.idea/encodings.xml
generated
Normal file
6
.idea/encodings.xml
generated
Normal file
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="Encoding">
|
||||
<file url="file://$PROJECT_DIR$/src/main/java" charset="UTF-8" />
|
||||
</component>
|
||||
</project>
|
||||
20
.idea/jarRepositories.xml
generated
Normal file
20
.idea/jarRepositories.xml
generated
Normal file
@@ -0,0 +1,20 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="RemoteRepositoriesConfiguration">
|
||||
<remote-repository>
|
||||
<option name="id" value="central" />
|
||||
<option name="name" value="Central Repository" />
|
||||
<option name="url" value="https://repo.maven.apache.org/maven2" />
|
||||
</remote-repository>
|
||||
<remote-repository>
|
||||
<option name="id" value="central" />
|
||||
<option name="name" value="Maven Central repository" />
|
||||
<option name="url" value="https://repo1.maven.org/maven2" />
|
||||
</remote-repository>
|
||||
<remote-repository>
|
||||
<option name="id" value="jboss.community" />
|
||||
<option name="name" value="JBoss Community repository" />
|
||||
<option name="url" value="https://repository.jboss.org/nexus/content/repositories/public/" />
|
||||
</remote-repository>
|
||||
</component>
|
||||
</project>
|
||||
12
.idea/misc.xml
generated
Normal file
12
.idea/misc.xml
generated
Normal file
@@ -0,0 +1,12 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="ExternalStorageConfigurationManager" enabled="true" />
|
||||
<component name="MavenProjectsManager">
|
||||
<option name="originalFiles">
|
||||
<list>
|
||||
<option value="$PROJECT_DIR$/pom.xml" />
|
||||
</list>
|
||||
</option>
|
||||
</component>
|
||||
<component name="ProjectRootManager" version="2" languageLevel="JDK_18" default="true" project-jdk-name="corretto-18" project-jdk-type="JavaSDK" />
|
||||
</project>
|
||||
6
.idea/vcs.xml
generated
Normal file
6
.idea/vcs.xml
generated
Normal file
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="VcsDirectoryMappings">
|
||||
<mapping directory="$PROJECT_DIR$" vcs="Git" />
|
||||
</component>
|
||||
</project>
|
||||
118
1
Normal file
118
1
Normal file
@@ -0,0 +1,118 @@
|
||||
# 奶茶店线上订单系统需求规格说明书
|
||||
|
||||
## 顾客端功能模块
|
||||
|
||||
### 1. 用户账户管理
|
||||
- 实现用户注册功能,支持上传完整个人信息,包括:姓名(必填)、性别(可选)、手机号码(必填,需短信验证)、密码(需符合安全规范)、收货/取餐地址(支持多地址管理,包含省市区、详细地址、联系人、联系电话、是否默认地址)、头像图片(支持JPG/PNG格式,最大2MB)
|
||||
- 开发登录功能,支持手机号+密码登录及验证码快捷登录
|
||||
- 提供个人信息修改界面,允许用户更新除手机号外的所有注册信息,修改手机号需通过原手机号验证
|
||||
|
||||
### 2. 奶茶产品选购系统
|
||||
- 设计产品分类展示功能,按产品类型(如奶茶类、果茶类、咖啡类等)进行分类导航
|
||||
- 实现产品详情页,完整展示产品信息:名称、价格、规格(大/中/小杯)、基础配料、产品图片、营养成分、用户评价
|
||||
- 开发个性化定制功能,支持:
|
||||
- 甜度调节(全糖、七分糖、五分糖、三分糖、无糖)
|
||||
- 冰度选择(正常冰、少冰、去冰、热饮)
|
||||
- 配料添加/移除(额外收费配料需明确标注价格)
|
||||
- 实现"加入购物车"功能,支持选择数量(1-10杯)并加入购物车
|
||||
|
||||
### 3. 购物车管理系统
|
||||
- 开发购物车页面,展示所有已选商品,包含:产品名称、定制信息、单价、数量、小计金额
|
||||
- 实现购物车商品管理功能:
|
||||
- 修改商品数量(1-10杯范围内)
|
||||
- 移除单个商品
|
||||
- 清空购物车
|
||||
- 选择/取消选择商品
|
||||
- 实时计算并展示所选商品的总计金额
|
||||
|
||||
### 4. 订单下单与支付流程
|
||||
- 开发订单确认页面,展示所选商品清单、定制信息、数量、单价、小计、配送费(如适用)、总计金额
|
||||
- 实现收货/取餐地址选择功能,支持从地址库选择或临时添加新地址
|
||||
- 集成支付系统,支持微信支付和支付宝两种支付方式
|
||||
- 开发订单提交功能,提交后生成唯一订单号,跳转至支付页面
|
||||
- 实现支付状态同步,支付成功后跳转至订单成功页面,支付失败提供重试入口
|
||||
|
||||
### 5. 订单信息查询系统
|
||||
- 开发订单中心,按状态分类展示订单:
|
||||
- 待支付订单(30分钟内未支付自动取消)
|
||||
- 制作中订单
|
||||
- 待取餐/配送中订单
|
||||
- 已完成订单
|
||||
- 已取消订单
|
||||
- 实现订单详情页,展示完整订单信息:订单号、下单时间、商品清单、定制信息、数量、单价、总计金额、支付方式、支付时间、取餐/配送地址、订单状态
|
||||
|
||||
### 6. 公告信息展示系统
|
||||
- 开发公告列表页面,按发布时间倒序展示门店公告
|
||||
- 实现公告详情页,完整展示公告内容、发布时间、发布人
|
||||
- 支持公告分类:活动公告、通知公告、新品上市等
|
||||
|
||||
### 7. 在线留言反馈系统
|
||||
- 开发留言提交功能,支持用户选择留言类型(点单问题、取餐问题、产品问题、其他建议)并输入详细内容(最多500字),可上传图片(最多3张)
|
||||
- 实现留言记录查询功能,展示历史留言内容、提交时间、处理状态、商家回复
|
||||
- 开发留言通知功能,当商家回复后推送消息提醒用户
|
||||
|
||||
### 8. 订单跟踪系统
|
||||
- 实现订单全流程状态跟踪,展示关键节点时间:
|
||||
- 订单提交时间
|
||||
- 支付完成时间
|
||||
- 商家接单时间
|
||||
- 开始制作时间
|
||||
- 制作完成时间
|
||||
- 取餐/配送完成时间
|
||||
- 开发订单状态实时更新功能,状态变更时推送消息通知用户
|
||||
|
||||
## 商家端功能模块
|
||||
|
||||
### 1. 商家账户与门店管理
|
||||
- 实现商家注册功能,支持上传完整门店及负责人信息,包括:
|
||||
- 门店信息:门店名称、详细地址(需定位确认)、联系电话、营业时间、门店图片(3-5张)、门店简介
|
||||
- 负责人信息:姓名、手机号码(需验证)、密码(需符合安全规范)、邮箱(需验证)、头像图片
|
||||
- 开发商家登录功能,支持手机号+密码登录
|
||||
- 提供信息修改界面,允许商家更新门店信息及负责人信息,修改关键信息(如门店地址、联系电话)需审核
|
||||
|
||||
### 2. 产品管理系统
|
||||
- 开发产品管理功能,支持:
|
||||
- 新增产品:填写产品名称、选择分类、设置价格、规格(大/中/小杯,可分别定价)、上传产品图片、设置基础配料、添加产品描述
|
||||
- 编辑产品:修改现有产品的所有信息
|
||||
- 下架产品:支持临时下架和永久删除功能
|
||||
- 实现配料管理功能,支持添加、编辑、删除配料,设置配料是否收费及单价
|
||||
- 开发产品上下架状态管理,支持批量操作
|
||||
|
||||
### 3. 订单管理系统
|
||||
- 开发新订单提醒功能,当有新订单时通过系统通知和声音提醒
|
||||
- 实现订单处理功能:
|
||||
- 接单/拒单操作(拒单需选择原因)
|
||||
- 更新订单制作状态(开始制作、制作完成)
|
||||
- 标记订单状态(待取餐、已完成、已取消)
|
||||
- 开发订单查询功能,支持按时间(今日/昨日/近7天/自定义)、订单状态、订单号进行查询
|
||||
- 实现订单详情查看,展示完整订单信息及客户信息
|
||||
|
||||
### 4. 库存管理系统
|
||||
- 开发原料管理功能,支持添加、编辑、删除原料信息(名称、单位、规格、初始库存)
|
||||
- 实现库存操作功能:
|
||||
- 入库记录(原料名称、数量、单价、供应商、入库日期)
|
||||
- 出库记录(原料名称、数量、用途、出库日期)
|
||||
- 开发库存预警功能,支持设置各原料的低库存阈值,当库存低于阈值时发出预警提醒
|
||||
- 实现库存报表功能,展示当前库存余量、库存变动记录、库存预警列表
|
||||
|
||||
### 5. 公告发布系统
|
||||
- 开发公告编辑功能,支持富文本编辑,可插入图片、链接
|
||||
- 实现公告发布功能,支持设置公告类型、发布范围、生效时间
|
||||
- 开发公告管理功能,支持查看、编辑、删除已发布公告,置顶重要公告
|
||||
|
||||
### 6. 评价管理系统
|
||||
- 开发评价查看功能,展示用户对产品的评价,包括:评价内容、评分(1-5星)、评价时间、评价图片、对应的订单信息
|
||||
- 实现评价回复功能,支持商家对用户评价进行文字回复
|
||||
- 开发负面评价处理功能,支持对低分评价(1-2星)进行申诉处理,记录处理结果
|
||||
|
||||
### 7. 数据统计分析系统
|
||||
- 开发销售数据统计功能,支持按日、周、月查看:
|
||||
- 销售总量(杯数)
|
||||
- 销售总额
|
||||
- 订单数量
|
||||
- 客单价
|
||||
- 实现热门产品排行功能,按销量和销售额分别排行
|
||||
- 开发数据可视化功能,通过图表展示销售趋势、产品销售分布、用户评价分析
|
||||
- 支持数据导出功能,可导出Excel格式报表
|
||||
|
||||
所有功能模块需保证界面友好、操作便捷、响应迅速,并确保数据安全与用户隐私保护。
|
||||
171
README.md
Normal file
171
README.md
Normal file
@@ -0,0 +1,171 @@
|
||||
# 奶茶店管理系统
|
||||
|
||||
基于Spring Boot + MyBatis + MySQL + Thymeleaf的奶茶店管理系统,包含用户端和商家端两个主要模块,集成协同过滤推荐算法。
|
||||
|
||||
## 技术栈
|
||||
|
||||
- **后端框架**: Spring Boot 2.7.18
|
||||
- **数据库**: MySQL 8.0+
|
||||
- **ORM**: MyBatis
|
||||
- **认证**: JWT Token
|
||||
- **前端**: HTML + Thymeleaf + Bootstrap 5
|
||||
- **算法**: 协同过滤推荐算法(基于用户的协同过滤)
|
||||
|
||||
## 功能模块
|
||||
|
||||
### 用户端功能
|
||||
1. 用户注册登录
|
||||
2. 个人信息管理
|
||||
3. 商品浏览(支持分类筛选)
|
||||
4. 商品个性化定制(甜度、冰度、配料)
|
||||
5. 购物车管理
|
||||
6. 订单下单支付(模拟微信/支付宝)
|
||||
7. 订单跟踪
|
||||
8. 个性化商品推荐(协同过滤算法)
|
||||
9. 公告查看
|
||||
10. 留言反馈
|
||||
|
||||
### 商家端功能
|
||||
1. 商家注册登录
|
||||
2. 商家信息管理
|
||||
3. 商品管理(新增/编辑/下架)
|
||||
4. 商品规格和配料管理
|
||||
5. 订单管理(接单/拒单/状态更新)
|
||||
6. 库存管理(入库/出库/预警)
|
||||
7. 公告发布
|
||||
8. 评价管理(查看/回复)
|
||||
9. 数据统计(日/周/月销量、营收、热门商品)
|
||||
|
||||
## 数据库设计
|
||||
|
||||
数据库表结构请参考 `src/main/resources/db/schema.sql`
|
||||
|
||||
主要表包括:
|
||||
- user, user_address - 用户相关
|
||||
- merchant - 商家
|
||||
- product, product_spec, product_topping, product_custom - 商品相关
|
||||
- cart - 购物车
|
||||
- order, order_item, order_track - 订单相关
|
||||
- inventory, inventory_log - 库存相关
|
||||
- announcement - 公告
|
||||
- message - 留言反馈
|
||||
- review - 评价
|
||||
- user_behavior, user_similarity - 协同过滤算法相关
|
||||
|
||||
## 安装和运行
|
||||
|
||||
### 1. 环境要求
|
||||
- JDK 1.8+
|
||||
- Maven 3.6+
|
||||
- MySQL 8.0+
|
||||
|
||||
### 2. 数据库配置
|
||||
1. 创建数据库:
|
||||
```sql
|
||||
CREATE DATABASE nl_shop CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
|
||||
```
|
||||
|
||||
2. 执行建表SQL:
|
||||
```bash
|
||||
mysql -u root -p nl_shop < src/main/resources/db/schema.sql
|
||||
```
|
||||
|
||||
3. 修改配置文件 `src/main/resources/application.yml`:
|
||||
```yaml
|
||||
spring:
|
||||
datasource:
|
||||
url: jdbc:mysql://localhost:3306/nl_shop?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai
|
||||
username: root
|
||||
password: your_password
|
||||
```
|
||||
|
||||
### 3. 编译运行
|
||||
```bash
|
||||
mvn clean install
|
||||
mvn spring-boot:run
|
||||
```
|
||||
|
||||
### 4. 访问系统
|
||||
- 用户端登录: http://localhost:8080/user/login.html
|
||||
- 商家端登录: http://localhost:8080/merchant/login.html
|
||||
|
||||
## 协同过滤算法
|
||||
|
||||
系统实现了基于用户的协同过滤推荐算法:
|
||||
|
||||
1. **用户行为记录**: 记录用户的浏览、购买、评分行为
|
||||
2. **相似度计算**: 使用余弦相似度计算用户之间的相似度
|
||||
3. **推荐生成**: 基于相似用户的偏好生成推荐列表
|
||||
4. **冷启动处理**: 新用户推荐热门商品
|
||||
|
||||
算法实现在 `CollaborativeFilteringService` 中。
|
||||
|
||||
## API接口
|
||||
|
||||
### 用户端接口
|
||||
- POST `/user/register` - 用户注册
|
||||
- POST `/user/login` - 用户登录
|
||||
- GET `/user/products` - 商品列表
|
||||
- GET `/user/products/{id}` - 商品详情
|
||||
- GET `/user/recommend` - 个性化推荐
|
||||
- POST `/user/cart/add` - 加入购物车
|
||||
- POST `/user/order/create` - 创建订单
|
||||
- POST `/user/order/{id}/pay` - 支付订单
|
||||
|
||||
### 商家端接口
|
||||
- POST `/merchant/register` - 商家注册
|
||||
- POST `/merchant/login` - 商家登录
|
||||
- GET `/merchant/products` - 商品列表
|
||||
- POST `/merchant/products` - 新增商品
|
||||
- GET `/merchant/orders` - 订单列表
|
||||
- PUT `/merchant/orders/{id}/accept` - 接单
|
||||
- GET `/merchant/statistics/sales` - 销量统计
|
||||
|
||||
详细API文档请参考Controller代码。
|
||||
|
||||
## 项目结构
|
||||
|
||||
```
|
||||
nl_shop/
|
||||
├── src/main/java/com/nlshop/
|
||||
│ ├── config/ # 配置类
|
||||
│ ├── controller/ # 控制器层
|
||||
│ │ ├── user/ # 用户端控制器
|
||||
│ │ └── merchant/ # 商家端控制器
|
||||
│ ├── service/ # 业务逻辑层
|
||||
│ │ ├── user/
|
||||
│ │ ├── merchant/
|
||||
│ │ └── algorithm/ # 协同过滤算法
|
||||
│ ├── mapper/ # MyBatis Mapper
|
||||
│ ├── entity/ # 实体类
|
||||
│ ├── dto/ # 数据传输对象
|
||||
│ ├── util/ # 工具类
|
||||
│ └── interceptor/ # 拦截器
|
||||
└── src/main/resources/
|
||||
├── mapper/ # MyBatis XML映射
|
||||
├── templates/ # Thymeleaf模板
|
||||
└── db/ # 数据库脚本
|
||||
```
|
||||
|
||||
## 注意事项
|
||||
|
||||
1. JWT Token默认有效期为24小时,可在 `application.yml` 中配置
|
||||
2. 文件上传路径配置在 `application.yml` 的 `file.upload-path`
|
||||
3. 密码使用MD5加密(生产环境建议使用BCrypt)
|
||||
4. 支付功能为模拟实现,实际项目需要对接真实支付接口
|
||||
|
||||
## 开发计划
|
||||
|
||||
- [x] 项目基础架构
|
||||
- [x] 数据库设计
|
||||
- [x] 用户端功能
|
||||
- [x] 商家端功能
|
||||
- [x] 协同过滤算法
|
||||
- [x] 前端页面
|
||||
- [ ] 单元测试
|
||||
- [ ] 性能优化
|
||||
- [ ] Redis缓存集成
|
||||
|
||||
## 许可证
|
||||
|
||||
MIT License
|
||||
157
pom.xml
Normal file
157
pom.xml
Normal file
@@ -0,0 +1,157 @@
|
||||
<?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>
|
||||
|
||||
<parent>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-parent</artifactId>
|
||||
<version>2.7.18</version>
|
||||
<relativePath/>
|
||||
</parent>
|
||||
|
||||
<groupId>com.nlshop</groupId>
|
||||
<artifactId>nl-shop</artifactId>
|
||||
<version>1.0.0</version>
|
||||
<name>nl-shop</name>
|
||||
<description>奶茶店管理系统</description>
|
||||
|
||||
<properties>
|
||||
<java.version>1.8</java.version>
|
||||
<maven.compiler.source>1.8</maven.compiler.source>
|
||||
<maven.compiler.target>1.8</maven.compiler.target>
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
<mybatis-plus.version>3.5.3.1</mybatis-plus.version>
|
||||
<pagehelper.version>1.4.6</pagehelper.version>
|
||||
<jjwt.version>0.11.5</jjwt.version>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
<!-- Spring Boot Web -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-web</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- Thymeleaf -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-thymeleaf</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- MyBatis -->
|
||||
<dependency>
|
||||
<groupId>org.mybatis.spring.boot</groupId>
|
||||
<artifactId>mybatis-spring-boot-starter</artifactId>
|
||||
<version>2.3.1</version>
|
||||
</dependency>
|
||||
|
||||
<!-- MySQL -->
|
||||
<dependency>
|
||||
<groupId>mysql</groupId>
|
||||
<artifactId>mysql-connector-java</artifactId>
|
||||
<version>8.0.33</version>
|
||||
</dependency>
|
||||
|
||||
<!-- PageHelper -->
|
||||
<dependency>
|
||||
<groupId>com.github.pagehelper</groupId>
|
||||
<artifactId>pagehelper-spring-boot-starter</artifactId>
|
||||
<version>${pagehelper.version}</version>
|
||||
</dependency>
|
||||
|
||||
<!-- JWT -->
|
||||
<dependency>
|
||||
<groupId>io.jsonwebtoken</groupId>
|
||||
<artifactId>jjwt-api</artifactId>
|
||||
<version>${jjwt.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.jsonwebtoken</groupId>
|
||||
<artifactId>jjwt-impl</artifactId>
|
||||
<version>${jjwt.version}</version>
|
||||
<scope>runtime</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.jsonwebtoken</groupId>
|
||||
<artifactId>jjwt-jackson</artifactId>
|
||||
<version>${jjwt.version}</version>
|
||||
<scope>runtime</scope>
|
||||
</dependency>
|
||||
|
||||
<!-- Lombok -->
|
||||
<dependency>
|
||||
<groupId>org.projectlombok</groupId>
|
||||
<artifactId>lombok</artifactId>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
|
||||
<!-- Validation -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-validation</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- Commons Lang -->
|
||||
<dependency>
|
||||
<groupId>org.apache.commons</groupId>
|
||||
<artifactId>commons-lang3</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- JSON处理 -->
|
||||
<dependency>
|
||||
<groupId>com.alibaba</groupId>
|
||||
<artifactId>fastjson</artifactId>
|
||||
<version>2.0.43</version>
|
||||
</dependency>
|
||||
|
||||
<!-- 文件上传 -->
|
||||
<dependency>
|
||||
<groupId>commons-fileupload</groupId>
|
||||
<artifactId>commons-fileupload</artifactId>
|
||||
<version>1.5</version>
|
||||
</dependency>
|
||||
|
||||
<!-- Redis -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-data-redis</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- Spring Boot Test -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-test</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-compiler-plugin</artifactId>
|
||||
<version>3.8.1</version>
|
||||
<configuration>
|
||||
<source>1.8</source>
|
||||
<target>1.8</target>
|
||||
<encoding>UTF-8</encoding>
|
||||
</configuration>
|
||||
</plugin>
|
||||
<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>
|
||||
13
src/main/java/com/nlshop/NlShopApplication.java
Normal file
13
src/main/java/com/nlshop/NlShopApplication.java
Normal file
@@ -0,0 +1,13 @@
|
||||
package com.nlshop;
|
||||
|
||||
import org.mybatis.spring.annotation.MapperScan;
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
|
||||
@SpringBootApplication
|
||||
@MapperScan("com.nlshop.mapper")
|
||||
public class NlShopApplication {
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(NlShopApplication.class, args);
|
||||
}
|
||||
}
|
||||
142
src/main/java/com/nlshop/config/StartupUrlPrinter.java
Normal file
142
src/main/java/com/nlshop/config/StartupUrlPrinter.java
Normal file
@@ -0,0 +1,142 @@
|
||||
package com.nlshop.config;
|
||||
|
||||
import org.springframework.boot.context.event.ApplicationReadyEvent;
|
||||
import org.springframework.context.ApplicationListener;
|
||||
import org.springframework.core.annotation.Order;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.web.method.HandlerMethod;
|
||||
import org.springframework.web.servlet.mvc.method.RequestMappingInfo;
|
||||
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@Component
|
||||
@Order(1)
|
||||
public class StartupUrlPrinter implements ApplicationListener<ApplicationReadyEvent> {
|
||||
|
||||
@Override
|
||||
public void onApplicationEvent(ApplicationReadyEvent event) {
|
||||
RequestMappingHandlerMapping mapping = event.getApplicationContext()
|
||||
.getBean(RequestMappingHandlerMapping.class);
|
||||
|
||||
String baseUrl = "http://localhost:14001";
|
||||
int port = event.getApplicationContext().getEnvironment()
|
||||
.getProperty("server.port", Integer.class, 14001);
|
||||
if (port != 14001) {
|
||||
baseUrl = "http://localhost:" + port;
|
||||
}
|
||||
|
||||
List<String> userPages = new ArrayList<>();
|
||||
List<String> userApis = new ArrayList<>();
|
||||
List<String> merchantPages = new ArrayList<>();
|
||||
List<String> merchantApis = new ArrayList<>();
|
||||
|
||||
Map<RequestMappingInfo, HandlerMethod> handlerMethods = mapping.getHandlerMethods();
|
||||
|
||||
for (Map.Entry<RequestMappingInfo, HandlerMethod> entry : handlerMethods.entrySet()) {
|
||||
RequestMappingInfo mappingInfo = entry.getKey();
|
||||
HandlerMethod handlerMethod = entry.getValue();
|
||||
|
||||
// 检查patternsCondition是否为null
|
||||
if (mappingInfo.getPatternsCondition() == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
Set<String> patterns = mappingInfo.getPatternsCondition().getPatterns();
|
||||
if (patterns == null || patterns.isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
for (String pattern : patterns) {
|
||||
String url = baseUrl + pattern;
|
||||
String className = handlerMethod.getBeanType().getSimpleName();
|
||||
|
||||
if (pattern.startsWith("/merchant")) {
|
||||
if (pattern.endsWith(".html")) {
|
||||
merchantPages.add(url);
|
||||
} else {
|
||||
merchantApis.add(url + " [" + getHttpMethods(mappingInfo) + "]");
|
||||
}
|
||||
} else if (!pattern.startsWith("/error") && !pattern.startsWith("/static")
|
||||
&& !pattern.startsWith("/upload") && !pattern.equals("/")) {
|
||||
if (pattern.endsWith(".html")) {
|
||||
userPages.add(url);
|
||||
} else {
|
||||
userApis.add(url + " [" + getHttpMethods(mappingInfo) + "]");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 添加视图控制器映射的页面
|
||||
userPages.add(baseUrl + "/login.html");
|
||||
userPages.add(baseUrl + "/register.html");
|
||||
userPages.add(baseUrl + "/index.html");
|
||||
userPages.add(baseUrl + "/cart.html");
|
||||
userPages.add(baseUrl + "/orders.html");
|
||||
userPages.add(baseUrl + "/order-detail.html");
|
||||
userPages.add(baseUrl + "/product-detail.html");
|
||||
userPages.add(baseUrl + "/checkout.html");
|
||||
userPages.add(baseUrl + "/profile.html");
|
||||
|
||||
merchantPages.add(baseUrl + "/merchant/login.html");
|
||||
merchantPages.add(baseUrl + "/merchant/dashboard.html");
|
||||
merchantPages.add(baseUrl + "/merchant/products.html");
|
||||
merchantPages.add(baseUrl + "/merchant/orders.html");
|
||||
|
||||
// 打印所有URL
|
||||
printUrls("用户端页面", userPages, baseUrl);
|
||||
printUrls("用户端API", userApis, baseUrl);
|
||||
printUrls("商家端页面", merchantPages, baseUrl);
|
||||
printUrls("商家端API", merchantApis, baseUrl);
|
||||
}
|
||||
|
||||
private String getHttpMethods(RequestMappingInfo mappingInfo) {
|
||||
if (mappingInfo.getMethodsCondition() == null) {
|
||||
return "GET";
|
||||
}
|
||||
|
||||
Set<String> methods = mappingInfo.getMethodsCondition().getMethods().stream()
|
||||
.map(m -> m.name())
|
||||
.collect(Collectors.toSet());
|
||||
if (methods.isEmpty()) {
|
||||
return "GET";
|
||||
}
|
||||
StringBuilder sb = new StringBuilder();
|
||||
for (String method : methods) {
|
||||
if (sb.length() > 0) {
|
||||
sb.append(",");
|
||||
}
|
||||
sb.append(method);
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
private void printUrls(String category, List<String> urls, String baseUrl) {
|
||||
if (urls.isEmpty()) return;
|
||||
|
||||
String separator = createSeparator(80);
|
||||
System.out.println("\n" + separator);
|
||||
System.out.println("📋 " + category + " (" + urls.size() + " 个)");
|
||||
System.out.println(separator);
|
||||
|
||||
for (int i = 0; i < urls.size(); i++) {
|
||||
String url = urls.get(i);
|
||||
System.out.println(String.format("%3d. %s", i + 1, url));
|
||||
}
|
||||
|
||||
System.out.println(separator);
|
||||
}
|
||||
|
||||
private String createSeparator(int length) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
for (int i = 0; i < length; i++) {
|
||||
sb.append("=");
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
}
|
||||
71
src/main/java/com/nlshop/config/WebMvcConfig.java
Normal file
71
src/main/java/com/nlshop/config/WebMvcConfig.java
Normal file
@@ -0,0 +1,71 @@
|
||||
package com.nlshop.config;
|
||||
|
||||
import com.nlshop.interceptor.AuthInterceptor;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
|
||||
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
|
||||
import org.springframework.web.servlet.config.annotation.ViewControllerRegistry;
|
||||
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
|
||||
|
||||
@Configuration
|
||||
public class WebMvcConfig implements WebMvcConfigurer {
|
||||
|
||||
@Autowired
|
||||
private AuthInterceptor authInterceptor;
|
||||
|
||||
@Override
|
||||
public void addViewControllers(ViewControllerRegistry registry) {
|
||||
// 用户端页面(去掉/user前缀)
|
||||
registry.addViewController("/login.html").setViewName("user/login");
|
||||
registry.addViewController("/register.html").setViewName("user/register");
|
||||
registry.addViewController("/index.html").setViewName("user/index");
|
||||
registry.addViewController("/cart.html").setViewName("user/cart");
|
||||
registry.addViewController("/orders.html").setViewName("user/orders");
|
||||
registry.addViewController("/order-detail.html").setViewName("user/order-detail");
|
||||
registry.addViewController("/product-detail.html").setViewName("user/product-detail");
|
||||
registry.addViewController("/checkout.html").setViewName("user/checkout");
|
||||
registry.addViewController("/payment.html").setViewName("user/payment");
|
||||
registry.addViewController("/payment-success.html").setViewName("user/payment-success");
|
||||
registry.addViewController("/profile.html").setViewName("user/profile");
|
||||
|
||||
// 商家端页面(保持/merchant前缀)
|
||||
registry.addViewController("/merchant/login.html").setViewName("merchant/login");
|
||||
registry.addViewController("/merchant/register.html").setViewName("merchant/register");
|
||||
registry.addViewController("/merchant/dashboard.html").setViewName("merchant/dashboard");
|
||||
registry.addViewController("/merchant/products.html").setViewName("merchant/products");
|
||||
registry.addViewController("/merchant/product-edit.html").setViewName("merchant/product-edit");
|
||||
registry.addViewController("/merchant/orders.html").setViewName("merchant/orders");
|
||||
registry.addViewController("/merchant/inventory.html").setViewName("merchant/inventory");
|
||||
registry.addViewController("/merchant/statistics.html").setViewName("merchant/statistics");
|
||||
registry.addViewController("/merchant/announcements.html").setViewName("merchant/announcements");
|
||||
registry.addViewController("/merchant/messages.html").setViewName("merchant/messages");
|
||||
registry.addViewController("/merchant/reviews.html").setViewName("merchant/reviews");
|
||||
registry.addViewController("/merchant/users.html").setViewName("merchant/users");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addInterceptors(InterceptorRegistry registry) {
|
||||
registry.addInterceptor(authInterceptor)
|
||||
.addPathPatterns("/**")
|
||||
.excludePathPatterns(
|
||||
// 登录/注册API接口
|
||||
"/login", "/register",
|
||||
"/merchant/login", "/merchant/register",
|
||||
// 登录/注册HTML页面
|
||||
"/login.html", "/register.html",
|
||||
"/merchant/login.html", "/merchant/register.html",
|
||||
// 静态资源
|
||||
"/static/**", "/upload/**",
|
||||
"/error", "/favicon.ico"
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addResourceHandlers(ResourceHandlerRegistry registry) {
|
||||
registry.addResourceHandler("/upload/**")
|
||||
.addResourceLocations("file:src/main/resources/static/upload/");
|
||||
registry.addResourceHandler("/static/**")
|
||||
.addResourceLocations("classpath:/static/");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
package com.nlshop.controller.merchant;
|
||||
|
||||
import com.nlshop.dto.response.ApiResponse;
|
||||
import com.nlshop.entity.Announcement;
|
||||
import com.nlshop.service.merchant.AnnouncementService;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import java.util.List;
|
||||
|
||||
@RestController("merchantAnnouncementController")
|
||||
@RequestMapping("/merchant/announcements")
|
||||
public class AnnouncementController {
|
||||
|
||||
@Autowired
|
||||
private AnnouncementService announcementService;
|
||||
|
||||
@PostMapping
|
||||
public ApiResponse<Announcement> createAnnouncement(@RequestBody Announcement announcement,
|
||||
HttpServletRequest request) {
|
||||
Long merchantId = (Long) request.getAttribute("userId");
|
||||
if (merchantId == null) {
|
||||
return ApiResponse.error("请先登录");
|
||||
}
|
||||
Announcement created = announcementService.createAnnouncement(merchantId, announcement);
|
||||
return ApiResponse.success(created);
|
||||
}
|
||||
|
||||
@GetMapping
|
||||
public ApiResponse<List<Announcement>> getAnnouncements(HttpServletRequest request) {
|
||||
Long merchantId = (Long) request.getAttribute("userId");
|
||||
if (merchantId == null) {
|
||||
return ApiResponse.error("请先登录");
|
||||
}
|
||||
List<Announcement> announcements = announcementService.getAnnouncements(merchantId);
|
||||
return ApiResponse.success(announcements);
|
||||
}
|
||||
|
||||
@GetMapping("/{id}")
|
||||
public ApiResponse<Announcement> getAnnouncement(@PathVariable Long id) {
|
||||
Announcement announcement = announcementService.getAnnouncement(id);
|
||||
return ApiResponse.success(announcement);
|
||||
}
|
||||
|
||||
@PutMapping("/{id}")
|
||||
public ApiResponse<Void> updateAnnouncement(@PathVariable Long id,
|
||||
@RequestBody Announcement announcement) {
|
||||
announcement.setId(id);
|
||||
announcementService.updateAnnouncement(announcement);
|
||||
return ApiResponse.success(null);
|
||||
}
|
||||
|
||||
@DeleteMapping("/{id}")
|
||||
public ApiResponse<Void> deleteAnnouncement(@PathVariable Long id) {
|
||||
announcementService.deleteAnnouncement(id);
|
||||
return ApiResponse.success(null);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
package com.nlshop.controller.merchant;
|
||||
|
||||
import com.nlshop.dto.response.ApiResponse;
|
||||
import com.nlshop.entity.Inventory;
|
||||
import com.nlshop.service.merchant.InventoryService;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import java.math.BigDecimal;
|
||||
import java.util.List;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/merchant/inventory")
|
||||
public class InventoryController {
|
||||
|
||||
@Autowired
|
||||
private InventoryService inventoryService;
|
||||
|
||||
@GetMapping
|
||||
public ApiResponse<List<Inventory>> getInventory(HttpServletRequest request) {
|
||||
Long merchantId = (Long) request.getAttribute("userId");
|
||||
if (merchantId == null) {
|
||||
return ApiResponse.error("请先登录");
|
||||
}
|
||||
List<Inventory> inventory = inventoryService.getInventory(merchantId);
|
||||
return ApiResponse.success(inventory);
|
||||
}
|
||||
|
||||
@PostMapping
|
||||
public ApiResponse<Inventory> addInventory(@RequestBody Inventory inventory,
|
||||
HttpServletRequest request) {
|
||||
Long merchantId = (Long) request.getAttribute("userId");
|
||||
if (merchantId == null) {
|
||||
return ApiResponse.error("请先登录");
|
||||
}
|
||||
Inventory added = inventoryService.addInventory(merchantId, inventory);
|
||||
return ApiResponse.success(added);
|
||||
}
|
||||
|
||||
@PostMapping("/{id}/in")
|
||||
public ApiResponse<Void> stockIn(@PathVariable Long id,
|
||||
@RequestParam BigDecimal quantity,
|
||||
@RequestParam String operator) {
|
||||
inventoryService.stockIn(id, quantity, operator);
|
||||
return ApiResponse.success(null);
|
||||
}
|
||||
|
||||
@PostMapping("/{id}/out")
|
||||
public ApiResponse<Void> stockOut(@PathVariable Long id,
|
||||
@RequestParam BigDecimal quantity,
|
||||
@RequestParam String operator) {
|
||||
inventoryService.stockOut(id, quantity, operator);
|
||||
return ApiResponse.success(null);
|
||||
}
|
||||
|
||||
@GetMapping("/alerts")
|
||||
public ApiResponse<List<Inventory>> getLowStockAlerts(HttpServletRequest request) {
|
||||
Long merchantId = (Long) request.getAttribute("userId");
|
||||
if (merchantId == null) {
|
||||
return ApiResponse.error("请先登录");
|
||||
}
|
||||
List<Inventory> alerts = inventoryService.getLowStockAlerts(merchantId);
|
||||
return ApiResponse.success(alerts);
|
||||
}
|
||||
|
||||
@PutMapping("/{id}")
|
||||
public ApiResponse<Void> updateInventory(@PathVariable Long id,
|
||||
@RequestBody Inventory inventory) {
|
||||
inventory.setId(id);
|
||||
inventoryService.updateInventory(inventory);
|
||||
return ApiResponse.success(null);
|
||||
}
|
||||
|
||||
@DeleteMapping("/{id}")
|
||||
public ApiResponse<Void> deleteInventory(@PathVariable Long id) {
|
||||
inventoryService.deleteInventory(id);
|
||||
return ApiResponse.success(null);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
package com.nlshop.controller.merchant;
|
||||
|
||||
import com.nlshop.dto.request.LoginRequest;
|
||||
import com.nlshop.dto.request.MerchantRegisterRequest;
|
||||
import com.nlshop.dto.response.ApiResponse;
|
||||
import com.nlshop.dto.response.LoginResponse;
|
||||
import com.nlshop.entity.Merchant;
|
||||
import com.nlshop.service.merchant.MerchantService;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpSession;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/merchant")
|
||||
public class MerchantController {
|
||||
|
||||
@Autowired
|
||||
private MerchantService merchantService;
|
||||
|
||||
@PostMapping("/register")
|
||||
public ApiResponse<Merchant> register(@Validated @RequestBody MerchantRegisterRequest request) {
|
||||
Merchant merchant = merchantService.register(request);
|
||||
return ApiResponse.success(merchant);
|
||||
}
|
||||
|
||||
@PostMapping("/login")
|
||||
public ApiResponse<LoginResponse> login(@Validated @RequestBody LoginRequest request,
|
||||
HttpServletRequest httpRequest) {
|
||||
Merchant merchant = merchantService.login(request.getUsername(), request.getPassword());
|
||||
|
||||
// 将用户信息存入Session
|
||||
HttpSession session = httpRequest.getSession(true);
|
||||
session.setAttribute("userId", merchant.getId());
|
||||
session.setAttribute("userType", "merchant");
|
||||
session.setAttribute("username", merchant.getUsername());
|
||||
|
||||
LoginResponse response = new LoginResponse();
|
||||
response.setToken(""); // 不再使用token,设为空字符串
|
||||
response.setUserId(merchant.getId());
|
||||
response.setUserType("merchant");
|
||||
response.setUsername(merchant.getUsername());
|
||||
response.setName(merchant.getStoreName());
|
||||
|
||||
return ApiResponse.success(response);
|
||||
}
|
||||
|
||||
@PostMapping("/logout")
|
||||
public ApiResponse<Void> logout(HttpServletRequest request) {
|
||||
HttpSession session = request.getSession(false);
|
||||
if (session != null) {
|
||||
session.invalidate();
|
||||
}
|
||||
return ApiResponse.success(null);
|
||||
}
|
||||
|
||||
@GetMapping("/profile")
|
||||
public ApiResponse<Merchant> getProfile(HttpServletRequest request) {
|
||||
Long merchantId = (Long) request.getAttribute("userId");
|
||||
if (merchantId == null) {
|
||||
return ApiResponse.error("请先登录");
|
||||
}
|
||||
Merchant merchant = merchantService.getProfile(merchantId);
|
||||
return ApiResponse.success(merchant);
|
||||
}
|
||||
|
||||
@PutMapping("/profile")
|
||||
public ApiResponse<Merchant> updateProfile(@RequestBody Merchant merchant,
|
||||
HttpServletRequest request) {
|
||||
Long merchantId = (Long) request.getAttribute("userId");
|
||||
if (merchantId == null) {
|
||||
return ApiResponse.error("请先登录");
|
||||
}
|
||||
Merchant updated = merchantService.updateProfile(merchantId, merchant);
|
||||
return ApiResponse.success(updated);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
package com.nlshop.controller.merchant;
|
||||
|
||||
import com.nlshop.dto.response.ApiResponse;
|
||||
import com.nlshop.entity.Message;
|
||||
import com.nlshop.service.merchant.MessageService;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import java.util.List;
|
||||
|
||||
@RestController("merchantMessageController")
|
||||
@RequestMapping("/merchant/messages")
|
||||
public class MessageController {
|
||||
|
||||
@Autowired
|
||||
private MessageService messageService;
|
||||
|
||||
@GetMapping
|
||||
public ApiResponse<List<Message>> getMessages(HttpServletRequest request) {
|
||||
Long merchantId = (Long) request.getAttribute("userId");
|
||||
if (merchantId == null) {
|
||||
return ApiResponse.error("请先登录");
|
||||
}
|
||||
List<Message> messages = messageService.getMessages(merchantId);
|
||||
return ApiResponse.success(messages);
|
||||
}
|
||||
|
||||
@GetMapping("/{id}")
|
||||
public ApiResponse<Message> getMessage(@PathVariable Long id) {
|
||||
Message message = messageService.getMessage(id);
|
||||
return ApiResponse.success(message);
|
||||
}
|
||||
|
||||
@PostMapping("/{id}/reply")
|
||||
public ApiResponse<Void> replyMessage(@PathVariable Long id,
|
||||
@RequestParam String reply) {
|
||||
messageService.replyMessage(id, reply);
|
||||
return ApiResponse.success(null);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
package com.nlshop.controller.merchant;
|
||||
|
||||
import com.nlshop.dto.response.ApiResponse;
|
||||
import com.nlshop.dto.response.OrderDetailResponse;
|
||||
import com.nlshop.dto.response.PageResponse;
|
||||
import com.nlshop.entity.Order;
|
||||
import com.nlshop.service.merchant.OrderManageService;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/merchant/orders")
|
||||
public class OrderManageController {
|
||||
|
||||
@Autowired
|
||||
private OrderManageService orderManageService;
|
||||
|
||||
@GetMapping
|
||||
public ApiResponse<PageResponse<Order>> getOrders(
|
||||
@RequestParam(required = false) String status,
|
||||
@RequestParam(defaultValue = "1") Integer pageNum,
|
||||
@RequestParam(defaultValue = "10") Integer pageSize,
|
||||
HttpServletRequest request) {
|
||||
Long merchantId = (Long) request.getAttribute("userId");
|
||||
if (merchantId == null) {
|
||||
return ApiResponse.error("请先登录");
|
||||
}
|
||||
PageResponse<Order> response = orderManageService.getOrders(merchantId, status, pageNum, pageSize);
|
||||
return ApiResponse.success(response);
|
||||
}
|
||||
|
||||
@GetMapping("/{id}")
|
||||
public ApiResponse<OrderDetailResponse> getOrderDetail(@PathVariable Long id,
|
||||
HttpServletRequest request) {
|
||||
Long merchantId = (Long) request.getAttribute("userId");
|
||||
if (merchantId == null) {
|
||||
return ApiResponse.error("请先登录");
|
||||
}
|
||||
OrderDetailResponse orderDetail = orderManageService.getOrderDetail(id, merchantId);
|
||||
return ApiResponse.success(orderDetail);
|
||||
}
|
||||
|
||||
@PutMapping("/{id}/accept")
|
||||
public ApiResponse<Void> acceptOrder(@PathVariable Long id,
|
||||
HttpServletRequest request) {
|
||||
Long merchantId = (Long) request.getAttribute("userId");
|
||||
if (merchantId == null) {
|
||||
return ApiResponse.error("请先登录");
|
||||
}
|
||||
orderManageService.acceptOrder(id, merchantId);
|
||||
return ApiResponse.success(null);
|
||||
}
|
||||
|
||||
@PutMapping("/{id}/reject")
|
||||
public ApiResponse<Void> rejectOrder(@PathVariable Long id,
|
||||
HttpServletRequest request) {
|
||||
Long merchantId = (Long) request.getAttribute("userId");
|
||||
if (merchantId == null) {
|
||||
return ApiResponse.error("请先登录");
|
||||
}
|
||||
orderManageService.rejectOrder(id, merchantId);
|
||||
return ApiResponse.success(null);
|
||||
}
|
||||
|
||||
@PutMapping("/{id}/status")
|
||||
public ApiResponse<Void> updateOrderStatus(@PathVariable Long id,
|
||||
@RequestParam String status,
|
||||
HttpServletRequest request) {
|
||||
Long merchantId = (Long) request.getAttribute("userId");
|
||||
if (merchantId == null) {
|
||||
return ApiResponse.error("请先登录");
|
||||
}
|
||||
orderManageService.updateOrderStatus(id, merchantId, status);
|
||||
return ApiResponse.success(null);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,222 @@
|
||||
package com.nlshop.controller.merchant;
|
||||
|
||||
import com.nlshop.dto.response.ApiResponse;
|
||||
import com.nlshop.dto.response.PageResponse;
|
||||
import com.nlshop.entity.Product;
|
||||
import com.nlshop.entity.ProductCustom;
|
||||
import com.nlshop.entity.ProductSpec;
|
||||
import com.nlshop.entity.ProductTopping;
|
||||
import com.nlshop.service.merchant.ProductManageService;
|
||||
import com.nlshop.util.FileUploadUtil;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/merchant/products")
|
||||
public class ProductManageController {
|
||||
|
||||
@Autowired
|
||||
private ProductManageService productManageService;
|
||||
|
||||
@Autowired
|
||||
private FileUploadUtil fileUploadUtil;
|
||||
|
||||
@GetMapping
|
||||
public ApiResponse<PageResponse<Product>> getProducts(
|
||||
@RequestParam(defaultValue = "1") Integer pageNum,
|
||||
@RequestParam(defaultValue = "10") Integer pageSize,
|
||||
HttpServletRequest request) {
|
||||
Long merchantId = (Long) request.getAttribute("userId");
|
||||
if (merchantId == null) {
|
||||
return ApiResponse.error("请先登录");
|
||||
}
|
||||
PageResponse<Product> response = productManageService.getProducts(merchantId, pageNum, pageSize);
|
||||
return ApiResponse.success(response);
|
||||
}
|
||||
|
||||
@GetMapping("/{id}")
|
||||
public ApiResponse<Map<String, Object>> getProduct(@PathVariable Long id,
|
||||
HttpServletRequest request) {
|
||||
Long merchantId = (Long) request.getAttribute("userId");
|
||||
if (merchantId == null) {
|
||||
return ApiResponse.error("请先登录");
|
||||
}
|
||||
Map<String, Object> product = productManageService.getProduct(id, merchantId);
|
||||
return ApiResponse.success(product);
|
||||
}
|
||||
|
||||
@PostMapping
|
||||
public ApiResponse<Product> createProduct(@RequestParam String name,
|
||||
@RequestParam(required = false) String category,
|
||||
@RequestParam(required = false) String description,
|
||||
@RequestParam String basePrice,
|
||||
@RequestParam(required = false) MultipartFile image,
|
||||
HttpServletRequest httpRequest) {
|
||||
Long merchantId = (Long) httpRequest.getAttribute("userId");
|
||||
if (merchantId == null) {
|
||||
return ApiResponse.error("请先登录");
|
||||
}
|
||||
|
||||
Product product = new Product();
|
||||
product.setName(name);
|
||||
product.setCategory(category);
|
||||
product.setDescription(description);
|
||||
if (basePrice != null && !basePrice.isEmpty()) {
|
||||
product.setBasePrice(new java.math.BigDecimal(basePrice));
|
||||
}
|
||||
|
||||
if (image != null && !image.isEmpty()) {
|
||||
try {
|
||||
String imagePath = fileUploadUtil.uploadProductImage(image);
|
||||
product.setImage(imagePath);
|
||||
} catch (Exception e) {
|
||||
return ApiResponse.error("图片上传失败: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
Product created = productManageService.createProduct(merchantId, product);
|
||||
return ApiResponse.success(created);
|
||||
}
|
||||
|
||||
@PutMapping("/{id}")
|
||||
public ApiResponse<Product> updateProduct(@PathVariable Long id,
|
||||
@RequestParam String name,
|
||||
@RequestParam(required = false) String category,
|
||||
@RequestParam(required = false) String description,
|
||||
@RequestParam String basePrice,
|
||||
@RequestParam(required = false) MultipartFile image,
|
||||
HttpServletRequest request) {
|
||||
Long merchantId = (Long) request.getAttribute("userId");
|
||||
if (merchantId == null) {
|
||||
return ApiResponse.error("请先登录");
|
||||
}
|
||||
|
||||
Product product = new Product();
|
||||
product.setName(name);
|
||||
product.setCategory(category);
|
||||
product.setDescription(description);
|
||||
if (basePrice != null && !basePrice.isEmpty()) {
|
||||
product.setBasePrice(new java.math.BigDecimal(basePrice));
|
||||
}
|
||||
|
||||
if (image != null && !image.isEmpty()) {
|
||||
try {
|
||||
String imagePath = fileUploadUtil.uploadProductImage(image);
|
||||
product.setImage(imagePath);
|
||||
} catch (Exception e) {
|
||||
return ApiResponse.error("图片上传失败: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
Product updated = productManageService.updateProduct(id, merchantId, product);
|
||||
return ApiResponse.success(updated);
|
||||
}
|
||||
|
||||
@PutMapping("/{id}/offline")
|
||||
public ApiResponse<Void> offlineProduct(@PathVariable Long id,
|
||||
HttpServletRequest request) {
|
||||
Long merchantId = (Long) request.getAttribute("userId");
|
||||
if (merchantId == null) {
|
||||
return ApiResponse.error("请先登录");
|
||||
}
|
||||
productManageService.offlineProduct(id, merchantId);
|
||||
return ApiResponse.success(null);
|
||||
}
|
||||
|
||||
@PutMapping("/{id}/online")
|
||||
public ApiResponse<Void> onlineProduct(@PathVariable Long id,
|
||||
HttpServletRequest request) {
|
||||
Long merchantId = (Long) request.getAttribute("userId");
|
||||
if (merchantId == null) {
|
||||
return ApiResponse.error("请先登录");
|
||||
}
|
||||
productManageService.onlineProduct(id, merchantId);
|
||||
return ApiResponse.success(null);
|
||||
}
|
||||
|
||||
@GetMapping("/{id}/specs")
|
||||
public ApiResponse<List<ProductSpec>> getSpecs(@PathVariable Long id) {
|
||||
List<ProductSpec> specs = productManageService.getSpecs(id);
|
||||
return ApiResponse.success(specs);
|
||||
}
|
||||
|
||||
@PostMapping("/{id}/specs")
|
||||
public ApiResponse<ProductSpec> addSpec(@PathVariable Long id,
|
||||
@RequestBody ProductSpec spec) {
|
||||
ProductSpec added = productManageService.addSpec(id, spec);
|
||||
return ApiResponse.success(added);
|
||||
}
|
||||
|
||||
@PutMapping("/specs/{specId}")
|
||||
public ApiResponse<Void> updateSpec(@PathVariable Long specId,
|
||||
@RequestBody ProductSpec spec) {
|
||||
spec.setId(specId);
|
||||
productManageService.updateSpec(spec);
|
||||
return ApiResponse.success(null);
|
||||
}
|
||||
|
||||
@DeleteMapping("/specs/{specId}")
|
||||
public ApiResponse<Void> deleteSpec(@PathVariable Long specId) {
|
||||
productManageService.deleteSpec(specId);
|
||||
return ApiResponse.success(null);
|
||||
}
|
||||
|
||||
@GetMapping("/{id}/toppings")
|
||||
public ApiResponse<List<ProductTopping>> getToppings(@PathVariable Long id) {
|
||||
List<ProductTopping> toppings = productManageService.getToppings(id);
|
||||
return ApiResponse.success(toppings);
|
||||
}
|
||||
|
||||
@PostMapping("/{id}/toppings")
|
||||
public ApiResponse<ProductTopping> addTopping(@PathVariable Long id,
|
||||
@RequestBody ProductTopping topping) {
|
||||
ProductTopping added = productManageService.addTopping(id, topping);
|
||||
return ApiResponse.success(added);
|
||||
}
|
||||
|
||||
@PutMapping("/toppings/{toppingId}")
|
||||
public ApiResponse<Void> updateTopping(@PathVariable Long toppingId,
|
||||
@RequestBody ProductTopping topping) {
|
||||
topping.setId(toppingId);
|
||||
productManageService.updateTopping(topping);
|
||||
return ApiResponse.success(null);
|
||||
}
|
||||
|
||||
@DeleteMapping("/toppings/{toppingId}")
|
||||
public ApiResponse<Void> deleteTopping(@PathVariable Long toppingId) {
|
||||
productManageService.deleteTopping(toppingId);
|
||||
return ApiResponse.success(null);
|
||||
}
|
||||
|
||||
@GetMapping("/{id}/customs")
|
||||
public ApiResponse<List<ProductCustom>> getCustoms(@PathVariable Long id) {
|
||||
List<ProductCustom> customs = productManageService.getCustoms(id);
|
||||
return ApiResponse.success(customs);
|
||||
}
|
||||
|
||||
@PostMapping("/{id}/customs")
|
||||
public ApiResponse<ProductCustom> addCustom(@PathVariable Long id,
|
||||
@RequestBody ProductCustom custom) {
|
||||
ProductCustom added = productManageService.addCustom(id, custom);
|
||||
return ApiResponse.success(added);
|
||||
}
|
||||
|
||||
@PutMapping("/customs/{customId}")
|
||||
public ApiResponse<Void> updateCustom(@PathVariable Long customId,
|
||||
@RequestBody ProductCustom custom) {
|
||||
custom.setId(customId);
|
||||
productManageService.updateCustom(custom);
|
||||
return ApiResponse.success(null);
|
||||
}
|
||||
|
||||
@DeleteMapping("/customs/{customId}")
|
||||
public ApiResponse<Void> deleteCustom(@PathVariable Long customId) {
|
||||
productManageService.deleteCustom(customId);
|
||||
return ApiResponse.success(null);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
package com.nlshop.controller.merchant;
|
||||
|
||||
import com.nlshop.dto.response.ApiResponse;
|
||||
import com.nlshop.entity.Review;
|
||||
import com.nlshop.exception.BusinessException;
|
||||
import com.nlshop.service.merchant.ReviewService;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import java.util.List;
|
||||
|
||||
@RestController("merchantReviewController")
|
||||
@RequestMapping("/merchant/reviews")
|
||||
public class ReviewController {
|
||||
|
||||
@Autowired
|
||||
private ReviewService reviewService;
|
||||
|
||||
@GetMapping
|
||||
public ApiResponse<List<Review>> getReviews(HttpServletRequest request) {
|
||||
Long merchantId = (Long) request.getAttribute("userId");
|
||||
if (merchantId == null) {
|
||||
return ApiResponse.error("请先登录");
|
||||
}
|
||||
List<Review> reviews = reviewService.getReviews(merchantId);
|
||||
return ApiResponse.success(reviews);
|
||||
}
|
||||
|
||||
@GetMapping("/{id}")
|
||||
public ApiResponse<Review> getReview(@PathVariable Long id) {
|
||||
Review review = reviewService.getReview(id);
|
||||
return ApiResponse.success(review);
|
||||
}
|
||||
|
||||
@PostMapping("/{id}/reply")
|
||||
public ApiResponse<Void> replyReview(@PathVariable Long id,
|
||||
@RequestParam String reply,
|
||||
HttpServletRequest request) {
|
||||
Long merchantId = (Long) request.getAttribute("userId");
|
||||
if (merchantId == null) {
|
||||
return ApiResponse.error("请先登录");
|
||||
}
|
||||
try {
|
||||
reviewService.replyReview(merchantId, id, reply);
|
||||
return ApiResponse.success(null);
|
||||
} catch (BusinessException e) {
|
||||
return ApiResponse.error(e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@PutMapping("/{id}/handle")
|
||||
public ApiResponse<Void> handleComplaint(@PathVariable Long id,
|
||||
@RequestParam String reply,
|
||||
HttpServletRequest request) {
|
||||
Long merchantId = (Long) request.getAttribute("userId");
|
||||
if (merchantId == null) {
|
||||
return ApiResponse.error("请先登录");
|
||||
}
|
||||
try {
|
||||
reviewService.handleComplaint(merchantId, id, reply);
|
||||
return ApiResponse.success(null);
|
||||
} catch (BusinessException e) {
|
||||
return ApiResponse.error(e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
package com.nlshop.controller.merchant;
|
||||
|
||||
import com.nlshop.dto.response.ApiResponse;
|
||||
import com.nlshop.service.merchant.StatisticsService;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/merchant/statistics")
|
||||
public class StatisticsController {
|
||||
|
||||
@Autowired
|
||||
private StatisticsService statisticsService;
|
||||
|
||||
@GetMapping("/sales")
|
||||
public ApiResponse<Map<String, Object>> getSalesStatistics(
|
||||
@RequestParam(defaultValue = "day") String period,
|
||||
HttpServletRequest request) {
|
||||
Long merchantId = (Long) request.getAttribute("userId");
|
||||
if (merchantId == null) {
|
||||
return ApiResponse.error("请先登录");
|
||||
}
|
||||
Map<String, Object> stats = statisticsService.getSalesStatistics(merchantId, period);
|
||||
return ApiResponse.success(stats);
|
||||
}
|
||||
|
||||
@GetMapping("/revenue")
|
||||
public ApiResponse<Map<String, Object>> getRevenueStatistics(
|
||||
@RequestParam(defaultValue = "day") String period,
|
||||
HttpServletRequest request) {
|
||||
Long merchantId = (Long) request.getAttribute("userId");
|
||||
if (merchantId == null) {
|
||||
return ApiResponse.error("请先登录");
|
||||
}
|
||||
Map<String, Object> stats = statisticsService.getRevenueStatistics(merchantId, period);
|
||||
return ApiResponse.success(stats);
|
||||
}
|
||||
|
||||
@GetMapping("/hot-products")
|
||||
public ApiResponse<List<Map<String, Object>>> getHotProducts(
|
||||
@RequestParam(defaultValue = "10") int limit,
|
||||
HttpServletRequest request) {
|
||||
Long merchantId = (Long) request.getAttribute("userId");
|
||||
if (merchantId == null) {
|
||||
return ApiResponse.error("请先登录");
|
||||
}
|
||||
List<Map<String, Object>> products = statisticsService.getHotProducts(merchantId, limit);
|
||||
return ApiResponse.success(products);
|
||||
}
|
||||
|
||||
@GetMapping("/active-users")
|
||||
public ApiResponse<List<Map<String, Object>>> getActiveUsers(
|
||||
@RequestParam(defaultValue = "30") int days,
|
||||
HttpServletRequest request) {
|
||||
Long merchantId = (Long) request.getAttribute("userId");
|
||||
if (merchantId == null) {
|
||||
return ApiResponse.error("请先登录");
|
||||
}
|
||||
List<Map<String, Object>> users = statisticsService.getActiveUsers(merchantId, days);
|
||||
return ApiResponse.success(users);
|
||||
}
|
||||
|
||||
@GetMapping("/sticky-users")
|
||||
public ApiResponse<List<Map<String, Object>>> getStickyUsers(
|
||||
@RequestParam(defaultValue = "30") int days,
|
||||
@RequestParam(defaultValue = "5") int minOrders,
|
||||
HttpServletRequest request) {
|
||||
Long merchantId = (Long) request.getAttribute("userId");
|
||||
if (merchantId == null) {
|
||||
return ApiResponse.error("请先登录");
|
||||
}
|
||||
List<Map<String, Object>> users = statisticsService.getStickyUsers(merchantId, days, minOrders);
|
||||
return ApiResponse.success(users);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
package com.nlshop.controller.merchant;
|
||||
|
||||
import com.nlshop.dto.response.ApiResponse;
|
||||
import com.nlshop.dto.response.PageResponse;
|
||||
import com.nlshop.entity.User;
|
||||
import com.nlshop.service.merchant.UserManageService;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/merchant/users")
|
||||
public class UserManageController {
|
||||
|
||||
@Autowired
|
||||
private UserManageService userManageService;
|
||||
|
||||
@GetMapping
|
||||
public ApiResponse<PageResponse<User>> getUsers(
|
||||
@RequestParam(defaultValue = "1") Integer pageNum,
|
||||
@RequestParam(defaultValue = "10") Integer pageSize,
|
||||
HttpServletRequest request) {
|
||||
Long merchantId = (Long) request.getAttribute("userId");
|
||||
if (merchantId == null) {
|
||||
return ApiResponse.error("请先登录");
|
||||
}
|
||||
PageResponse<User> response = userManageService.getUsers(pageNum, pageSize);
|
||||
return ApiResponse.success(response);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
package com.nlshop.controller.user;
|
||||
|
||||
import com.nlshop.dto.response.ApiResponse;
|
||||
import com.nlshop.entity.Announcement;
|
||||
import com.nlshop.service.merchant.AnnouncementService;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@RestController("userAnnouncementController")
|
||||
@RequestMapping("/announcements")
|
||||
public class AnnouncementController {
|
||||
|
||||
@Autowired
|
||||
private AnnouncementService announcementService;
|
||||
|
||||
@GetMapping
|
||||
public ApiResponse<List<Announcement>> getAnnouncements() {
|
||||
List<Announcement> announcements = announcementService.getPublishedAnnouncements();
|
||||
return ApiResponse.success(announcements);
|
||||
}
|
||||
}
|
||||
116
src/main/java/com/nlshop/controller/user/CartController.java
Normal file
116
src/main/java/com/nlshop/controller/user/CartController.java
Normal file
@@ -0,0 +1,116 @@
|
||||
package com.nlshop.controller.user;
|
||||
|
||||
import com.nlshop.dto.request.CartAddRequest;
|
||||
import com.nlshop.dto.response.ApiResponse;
|
||||
import com.nlshop.entity.CartItem;
|
||||
import com.nlshop.service.user.CartService;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import java.util.List;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/cart")
|
||||
public class CartController {
|
||||
|
||||
@Autowired
|
||||
private CartService cartService;
|
||||
|
||||
@PostMapping("/add")
|
||||
public ApiResponse<CartItem> addToCart(@Validated @RequestBody CartAddRequest request,
|
||||
HttpServletRequest httpRequest) {
|
||||
Long userId = (Long) httpRequest.getAttribute("userId");
|
||||
if (userId == null) {
|
||||
return ApiResponse.error("请先登录");
|
||||
}
|
||||
CartItem item = cartService.addToCart(userId, request);
|
||||
return ApiResponse.success(item);
|
||||
}
|
||||
|
||||
@GetMapping
|
||||
public ApiResponse<List<CartItem>> getCart(HttpServletRequest request) {
|
||||
Long userId = (Long) request.getAttribute("userId");
|
||||
if (userId == null) {
|
||||
return ApiResponse.error("请先登录");
|
||||
}
|
||||
List<CartItem> items = cartService.getCart(userId);
|
||||
return ApiResponse.success(items);
|
||||
}
|
||||
|
||||
@PutMapping("/{id}")
|
||||
public ApiResponse<Void> updateQuantity(@PathVariable Long id,
|
||||
@RequestParam Integer quantity,
|
||||
HttpServletRequest request) {
|
||||
Long userId = (Long) request.getAttribute("userId");
|
||||
if (userId == null) {
|
||||
return ApiResponse.error("请先登录");
|
||||
}
|
||||
try {
|
||||
cartService.updateQuantity(userId, id, quantity);
|
||||
return ApiResponse.success(null);
|
||||
} catch (RuntimeException e) {
|
||||
return ApiResponse.error(e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@DeleteMapping("/{id}")
|
||||
public ApiResponse<Void> deleteCartItem(@PathVariable Long id,
|
||||
HttpServletRequest request) {
|
||||
Long userId = (Long) request.getAttribute("userId");
|
||||
if (userId == null) {
|
||||
return ApiResponse.error("请先登录");
|
||||
}
|
||||
try {
|
||||
cartService.deleteCartItem(userId, id);
|
||||
return ApiResponse.success(null);
|
||||
} catch (RuntimeException e) {
|
||||
return ApiResponse.error(e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@DeleteMapping("/batch")
|
||||
public ApiResponse<Void> deleteBatch(@RequestBody List<Long> ids,
|
||||
HttpServletRequest request) {
|
||||
Long userId = (Long) request.getAttribute("userId");
|
||||
if (userId == null) {
|
||||
return ApiResponse.error("请先登录");
|
||||
}
|
||||
if (ids == null || ids.isEmpty()) {
|
||||
return ApiResponse.error("请选择要删除的商品");
|
||||
}
|
||||
try {
|
||||
cartService.deleteBatch(userId, ids);
|
||||
return ApiResponse.success(null);
|
||||
} catch (RuntimeException e) {
|
||||
return ApiResponse.error(e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 同步cookies中的购物车数据(用于登录后同步)
|
||||
*/
|
||||
@PostMapping("/sync")
|
||||
public ApiResponse<Void> syncCart(@RequestBody List<CartAddRequest> items,
|
||||
HttpServletRequest request) {
|
||||
Long userId = (Long) request.getAttribute("userId");
|
||||
if (userId == null) {
|
||||
return ApiResponse.error("请先登录");
|
||||
}
|
||||
|
||||
if (items == null || items.isEmpty()) {
|
||||
return ApiResponse.success(null);
|
||||
}
|
||||
|
||||
try {
|
||||
cartService.syncCartFromCookies(userId, items);
|
||||
return ApiResponse.success(null);
|
||||
} catch (Exception e) {
|
||||
// 同步失败不应该影响登录流程,只记录日志
|
||||
System.err.println("同步购物车失败: userId=" + userId + ", error=" + e.getMessage());
|
||||
e.printStackTrace();
|
||||
return ApiResponse.success(null); // 返回成功,避免影响登录流程
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
package com.nlshop.controller.user;
|
||||
|
||||
import com.nlshop.dto.response.ApiResponse;
|
||||
import com.nlshop.entity.Message;
|
||||
import com.nlshop.service.user.MessageService;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import java.util.List;
|
||||
|
||||
@RestController("userMessageController")
|
||||
@RequestMapping("/message")
|
||||
public class MessageController {
|
||||
|
||||
@Autowired
|
||||
private MessageService messageService;
|
||||
|
||||
@PostMapping
|
||||
public ApiResponse<Message> createMessage(@RequestParam Long merchantId,
|
||||
@RequestParam(required = false) Long orderId,
|
||||
@RequestParam String content,
|
||||
HttpServletRequest request) {
|
||||
Long userId = (Long) request.getAttribute("userId");
|
||||
if (userId == null) {
|
||||
return ApiResponse.error("请先登录");
|
||||
}
|
||||
Message message = messageService.createMessage(userId, merchantId, orderId, content);
|
||||
return ApiResponse.success(message);
|
||||
}
|
||||
|
||||
@GetMapping
|
||||
public ApiResponse<List<Message>> getMessages(HttpServletRequest request) {
|
||||
Long userId = (Long) request.getAttribute("userId");
|
||||
if (userId == null) {
|
||||
return ApiResponse.error("请先登录");
|
||||
}
|
||||
List<Message> messages = messageService.getMessages(userId);
|
||||
return ApiResponse.success(messages);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
package com.nlshop.controller.user;
|
||||
|
||||
import com.nlshop.dto.request.OrderCreateRequest;
|
||||
import com.nlshop.dto.request.PaymentRequest;
|
||||
import com.nlshop.dto.response.ApiResponse;
|
||||
import com.nlshop.dto.response.OrderDetailResponse;
|
||||
import com.nlshop.entity.Order;
|
||||
import com.nlshop.entity.OrderTrack;
|
||||
import com.nlshop.exception.BusinessException;
|
||||
import com.nlshop.service.user.OrderService;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import java.util.List;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/order")
|
||||
public class OrderController {
|
||||
|
||||
@Autowired
|
||||
private OrderService orderService;
|
||||
|
||||
@PostMapping("/create")
|
||||
public ApiResponse<Order> createOrder(@Validated @RequestBody OrderCreateRequest request,
|
||||
HttpServletRequest httpRequest) {
|
||||
Long userId = (Long) httpRequest.getAttribute("userId");
|
||||
if (userId == null) {
|
||||
return ApiResponse.error("请先登录");
|
||||
}
|
||||
Order order = orderService.createOrder(userId, request);
|
||||
return ApiResponse.success(order);
|
||||
}
|
||||
|
||||
@PostMapping("/{id}/pay")
|
||||
public ApiResponse<Void> payOrder(@PathVariable Long id,
|
||||
@Validated @RequestBody PaymentRequest request,
|
||||
HttpServletRequest httpRequest) {
|
||||
Long userId = (Long) httpRequest.getAttribute("userId");
|
||||
if (userId == null) {
|
||||
return ApiResponse.error("请先登录");
|
||||
}
|
||||
try {
|
||||
orderService.payOrder(userId, id, request);
|
||||
return ApiResponse.success(null);
|
||||
} catch (BusinessException e) {
|
||||
return ApiResponse.error(e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@GetMapping("/orders")
|
||||
public ApiResponse<List<Order>> getOrders(@RequestParam(required = false) String status,
|
||||
HttpServletRequest request) {
|
||||
Long userId = (Long) request.getAttribute("userId");
|
||||
if (userId == null) {
|
||||
return ApiResponse.error("请先登录");
|
||||
}
|
||||
List<Order> orders = orderService.getOrders(userId, status);
|
||||
return ApiResponse.success(orders);
|
||||
}
|
||||
|
||||
@GetMapping("/orders/{id}")
|
||||
public ApiResponse<OrderDetailResponse> getOrderDetail(@PathVariable Long id,
|
||||
HttpServletRequest request) {
|
||||
Long userId = (Long) request.getAttribute("userId");
|
||||
if (userId == null) {
|
||||
return ApiResponse.error("请先登录");
|
||||
}
|
||||
OrderDetailResponse orderDetail = orderService.getOrderDetail(id, userId);
|
||||
return ApiResponse.success(orderDetail);
|
||||
}
|
||||
|
||||
@GetMapping("/orders/{id}/track")
|
||||
public ApiResponse<List<OrderTrack>> getOrderTrack(@PathVariable Long id,
|
||||
HttpServletRequest request) {
|
||||
Long userId = (Long) request.getAttribute("userId");
|
||||
if (userId == null) {
|
||||
return ApiResponse.error("请先登录");
|
||||
}
|
||||
try {
|
||||
List<OrderTrack> tracks = orderService.getOrderTrack(userId, id);
|
||||
return ApiResponse.success(tracks);
|
||||
} catch (BusinessException e) {
|
||||
return ApiResponse.error(e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
package com.nlshop.controller.user;
|
||||
|
||||
import com.nlshop.dto.response.ApiResponse;
|
||||
import com.nlshop.dto.response.PageResponse;
|
||||
import com.nlshop.entity.Product;
|
||||
import com.nlshop.service.algorithm.CollaborativeFilteringService;
|
||||
import com.nlshop.service.user.ProductService;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/products")
|
||||
public class ProductController {
|
||||
|
||||
@Autowired
|
||||
private ProductService productService;
|
||||
|
||||
@Autowired
|
||||
private CollaborativeFilteringService cfService;
|
||||
|
||||
@GetMapping
|
||||
public ApiResponse<PageResponse<Product>> getProducts(
|
||||
@RequestParam(required = false) String category,
|
||||
@RequestParam(defaultValue = "1") Integer pageNum,
|
||||
@RequestParam(defaultValue = "10") Integer pageSize) {
|
||||
PageResponse<Product> response = productService.getProducts(category, pageNum, pageSize);
|
||||
return ApiResponse.success(response);
|
||||
}
|
||||
|
||||
@GetMapping("/{id}")
|
||||
public ApiResponse<Map<String, Object>> getProductDetail(@PathVariable Long id,
|
||||
HttpServletRequest request) {
|
||||
Long userId = null;
|
||||
try {
|
||||
userId = (Long) request.getAttribute("userId");
|
||||
} catch (Exception e) {
|
||||
// 未登录用户也可以查看商品详情
|
||||
}
|
||||
Map<String, Object> detail = productService.getProductDetail(id, userId);
|
||||
return ApiResponse.success(detail);
|
||||
}
|
||||
|
||||
@GetMapping("/hot")
|
||||
public ApiResponse<List<Product>> getHotProducts(@RequestParam(defaultValue = "10") int limit) {
|
||||
List<Product> products = productService.getHotProducts(limit);
|
||||
return ApiResponse.success(products);
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量记录浏览行为(用于登录后同步cookies中的浏览历史)
|
||||
*/
|
||||
@PostMapping("/record-views")
|
||||
public ApiResponse<Void> recordViews(@RequestBody List<Long> productIds,
|
||||
HttpServletRequest request) {
|
||||
Long userId = (Long) request.getAttribute("userId");
|
||||
if (userId == null) {
|
||||
return ApiResponse.error("请先登录");
|
||||
}
|
||||
|
||||
if (productIds == null || productIds.isEmpty()) {
|
||||
return ApiResponse.success(null);
|
||||
}
|
||||
|
||||
// 批量记录浏览行为
|
||||
for (Long productId : productIds) {
|
||||
try {
|
||||
cfService.recordView(userId, productId);
|
||||
} catch (Exception e) {
|
||||
// 单个失败不影响其他,只记录日志
|
||||
System.err.println("记录浏览行为失败: userId=" + userId + ", productId=" + productId + ", error=" + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
return ApiResponse.success(null);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
package com.nlshop.controller.user;
|
||||
|
||||
import com.nlshop.dto.response.ApiResponse;
|
||||
import com.nlshop.entity.Product;
|
||||
import com.nlshop.service.user.RecommendService;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import java.util.List;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/recommend")
|
||||
public class RecommendController {
|
||||
|
||||
@Autowired
|
||||
private RecommendService recommendService;
|
||||
|
||||
@GetMapping
|
||||
public ApiResponse<List<Product>> getRecommendations(
|
||||
@RequestParam(defaultValue = "10") int limit,
|
||||
HttpServletRequest request) {
|
||||
Long userId = (Long) request.getAttribute("userId");
|
||||
// 推荐功能可以允许未登录用户,userId可以为null
|
||||
List<Product> products = recommendService.getRecommendations(userId, limit);
|
||||
return ApiResponse.success(products);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,97 @@
|
||||
package com.nlshop.controller.user;
|
||||
|
||||
import com.nlshop.dto.response.ApiResponse;
|
||||
import com.nlshop.entity.Review;
|
||||
import com.nlshop.mapper.ReviewMapper;
|
||||
import com.nlshop.service.algorithm.CollaborativeFilteringService;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import java.math.BigDecimal;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@RestController("userReviewController")
|
||||
@RequestMapping("/review")
|
||||
public class ReviewController {
|
||||
|
||||
@Autowired
|
||||
private ReviewMapper reviewMapper;
|
||||
|
||||
@Autowired
|
||||
private CollaborativeFilteringService cfService;
|
||||
|
||||
@PostMapping
|
||||
public ApiResponse<Review> createReview(@RequestParam Long orderId,
|
||||
@RequestParam Long productId,
|
||||
@RequestParam Integer rating,
|
||||
@RequestParam(required = false) String content,
|
||||
HttpServletRequest request) {
|
||||
Long userId = (Long) request.getAttribute("userId");
|
||||
if (userId == null) {
|
||||
return ApiResponse.error("请先登录");
|
||||
}
|
||||
|
||||
// 检查是否已经评价过
|
||||
Review existing = reviewMapper.selectByOrderId(orderId);
|
||||
if (existing != null) {
|
||||
return ApiResponse.error("该订单已评价");
|
||||
}
|
||||
|
||||
Review review = new Review();
|
||||
review.setUserId(userId);
|
||||
review.setOrderId(orderId);
|
||||
review.setProductId(productId);
|
||||
review.setRating(rating);
|
||||
review.setContent(content);
|
||||
reviewMapper.insert(review);
|
||||
|
||||
// 记录评分行为到user_behavior表(用于协同过滤算法)
|
||||
cfService.recordRating(userId, productId, new BigDecimal(rating));
|
||||
|
||||
return ApiResponse.success(review);
|
||||
}
|
||||
|
||||
@GetMapping("/by-orders")
|
||||
public ApiResponse<List<Review>> getReviewsByOrders(@RequestParam String orderIds,
|
||||
HttpServletRequest request) {
|
||||
Long userId = (Long) request.getAttribute("userId");
|
||||
if (userId == null) {
|
||||
return ApiResponse.error("请先登录");
|
||||
}
|
||||
|
||||
try {
|
||||
List<Long> orderIdList = Arrays.stream(orderIds.split(","))
|
||||
.map(Long::parseLong)
|
||||
.collect(Collectors.toList());
|
||||
|
||||
List<Review> reviews = reviewMapper.selectByOrderIdList(orderIdList);
|
||||
return ApiResponse.success(reviews);
|
||||
} catch (Exception e) {
|
||||
return ApiResponse.error("参数错误");
|
||||
}
|
||||
}
|
||||
|
||||
@GetMapping("/by-order")
|
||||
public ApiResponse<Review> getReviewByOrder(@RequestParam Long orderId,
|
||||
HttpServletRequest request) {
|
||||
Long userId = (Long) request.getAttribute("userId");
|
||||
if (userId == null) {
|
||||
return ApiResponse.error("请先登录");
|
||||
}
|
||||
|
||||
Review review = reviewMapper.selectByOrderId(orderId);
|
||||
if (review == null) {
|
||||
return ApiResponse.error("未找到评论");
|
||||
}
|
||||
|
||||
// 验证评论是否属于当前用户
|
||||
if (!review.getUserId().equals(userId)) {
|
||||
return ApiResponse.error("无权访问");
|
||||
}
|
||||
|
||||
return ApiResponse.success(review);
|
||||
}
|
||||
}
|
||||
145
src/main/java/com/nlshop/controller/user/UserController.java
Normal file
145
src/main/java/com/nlshop/controller/user/UserController.java
Normal file
@@ -0,0 +1,145 @@
|
||||
package com.nlshop.controller.user;
|
||||
|
||||
import com.nlshop.dto.request.LoginRequest;
|
||||
import com.nlshop.dto.request.UserRegisterRequest;
|
||||
import com.nlshop.dto.response.ApiResponse;
|
||||
import com.nlshop.dto.response.LoginResponse;
|
||||
import com.nlshop.entity.User;
|
||||
import com.nlshop.entity.UserAddress;
|
||||
import com.nlshop.exception.BusinessException;
|
||||
import com.nlshop.service.user.UserService;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpSession;
|
||||
import java.util.List;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/")
|
||||
public class UserController {
|
||||
|
||||
@Autowired
|
||||
private UserService userService;
|
||||
|
||||
@PostMapping("/register")
|
||||
public ApiResponse<User> register(@Validated @RequestBody UserRegisterRequest request) {
|
||||
User user = userService.register(request);
|
||||
return ApiResponse.success(user);
|
||||
}
|
||||
|
||||
@PostMapping("/login")
|
||||
public ApiResponse<LoginResponse> login(@Validated @RequestBody LoginRequest request,
|
||||
HttpServletRequest httpRequest) {
|
||||
User user = userService.login(request.getUsername(), request.getPassword());
|
||||
|
||||
// 将用户信息存入Session
|
||||
HttpSession session = httpRequest.getSession(true);
|
||||
session.setAttribute("userId", user.getId());
|
||||
session.setAttribute("userType", "user");
|
||||
session.setAttribute("username", user.getUsername());
|
||||
|
||||
LoginResponse response = new LoginResponse();
|
||||
response.setToken(""); // 不再使用token,设为空字符串
|
||||
response.setUserId(user.getId());
|
||||
response.setUserType("user");
|
||||
response.setUsername(user.getUsername());
|
||||
response.setName(user.getName());
|
||||
|
||||
return ApiResponse.success(response);
|
||||
}
|
||||
|
||||
@PostMapping("/logout")
|
||||
public ApiResponse<Void> logout(HttpServletRequest request) {
|
||||
HttpSession session = request.getSession(false);
|
||||
if (session != null) {
|
||||
session.invalidate();
|
||||
}
|
||||
return ApiResponse.success(null);
|
||||
}
|
||||
|
||||
@GetMapping("/profile")
|
||||
public ApiResponse<User> getProfile(HttpServletRequest request) {
|
||||
Long userId = (Long) request.getAttribute("userId");
|
||||
if (userId == null) {
|
||||
return ApiResponse.error("请先登录");
|
||||
}
|
||||
User user = userService.getProfile(userId);
|
||||
return ApiResponse.success(user);
|
||||
}
|
||||
|
||||
@PutMapping("/profile")
|
||||
public ApiResponse<User> updateProfile(@RequestBody User user, HttpServletRequest request) {
|
||||
Long userId = (Long) request.getAttribute("userId");
|
||||
if (userId == null) {
|
||||
return ApiResponse.error("请先登录");
|
||||
}
|
||||
User updated = userService.updateProfile(userId, user);
|
||||
return ApiResponse.success(updated);
|
||||
}
|
||||
|
||||
@PostMapping("/avatar")
|
||||
public ApiResponse<String> uploadAvatar(@RequestParam("file") MultipartFile file,
|
||||
HttpServletRequest request) {
|
||||
Long userId = (Long) request.getAttribute("userId");
|
||||
if (userId == null) {
|
||||
return ApiResponse.error("请先登录");
|
||||
}
|
||||
// 文件上传逻辑在Controller中处理,这里简化
|
||||
return ApiResponse.success("上传成功");
|
||||
}
|
||||
|
||||
@GetMapping("/address")
|
||||
public ApiResponse<List<UserAddress>> getAddresses(HttpServletRequest request) {
|
||||
Long userId = (Long) request.getAttribute("userId");
|
||||
if (userId == null) {
|
||||
return ApiResponse.error("请先登录");
|
||||
}
|
||||
List<UserAddress> addresses = userService.getAddresses(userId);
|
||||
return ApiResponse.success(addresses);
|
||||
}
|
||||
|
||||
@PostMapping("/address")
|
||||
public ApiResponse<UserAddress> addAddress(@RequestBody UserAddress address,
|
||||
HttpServletRequest request) {
|
||||
Long userId = (Long) request.getAttribute("userId");
|
||||
if (userId == null) {
|
||||
return ApiResponse.error("请先登录");
|
||||
}
|
||||
UserAddress added = userService.addAddress(userId, address);
|
||||
return ApiResponse.success(added);
|
||||
}
|
||||
|
||||
@PutMapping("/address/{id}")
|
||||
public ApiResponse<UserAddress> updateAddress(@PathVariable Long id,
|
||||
@RequestBody UserAddress address,
|
||||
HttpServletRequest request) {
|
||||
Long userId = (Long) request.getAttribute("userId");
|
||||
if (userId == null) {
|
||||
return ApiResponse.error("请先登录");
|
||||
}
|
||||
try {
|
||||
UserAddress updated = userService.updateAddress(userId, id, address);
|
||||
return ApiResponse.success(updated);
|
||||
} catch (BusinessException e) {
|
||||
return ApiResponse.error(e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@DeleteMapping("/address/{id}")
|
||||
public ApiResponse<Void> deleteAddress(@PathVariable Long id,
|
||||
HttpServletRequest request) {
|
||||
Long userId = (Long) request.getAttribute("userId");
|
||||
if (userId == null) {
|
||||
return ApiResponse.error("请先登录");
|
||||
}
|
||||
try {
|
||||
userService.deleteAddress(userId, id);
|
||||
return ApiResponse.success(null);
|
||||
} catch (BusinessException e) {
|
||||
return ApiResponse.error(e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
19
src/main/java/com/nlshop/dto/request/CartAddRequest.java
Normal file
19
src/main/java/com/nlshop/dto/request/CartAddRequest.java
Normal file
@@ -0,0 +1,19 @@
|
||||
package com.nlshop.dto.request;
|
||||
|
||||
import lombok.Data;
|
||||
import javax.validation.constraints.NotNull;
|
||||
|
||||
@Data
|
||||
public class CartAddRequest {
|
||||
@NotNull(message = "商品ID不能为空")
|
||||
private Long productId;
|
||||
|
||||
private Long specId;
|
||||
|
||||
@NotNull(message = "数量不能为空")
|
||||
private Integer quantity;
|
||||
|
||||
private String customSweetness;
|
||||
private String customIce;
|
||||
private String toppings; // JSON格式
|
||||
}
|
||||
13
src/main/java/com/nlshop/dto/request/LoginRequest.java
Normal file
13
src/main/java/com/nlshop/dto/request/LoginRequest.java
Normal file
@@ -0,0 +1,13 @@
|
||||
package com.nlshop.dto.request;
|
||||
|
||||
import lombok.Data;
|
||||
import javax.validation.constraints.NotBlank;
|
||||
|
||||
@Data
|
||||
public class LoginRequest {
|
||||
@NotBlank(message = "用户名不能为空")
|
||||
private String username;
|
||||
|
||||
@NotBlank(message = "密码不能为空")
|
||||
private String password;
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
package com.nlshop.dto.request;
|
||||
|
||||
import lombok.Data;
|
||||
import javax.validation.constraints.NotBlank;
|
||||
|
||||
@Data
|
||||
public class MerchantRegisterRequest {
|
||||
@NotBlank(message = "用户名不能为空")
|
||||
private String username;
|
||||
|
||||
@NotBlank(message = "密码不能为空")
|
||||
private String password;
|
||||
|
||||
@NotBlank(message = "门店名称不能为空")
|
||||
private String storeName;
|
||||
|
||||
private String address;
|
||||
private String phone;
|
||||
private String email;
|
||||
private String managerName;
|
||||
}
|
||||
17
src/main/java/com/nlshop/dto/request/OrderCreateRequest.java
Normal file
17
src/main/java/com/nlshop/dto/request/OrderCreateRequest.java
Normal file
@@ -0,0 +1,17 @@
|
||||
package com.nlshop.dto.request;
|
||||
|
||||
import lombok.Data;
|
||||
import javax.validation.constraints.NotBlank;
|
||||
import javax.validation.constraints.NotNull;
|
||||
|
||||
@Data
|
||||
public class OrderCreateRequest {
|
||||
@NotNull(message = "商家ID不能为空")
|
||||
private Long merchantId;
|
||||
|
||||
@NotBlank(message = "收货地址不能为空")
|
||||
private String address;
|
||||
|
||||
@NotBlank(message = "联系电话不能为空")
|
||||
private String contactPhone;
|
||||
}
|
||||
10
src/main/java/com/nlshop/dto/request/PaymentRequest.java
Normal file
10
src/main/java/com/nlshop/dto/request/PaymentRequest.java
Normal file
@@ -0,0 +1,10 @@
|
||||
package com.nlshop.dto.request;
|
||||
|
||||
import lombok.Data;
|
||||
import javax.validation.constraints.NotBlank;
|
||||
|
||||
@Data
|
||||
public class PaymentRequest {
|
||||
@NotBlank(message = "支付方式不能为空")
|
||||
private String paymentMethod; // wechat/alipay
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
package com.nlshop.dto.request;
|
||||
|
||||
import lombok.Data;
|
||||
import javax.validation.constraints.NotBlank;
|
||||
import javax.validation.constraints.Pattern;
|
||||
|
||||
@Data
|
||||
public class UserRegisterRequest {
|
||||
@NotBlank(message = "用户名不能为空")
|
||||
private String username;
|
||||
|
||||
@NotBlank(message = "密码不能为空")
|
||||
@Pattern(regexp = ".{6,}", message = "密码长度至少6位")
|
||||
private String password;
|
||||
|
||||
private String name;
|
||||
private Integer gender;
|
||||
|
||||
@Pattern(regexp = "^1[3-9]\\d{9}$", message = "手机号格式不正确")
|
||||
private String phone;
|
||||
|
||||
private String email;
|
||||
private String address;
|
||||
}
|
||||
40
src/main/java/com/nlshop/dto/response/ApiResponse.java
Normal file
40
src/main/java/com/nlshop/dto/response/ApiResponse.java
Normal file
@@ -0,0 +1,40 @@
|
||||
package com.nlshop.dto.response;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
public class ApiResponse<T> {
|
||||
private Integer code;
|
||||
private String message;
|
||||
private T data;
|
||||
|
||||
public static <T> ApiResponse<T> success(T data) {
|
||||
ApiResponse<T> response = new ApiResponse<>();
|
||||
response.setCode(200);
|
||||
response.setMessage("操作成功");
|
||||
response.setData(data);
|
||||
return response;
|
||||
}
|
||||
|
||||
public static <T> ApiResponse<T> success(String message, T data) {
|
||||
ApiResponse<T> response = new ApiResponse<>();
|
||||
response.setCode(200);
|
||||
response.setMessage(message);
|
||||
response.setData(data);
|
||||
return response;
|
||||
}
|
||||
|
||||
public static <T> ApiResponse<T> error(String message) {
|
||||
ApiResponse<T> response = new ApiResponse<>();
|
||||
response.setCode(500);
|
||||
response.setMessage(message);
|
||||
return response;
|
||||
}
|
||||
|
||||
public static <T> ApiResponse<T> error(Integer code, String message) {
|
||||
ApiResponse<T> response = new ApiResponse<>();
|
||||
response.setCode(code);
|
||||
response.setMessage(message);
|
||||
return response;
|
||||
}
|
||||
}
|
||||
12
src/main/java/com/nlshop/dto/response/LoginResponse.java
Normal file
12
src/main/java/com/nlshop/dto/response/LoginResponse.java
Normal file
@@ -0,0 +1,12 @@
|
||||
package com.nlshop.dto.response;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
public class LoginResponse {
|
||||
private String token;
|
||||
private Long userId;
|
||||
private String userType; // user/merchant
|
||||
private String username;
|
||||
private String name;
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
package com.nlshop.dto.response;
|
||||
|
||||
import com.nlshop.entity.Order;
|
||||
import com.nlshop.entity.OrderItem;
|
||||
import com.nlshop.entity.OrderTrack;
|
||||
import lombok.Data;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
@Data
|
||||
public class OrderDetailResponse {
|
||||
private Order order;
|
||||
private List<OrderItem> items;
|
||||
private List<OrderTrack> tracks;
|
||||
private Map<Long, String> productImages; // 商品ID到图片URL的映射
|
||||
|
||||
public static OrderDetailResponse of(Order order, List<OrderItem> items, List<OrderTrack> tracks, Map<Long, String> productImages) {
|
||||
OrderDetailResponse response = new OrderDetailResponse();
|
||||
response.setOrder(order);
|
||||
response.setItems(items);
|
||||
response.setTracks(tracks);
|
||||
response.setProductImages(productImages);
|
||||
return response;
|
||||
}
|
||||
}
|
||||
23
src/main/java/com/nlshop/dto/response/PageResponse.java
Normal file
23
src/main/java/com/nlshop/dto/response/PageResponse.java
Normal file
@@ -0,0 +1,23 @@
|
||||
package com.nlshop.dto.response;
|
||||
|
||||
import lombok.Data;
|
||||
import java.util.List;
|
||||
|
||||
@Data
|
||||
public class PageResponse<T> {
|
||||
private List<T> list;
|
||||
private Long total;
|
||||
private Integer pageNum;
|
||||
private Integer pageSize;
|
||||
private Integer pages;
|
||||
|
||||
public static <T> PageResponse<T> of(List<T> list, Long total, Integer pageNum, Integer pageSize) {
|
||||
PageResponse<T> response = new PageResponse<>();
|
||||
response.setList(list);
|
||||
response.setTotal(total);
|
||||
response.setPageNum(pageNum);
|
||||
response.setPageSize(pageSize);
|
||||
response.setPages((int) Math.ceil((double) total / pageSize));
|
||||
return response;
|
||||
}
|
||||
}
|
||||
15
src/main/java/com/nlshop/entity/Announcement.java
Normal file
15
src/main/java/com/nlshop/entity/Announcement.java
Normal file
@@ -0,0 +1,15 @@
|
||||
package com.nlshop.entity;
|
||||
|
||||
import lombok.Data;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
@Data
|
||||
public class Announcement {
|
||||
private Long id;
|
||||
private Long merchantId;
|
||||
private String title;
|
||||
private String content;
|
||||
private String type; // ACTIVITY-活动,NOTICE-通知
|
||||
private Integer status; // 0-下架,1-发布
|
||||
private LocalDateTime createTime;
|
||||
}
|
||||
21
src/main/java/com/nlshop/entity/CartItem.java
Normal file
21
src/main/java/com/nlshop/entity/CartItem.java
Normal file
@@ -0,0 +1,21 @@
|
||||
package com.nlshop.entity;
|
||||
|
||||
import lombok.Data;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
@Data
|
||||
public class CartItem {
|
||||
private Long id;
|
||||
private Long userId;
|
||||
private Long productId;
|
||||
private Long specId;
|
||||
private Integer quantity;
|
||||
private String customSweetness;
|
||||
private String customIce;
|
||||
private String toppings; // JSON格式
|
||||
private LocalDateTime createTime;
|
||||
|
||||
// 关联查询字段
|
||||
private Product product;
|
||||
private ProductSpec spec;
|
||||
}
|
||||
17
src/main/java/com/nlshop/entity/Inventory.java
Normal file
17
src/main/java/com/nlshop/entity/Inventory.java
Normal file
@@ -0,0 +1,17 @@
|
||||
package com.nlshop.entity;
|
||||
|
||||
import lombok.Data;
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
@Data
|
||||
public class Inventory {
|
||||
private Long id;
|
||||
private Long merchantId;
|
||||
private String materialName;
|
||||
private BigDecimal quantity;
|
||||
private String unit;
|
||||
private BigDecimal minThreshold;
|
||||
private LocalDateTime lastInTime;
|
||||
private LocalDateTime lastOutTime;
|
||||
}
|
||||
15
src/main/java/com/nlshop/entity/InventoryLog.java
Normal file
15
src/main/java/com/nlshop/entity/InventoryLog.java
Normal file
@@ -0,0 +1,15 @@
|
||||
package com.nlshop.entity;
|
||||
|
||||
import lombok.Data;
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
@Data
|
||||
public class InventoryLog {
|
||||
private Long id;
|
||||
private Long inventoryId;
|
||||
private String operationType; // IN-入库,OUT-出库
|
||||
private BigDecimal quantity;
|
||||
private String operator;
|
||||
private LocalDateTime createTime;
|
||||
}
|
||||
19
src/main/java/com/nlshop/entity/Merchant.java
Normal file
19
src/main/java/com/nlshop/entity/Merchant.java
Normal file
@@ -0,0 +1,19 @@
|
||||
package com.nlshop.entity;
|
||||
|
||||
import lombok.Data;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
@Data
|
||||
public class Merchant {
|
||||
private Long id;
|
||||
private String username;
|
||||
private String password;
|
||||
private String storeName;
|
||||
private String address;
|
||||
private String phone;
|
||||
private String email;
|
||||
private String managerName;
|
||||
private String avatar;
|
||||
private LocalDateTime createTime;
|
||||
private LocalDateTime updateTime;
|
||||
}
|
||||
21
src/main/java/com/nlshop/entity/Message.java
Normal file
21
src/main/java/com/nlshop/entity/Message.java
Normal file
@@ -0,0 +1,21 @@
|
||||
package com.nlshop.entity;
|
||||
|
||||
import lombok.Data;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
@Data
|
||||
public class Message {
|
||||
private Long id;
|
||||
private Long userId;
|
||||
private Long merchantId;
|
||||
private Long orderId;
|
||||
private String content;
|
||||
private String reply;
|
||||
private Integer status; // 0-未回复,1-已回复
|
||||
private LocalDateTime createTime;
|
||||
private LocalDateTime replyTime;
|
||||
|
||||
// 关联查询字段
|
||||
private User user;
|
||||
private Order order;
|
||||
}
|
||||
25
src/main/java/com/nlshop/entity/Order.java
Normal file
25
src/main/java/com/nlshop/entity/Order.java
Normal file
@@ -0,0 +1,25 @@
|
||||
package com.nlshop.entity;
|
||||
|
||||
import lombok.Data;
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
@Data
|
||||
public class Order {
|
||||
private Long id;
|
||||
private String orderNo;
|
||||
private Long userId;
|
||||
private Long merchantId;
|
||||
private BigDecimal totalAmount;
|
||||
private String paymentMethod; // wechat/alipay
|
||||
private Integer paymentStatus; // 0-未支付,1-已支付
|
||||
private String orderStatus; // PENDING_PAY, MAKING, READY, COMPLETED, CANCELLED
|
||||
private String address;
|
||||
private String contactPhone;
|
||||
private LocalDateTime createTime;
|
||||
private LocalDateTime payTime;
|
||||
private LocalDateTime completeTime;
|
||||
|
||||
// 关联查询字段
|
||||
private Merchant merchant;
|
||||
}
|
||||
19
src/main/java/com/nlshop/entity/OrderItem.java
Normal file
19
src/main/java/com/nlshop/entity/OrderItem.java
Normal file
@@ -0,0 +1,19 @@
|
||||
package com.nlshop.entity;
|
||||
|
||||
import lombok.Data;
|
||||
import java.math.BigDecimal;
|
||||
|
||||
@Data
|
||||
public class OrderItem {
|
||||
private Long id;
|
||||
private Long orderId;
|
||||
private Long productId;
|
||||
private String productName;
|
||||
private String specName;
|
||||
private Integer quantity;
|
||||
private BigDecimal price;
|
||||
private String customSweetness;
|
||||
private String customIce;
|
||||
private String toppings; // JSON格式
|
||||
private BigDecimal subtotal;
|
||||
}
|
||||
13
src/main/java/com/nlshop/entity/OrderTrack.java
Normal file
13
src/main/java/com/nlshop/entity/OrderTrack.java
Normal file
@@ -0,0 +1,13 @@
|
||||
package com.nlshop.entity;
|
||||
|
||||
import lombok.Data;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
@Data
|
||||
public class OrderTrack {
|
||||
private Long id;
|
||||
private Long orderId;
|
||||
private String status;
|
||||
private String description;
|
||||
private LocalDateTime createTime;
|
||||
}
|
||||
18
src/main/java/com/nlshop/entity/Product.java
Normal file
18
src/main/java/com/nlshop/entity/Product.java
Normal file
@@ -0,0 +1,18 @@
|
||||
package com.nlshop.entity;
|
||||
|
||||
import lombok.Data;
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
@Data
|
||||
public class Product {
|
||||
private Long id;
|
||||
private Long merchantId;
|
||||
private String name;
|
||||
private String category;
|
||||
private String description;
|
||||
private BigDecimal basePrice;
|
||||
private String image;
|
||||
private Integer status; // 0-下架,1-上架
|
||||
private LocalDateTime createTime;
|
||||
}
|
||||
13
src/main/java/com/nlshop/entity/ProductCustom.java
Normal file
13
src/main/java/com/nlshop/entity/ProductCustom.java
Normal file
@@ -0,0 +1,13 @@
|
||||
package com.nlshop.entity;
|
||||
|
||||
import lombok.Data;
|
||||
import java.math.BigDecimal;
|
||||
|
||||
@Data
|
||||
public class ProductCustom {
|
||||
private Long id;
|
||||
private Long productId;
|
||||
private String optionType; // sweetness-甜度,ice-冰度
|
||||
private String optionValue;
|
||||
private BigDecimal priceAdjust;
|
||||
}
|
||||
12
src/main/java/com/nlshop/entity/ProductSpec.java
Normal file
12
src/main/java/com/nlshop/entity/ProductSpec.java
Normal file
@@ -0,0 +1,12 @@
|
||||
package com.nlshop.entity;
|
||||
|
||||
import lombok.Data;
|
||||
import java.math.BigDecimal;
|
||||
|
||||
@Data
|
||||
public class ProductSpec {
|
||||
private Long id;
|
||||
private Long productId;
|
||||
private String specName;
|
||||
private BigDecimal priceAdjust;
|
||||
}
|
||||
12
src/main/java/com/nlshop/entity/ProductTopping.java
Normal file
12
src/main/java/com/nlshop/entity/ProductTopping.java
Normal file
@@ -0,0 +1,12 @@
|
||||
package com.nlshop.entity;
|
||||
|
||||
import lombok.Data;
|
||||
import java.math.BigDecimal;
|
||||
|
||||
@Data
|
||||
public class ProductTopping {
|
||||
private Long id;
|
||||
private Long productId;
|
||||
private String toppingName;
|
||||
private BigDecimal price;
|
||||
}
|
||||
20
src/main/java/com/nlshop/entity/Review.java
Normal file
20
src/main/java/com/nlshop/entity/Review.java
Normal file
@@ -0,0 +1,20 @@
|
||||
package com.nlshop.entity;
|
||||
|
||||
import lombok.Data;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
@Data
|
||||
public class Review {
|
||||
private Long id;
|
||||
private Long userId;
|
||||
private Long orderId;
|
||||
private Long productId;
|
||||
private Integer rating; // 1-5
|
||||
private String content;
|
||||
private String reply;
|
||||
private LocalDateTime createTime;
|
||||
|
||||
// 关联查询字段
|
||||
private User user;
|
||||
private Product product;
|
||||
}
|
||||
19
src/main/java/com/nlshop/entity/User.java
Normal file
19
src/main/java/com/nlshop/entity/User.java
Normal file
@@ -0,0 +1,19 @@
|
||||
package com.nlshop.entity;
|
||||
|
||||
import lombok.Data;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
@Data
|
||||
public class User {
|
||||
private Long id;
|
||||
private String username;
|
||||
private String password;
|
||||
private String name;
|
||||
private Integer gender; // 0-女,1-男
|
||||
private String phone;
|
||||
private String email;
|
||||
private String avatar;
|
||||
private String address;
|
||||
private LocalDateTime createTime;
|
||||
private LocalDateTime updateTime;
|
||||
}
|
||||
15
src/main/java/com/nlshop/entity/UserAddress.java
Normal file
15
src/main/java/com/nlshop/entity/UserAddress.java
Normal file
@@ -0,0 +1,15 @@
|
||||
package com.nlshop.entity;
|
||||
|
||||
import lombok.Data;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
@Data
|
||||
public class UserAddress {
|
||||
private Long id;
|
||||
private Long userId;
|
||||
private String address;
|
||||
private String contactName;
|
||||
private String contactPhone;
|
||||
private Integer isDefault; // 0-否,1-是
|
||||
private LocalDateTime createTime;
|
||||
}
|
||||
15
src/main/java/com/nlshop/entity/UserBehavior.java
Normal file
15
src/main/java/com/nlshop/entity/UserBehavior.java
Normal file
@@ -0,0 +1,15 @@
|
||||
package com.nlshop.entity;
|
||||
|
||||
import lombok.Data;
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
@Data
|
||||
public class UserBehavior {
|
||||
private Long id;
|
||||
private Long userId;
|
||||
private Long productId;
|
||||
private String behaviorType; // VIEW-浏览,PURCHASE-购买,RATING-评分
|
||||
private BigDecimal score; // 0-5
|
||||
private LocalDateTime createTime;
|
||||
}
|
||||
13
src/main/java/com/nlshop/entity/UserSimilarity.java
Normal file
13
src/main/java/com/nlshop/entity/UserSimilarity.java
Normal file
@@ -0,0 +1,13 @@
|
||||
package com.nlshop.entity;
|
||||
|
||||
import lombok.Data;
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
@Data
|
||||
public class UserSimilarity {
|
||||
private Long user1Id;
|
||||
private Long user2Id;
|
||||
private BigDecimal similarityScore;
|
||||
private LocalDateTime updateTime;
|
||||
}
|
||||
18
src/main/java/com/nlshop/exception/BusinessException.java
Normal file
18
src/main/java/com/nlshop/exception/BusinessException.java
Normal file
@@ -0,0 +1,18 @@
|
||||
package com.nlshop.exception;
|
||||
|
||||
import lombok.Getter;
|
||||
|
||||
@Getter
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
package com.nlshop.exception;
|
||||
|
||||
import com.nlshop.dto.response.ApiResponse;
|
||||
import org.springframework.validation.BindException;
|
||||
import org.springframework.validation.FieldError;
|
||||
import org.springframework.web.bind.MethodArgumentNotValidException;
|
||||
import org.springframework.web.bind.annotation.ControllerAdvice;
|
||||
import org.springframework.web.bind.annotation.ExceptionHandler;
|
||||
import org.springframework.web.bind.annotation.ResponseBody;
|
||||
|
||||
import javax.validation.ConstraintViolation;
|
||||
import javax.validation.ConstraintViolationException;
|
||||
import java.util.Set;
|
||||
|
||||
@ControllerAdvice
|
||||
public class GlobalExceptionHandler {
|
||||
|
||||
@ExceptionHandler(Exception.class)
|
||||
@ResponseBody
|
||||
public ApiResponse<?> handleException(Exception e) {
|
||||
e.printStackTrace();
|
||||
return ApiResponse.error("系统错误: " + e.getMessage());
|
||||
}
|
||||
|
||||
@ExceptionHandler(BusinessException.class)
|
||||
@ResponseBody
|
||||
public ApiResponse<?> handleBusinessException(BusinessException e) {
|
||||
return ApiResponse.error(e.getCode(), e.getMessage());
|
||||
}
|
||||
|
||||
@ExceptionHandler(MethodArgumentNotValidException.class)
|
||||
@ResponseBody
|
||||
public ApiResponse<?> handleValidationException(MethodArgumentNotValidException e) {
|
||||
FieldError fieldError = e.getBindingResult().getFieldError();
|
||||
String message = fieldError != null ? fieldError.getDefaultMessage() : "参数验证失败";
|
||||
return ApiResponse.error(400, message);
|
||||
}
|
||||
|
||||
@ExceptionHandler(BindException.class)
|
||||
@ResponseBody
|
||||
public ApiResponse<?> handleBindException(BindException e) {
|
||||
FieldError fieldError = e.getBindingResult().getFieldError();
|
||||
String message = fieldError != null ? fieldError.getDefaultMessage() : "参数验证失败";
|
||||
return ApiResponse.error(400, message);
|
||||
}
|
||||
|
||||
@ExceptionHandler(ConstraintViolationException.class)
|
||||
@ResponseBody
|
||||
public ApiResponse<?> handleConstraintViolationException(ConstraintViolationException e) {
|
||||
Set<ConstraintViolation<?>> violations = e.getConstraintViolations();
|
||||
String message = violations.iterator().next().getMessage();
|
||||
return ApiResponse.error(400, message);
|
||||
}
|
||||
}
|
||||
74
src/main/java/com/nlshop/interceptor/AuthInterceptor.java
Normal file
74
src/main/java/com/nlshop/interceptor/AuthInterceptor.java
Normal file
@@ -0,0 +1,74 @@
|
||||
package com.nlshop.interceptor;
|
||||
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.web.servlet.HandlerInterceptor;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import javax.servlet.http.HttpSession;
|
||||
import java.io.IOException;
|
||||
|
||||
@Component
|
||||
public class AuthInterceptor implements HandlerInterceptor {
|
||||
|
||||
@Override
|
||||
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
|
||||
// 允许OPTIONS请求
|
||||
if ("OPTIONS".equals(request.getMethod())) {
|
||||
return true;
|
||||
}
|
||||
|
||||
String requestPath = request.getRequestURI();
|
||||
boolean isHtmlPage = requestPath.endsWith(".html");
|
||||
boolean isMerchantPath = requestPath.startsWith("/merchant");
|
||||
|
||||
// 从Session中获取用户信息
|
||||
HttpSession session = request.getSession(false);
|
||||
Long userId = null;
|
||||
String userType = null;
|
||||
|
||||
if (session != null) {
|
||||
userId = (Long) session.getAttribute("userId");
|
||||
userType = (String) session.getAttribute("userType");
|
||||
|
||||
if (userId != null && userType != null) {
|
||||
// 将用户信息存入request
|
||||
request.setAttribute("userId", userId);
|
||||
request.setAttribute("userType", userType);
|
||||
}
|
||||
}
|
||||
|
||||
// 检查用户是否已登录
|
||||
boolean isLoggedIn = (userId != null && userType != null);
|
||||
|
||||
// 如果未登录,进行拦截
|
||||
if (!isLoggedIn) {
|
||||
// 保存原始URL到session,登录后可以跳转回来
|
||||
if (session != null && !requestPath.equals("/login.html") &&
|
||||
!requestPath.equals("/register.html") &&
|
||||
!requestPath.equals("/merchant/login.html") &&
|
||||
!requestPath.equals("/merchant/register.html")) {
|
||||
session.setAttribute("returnUrl", requestPath);
|
||||
}
|
||||
|
||||
if (isHtmlPage) {
|
||||
// HTML页面请求:重定向到登录页
|
||||
if (isMerchantPath) {
|
||||
response.sendRedirect("/merchant/login.html");
|
||||
} else {
|
||||
response.sendRedirect("/login.html");
|
||||
}
|
||||
return false;
|
||||
} else {
|
||||
// API请求:返回401 JSON错误
|
||||
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
|
||||
response.setContentType("application/json;charset=UTF-8");
|
||||
response.getWriter().write("{\"code\":401,\"message\":\"请先登录\"}");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// 已登录,允许继续
|
||||
return true;
|
||||
}
|
||||
}
|
||||
15
src/main/java/com/nlshop/mapper/AnnouncementMapper.java
Normal file
15
src/main/java/com/nlshop/mapper/AnnouncementMapper.java
Normal file
@@ -0,0 +1,15 @@
|
||||
package com.nlshop.mapper;
|
||||
|
||||
import com.nlshop.entity.Announcement;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
import java.util.List;
|
||||
|
||||
@Mapper
|
||||
public interface AnnouncementMapper {
|
||||
int insert(Announcement announcement);
|
||||
List<Announcement> selectByMerchantId(Long merchantId);
|
||||
List<Announcement> selectPublished();
|
||||
Announcement selectById(Long id);
|
||||
int update(Announcement announcement);
|
||||
int delete(Long id);
|
||||
}
|
||||
21
src/main/java/com/nlshop/mapper/CartMapper.java
Normal file
21
src/main/java/com/nlshop/mapper/CartMapper.java
Normal file
@@ -0,0 +1,21 @@
|
||||
package com.nlshop.mapper;
|
||||
|
||||
import com.nlshop.entity.CartItem;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
import java.util.List;
|
||||
|
||||
@Mapper
|
||||
public interface CartMapper {
|
||||
int insert(CartItem item);
|
||||
List<CartItem> selectByUserId(Long userId);
|
||||
CartItem selectById(Long id);
|
||||
CartItem selectByUserAndProduct(@Param("userId") Long userId, @Param("productId") Long productId,
|
||||
@Param("specId") Long specId, @Param("customSweetness") String customSweetness,
|
||||
@Param("customIce") String customIce, @Param("toppings") String toppings);
|
||||
int update(CartItem item);
|
||||
int updateQuantity(@Param("id") Long id, @Param("quantity") Integer quantity);
|
||||
int delete(Long id);
|
||||
int deleteByUserId(Long userId);
|
||||
int deleteBatch(@Param("ids") List<Long> ids, @Param("userId") Long userId);
|
||||
}
|
||||
11
src/main/java/com/nlshop/mapper/InventoryLogMapper.java
Normal file
11
src/main/java/com/nlshop/mapper/InventoryLogMapper.java
Normal file
@@ -0,0 +1,11 @@
|
||||
package com.nlshop.mapper;
|
||||
|
||||
import com.nlshop.entity.InventoryLog;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
import java.util.List;
|
||||
|
||||
@Mapper
|
||||
public interface InventoryLogMapper {
|
||||
int insert(InventoryLog log);
|
||||
List<InventoryLog> selectByInventoryId(Long inventoryId);
|
||||
}
|
||||
17
src/main/java/com/nlshop/mapper/InventoryMapper.java
Normal file
17
src/main/java/com/nlshop/mapper/InventoryMapper.java
Normal file
@@ -0,0 +1,17 @@
|
||||
package com.nlshop.mapper;
|
||||
|
||||
import com.nlshop.entity.Inventory;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
import java.util.List;
|
||||
|
||||
@Mapper
|
||||
public interface InventoryMapper {
|
||||
int insert(Inventory inventory);
|
||||
List<Inventory> selectByMerchantId(Long merchantId);
|
||||
Inventory selectById(Long id);
|
||||
Inventory selectByMerchantAndMaterial(@Param("merchantId") Long merchantId, @Param("materialName") String materialName);
|
||||
int update(Inventory inventory);
|
||||
int delete(Long id);
|
||||
List<Inventory> selectLowStock(Long merchantId);
|
||||
}
|
||||
12
src/main/java/com/nlshop/mapper/MerchantMapper.java
Normal file
12
src/main/java/com/nlshop/mapper/MerchantMapper.java
Normal file
@@ -0,0 +1,12 @@
|
||||
package com.nlshop.mapper;
|
||||
|
||||
import com.nlshop.entity.Merchant;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
@Mapper
|
||||
public interface MerchantMapper {
|
||||
int insert(Merchant merchant);
|
||||
Merchant selectById(Long id);
|
||||
Merchant selectByUsername(String username);
|
||||
int update(Merchant merchant);
|
||||
}
|
||||
14
src/main/java/com/nlshop/mapper/MessageMapper.java
Normal file
14
src/main/java/com/nlshop/mapper/MessageMapper.java
Normal file
@@ -0,0 +1,14 @@
|
||||
package com.nlshop.mapper;
|
||||
|
||||
import com.nlshop.entity.Message;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
import java.util.List;
|
||||
|
||||
@Mapper
|
||||
public interface MessageMapper {
|
||||
int insert(Message message);
|
||||
List<Message> selectByUserId(Long userId);
|
||||
List<Message> selectByMerchantId(Long merchantId);
|
||||
Message selectById(Long id);
|
||||
int update(Message message);
|
||||
}
|
||||
12
src/main/java/com/nlshop/mapper/OrderItemMapper.java
Normal file
12
src/main/java/com/nlshop/mapper/OrderItemMapper.java
Normal file
@@ -0,0 +1,12 @@
|
||||
package com.nlshop.mapper;
|
||||
|
||||
import com.nlshop.entity.OrderItem;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
import java.util.List;
|
||||
|
||||
@Mapper
|
||||
public interface OrderItemMapper {
|
||||
int insert(OrderItem item);
|
||||
List<OrderItem> selectByOrderId(Long orderId);
|
||||
OrderItem selectById(Long id);
|
||||
}
|
||||
22
src/main/java/com/nlshop/mapper/OrderMapper.java
Normal file
22
src/main/java/com/nlshop/mapper/OrderMapper.java
Normal file
@@ -0,0 +1,22 @@
|
||||
package com.nlshop.mapper;
|
||||
|
||||
import com.nlshop.entity.Order;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
import java.util.List;
|
||||
|
||||
@Mapper
|
||||
public interface OrderMapper {
|
||||
int insert(Order order);
|
||||
Order selectById(Long id);
|
||||
Order selectByOrderNo(String orderNo);
|
||||
List<Order> selectByUserId(@Param("userId") Long userId, @Param("status") String status);
|
||||
List<Order> selectByMerchantId(@Param("merchantId") Long merchantId, @Param("status") String status);
|
||||
int update(Order order);
|
||||
int updateStatus(@Param("id") Long id, @Param("status") String status);
|
||||
int updatePayment(@Param("id") Long id, @Param("paymentStatus") Integer paymentStatus,
|
||||
@Param("paymentMethod") String paymentMethod);
|
||||
List<java.util.Map<String, Object>> selectStickyUsers(@Param("merchantId") Long merchantId,
|
||||
@Param("days") int days,
|
||||
@Param("minOrders") int minOrders);
|
||||
}
|
||||
11
src/main/java/com/nlshop/mapper/OrderTrackMapper.java
Normal file
11
src/main/java/com/nlshop/mapper/OrderTrackMapper.java
Normal file
@@ -0,0 +1,11 @@
|
||||
package com.nlshop.mapper;
|
||||
|
||||
import com.nlshop.entity.OrderTrack;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
import java.util.List;
|
||||
|
||||
@Mapper
|
||||
public interface OrderTrackMapper {
|
||||
int insert(OrderTrack track);
|
||||
List<OrderTrack> selectByOrderId(Long orderId);
|
||||
}
|
||||
15
src/main/java/com/nlshop/mapper/ProductCustomMapper.java
Normal file
15
src/main/java/com/nlshop/mapper/ProductCustomMapper.java
Normal file
@@ -0,0 +1,15 @@
|
||||
package com.nlshop.mapper;
|
||||
|
||||
import com.nlshop.entity.ProductCustom;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
import java.util.List;
|
||||
|
||||
@Mapper
|
||||
public interface ProductCustomMapper {
|
||||
int insert(ProductCustom custom);
|
||||
List<ProductCustom> selectByProductId(Long productId);
|
||||
ProductCustom selectById(Long id);
|
||||
int update(ProductCustom custom);
|
||||
int delete(Long id);
|
||||
int deleteByProductId(Long productId);
|
||||
}
|
||||
17
src/main/java/com/nlshop/mapper/ProductMapper.java
Normal file
17
src/main/java/com/nlshop/mapper/ProductMapper.java
Normal file
@@ -0,0 +1,17 @@
|
||||
package com.nlshop.mapper;
|
||||
|
||||
import com.nlshop.entity.Product;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
import java.util.List;
|
||||
|
||||
@Mapper
|
||||
public interface ProductMapper {
|
||||
int insert(Product product);
|
||||
Product selectById(Long id);
|
||||
List<Product> selectByMerchantId(@Param("merchantId") Long merchantId, @Param("status") Integer status);
|
||||
List<Product> selectAll(@Param("category") String category, @Param("status") Integer status);
|
||||
int update(Product product);
|
||||
int updateStatus(@Param("id") Long id, @Param("status") Integer status);
|
||||
List<Product> selectHotProducts(int limit);
|
||||
}
|
||||
15
src/main/java/com/nlshop/mapper/ProductSpecMapper.java
Normal file
15
src/main/java/com/nlshop/mapper/ProductSpecMapper.java
Normal file
@@ -0,0 +1,15 @@
|
||||
package com.nlshop.mapper;
|
||||
|
||||
import com.nlshop.entity.ProductSpec;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
import java.util.List;
|
||||
|
||||
@Mapper
|
||||
public interface ProductSpecMapper {
|
||||
int insert(ProductSpec spec);
|
||||
List<ProductSpec> selectByProductId(Long productId);
|
||||
ProductSpec selectById(Long id);
|
||||
int update(ProductSpec spec);
|
||||
int delete(Long id);
|
||||
int deleteByProductId(Long productId);
|
||||
}
|
||||
15
src/main/java/com/nlshop/mapper/ProductToppingMapper.java
Normal file
15
src/main/java/com/nlshop/mapper/ProductToppingMapper.java
Normal file
@@ -0,0 +1,15 @@
|
||||
package com.nlshop.mapper;
|
||||
|
||||
import com.nlshop.entity.ProductTopping;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
import java.util.List;
|
||||
|
||||
@Mapper
|
||||
public interface ProductToppingMapper {
|
||||
int insert(ProductTopping topping);
|
||||
List<ProductTopping> selectByProductId(Long productId);
|
||||
ProductTopping selectById(Long id);
|
||||
int update(ProductTopping topping);
|
||||
int delete(Long id);
|
||||
int deleteByProductId(Long productId);
|
||||
}
|
||||
17
src/main/java/com/nlshop/mapper/ReviewMapper.java
Normal file
17
src/main/java/com/nlshop/mapper/ReviewMapper.java
Normal file
@@ -0,0 +1,17 @@
|
||||
package com.nlshop.mapper;
|
||||
|
||||
import com.nlshop.entity.Review;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
import java.util.List;
|
||||
|
||||
@Mapper
|
||||
public interface ReviewMapper {
|
||||
int insert(Review review);
|
||||
List<Review> selectByMerchantId(Long merchantId);
|
||||
List<Review> selectByProductId(Long productId);
|
||||
Review selectById(Long id);
|
||||
Review selectByOrderId(Long orderId);
|
||||
List<Review> selectByOrderIdList(@Param("orderIds") List<Long> orderIds);
|
||||
int update(Review review);
|
||||
}
|
||||
15
src/main/java/com/nlshop/mapper/UserAddressMapper.java
Normal file
15
src/main/java/com/nlshop/mapper/UserAddressMapper.java
Normal file
@@ -0,0 +1,15 @@
|
||||
package com.nlshop.mapper;
|
||||
|
||||
import com.nlshop.entity.UserAddress;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
import java.util.List;
|
||||
|
||||
@Mapper
|
||||
public interface UserAddressMapper {
|
||||
int insert(UserAddress address);
|
||||
List<UserAddress> selectByUserId(Long userId);
|
||||
UserAddress selectById(Long id);
|
||||
int update(UserAddress address);
|
||||
int delete(Long id);
|
||||
UserAddress selectDefaultByUserId(Long userId);
|
||||
}
|
||||
19
src/main/java/com/nlshop/mapper/UserBehaviorMapper.java
Normal file
19
src/main/java/com/nlshop/mapper/UserBehaviorMapper.java
Normal file
@@ -0,0 +1,19 @@
|
||||
package com.nlshop.mapper;
|
||||
|
||||
import com.nlshop.entity.UserBehavior;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
import java.util.List;
|
||||
|
||||
@Mapper
|
||||
public interface UserBehaviorMapper {
|
||||
int insert(UserBehavior behavior);
|
||||
List<UserBehavior> selectByUserId(Long userId);
|
||||
List<UserBehavior> selectByProductId(Long productId);
|
||||
UserBehavior selectByUserAndProduct(@Param("userId") Long userId, @Param("productId") Long productId,
|
||||
@Param("behaviorType") String behaviorType);
|
||||
int update(UserBehavior behavior);
|
||||
List<Long> selectProductIdsByUserId(Long userId);
|
||||
List<Long> selectUserIdsByProductId(Long productId);
|
||||
List<java.util.Map<String, Object>> selectActiveUsers(@Param("merchantId") Long merchantId, @Param("days") int days);
|
||||
}
|
||||
16
src/main/java/com/nlshop/mapper/UserMapper.java
Normal file
16
src/main/java/com/nlshop/mapper/UserMapper.java
Normal file
@@ -0,0 +1,16 @@
|
||||
package com.nlshop.mapper;
|
||||
|
||||
import com.nlshop.entity.User;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
import java.util.List;
|
||||
|
||||
@Mapper
|
||||
public interface UserMapper {
|
||||
int insert(User user);
|
||||
User selectById(Long id);
|
||||
User selectByUsername(String username);
|
||||
int update(User user);
|
||||
int updatePassword(@Param("id") Long id, @Param("password") String password);
|
||||
List<User> selectAll();
|
||||
}
|
||||
13
src/main/java/com/nlshop/mapper/UserSimilarityMapper.java
Normal file
13
src/main/java/com/nlshop/mapper/UserSimilarityMapper.java
Normal file
@@ -0,0 +1,13 @@
|
||||
package com.nlshop.mapper;
|
||||
|
||||
import com.nlshop.entity.UserSimilarity;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
import java.util.List;
|
||||
|
||||
@Mapper
|
||||
public interface UserSimilarityMapper {
|
||||
int insertOrUpdate(UserSimilarity similarity);
|
||||
UserSimilarity selectByUserIds(@Param("user1Id") Long user1Id, @Param("user2Id") Long user2Id);
|
||||
List<UserSimilarity> selectSimilarUsers(@Param("userId") Long userId, @Param("limit") int limit);
|
||||
}
|
||||
@@ -0,0 +1,324 @@
|
||||
package com.nlshop.service.algorithm;
|
||||
|
||||
import com.nlshop.entity.Product;
|
||||
import com.nlshop.entity.UserBehavior;
|
||||
import com.nlshop.entity.UserSimilarity;
|
||||
import com.nlshop.mapper.ProductMapper;
|
||||
import com.nlshop.mapper.UserBehaviorMapper;
|
||||
import com.nlshop.mapper.UserSimilarityMapper;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.math.RoundingMode;
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@Service
|
||||
public class CollaborativeFilteringService {
|
||||
|
||||
@Autowired
|
||||
private UserBehaviorMapper userBehaviorMapper;
|
||||
|
||||
@Autowired
|
||||
private UserSimilarityMapper userSimilarityMapper;
|
||||
|
||||
@Autowired
|
||||
private ProductMapper productMapper;
|
||||
|
||||
/**
|
||||
* 计算用户相似度矩阵
|
||||
*/
|
||||
public void calculateUserSimilarity(Long userId) {
|
||||
List<Long> productIds = userBehaviorMapper.selectProductIdsByUserId(userId);
|
||||
if (productIds.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 获取所有用户
|
||||
Set<Long> allUserIds = new HashSet<>();
|
||||
for (Long productId : productIds) {
|
||||
allUserIds.addAll(userBehaviorMapper.selectUserIdsByProductId(productId));
|
||||
}
|
||||
allUserIds.remove(userId);
|
||||
|
||||
// 计算与每个用户的相似度
|
||||
Map<Long, Map<Long, BigDecimal>> userProductScores = buildUserProductMatrix(userId, allUserIds);
|
||||
|
||||
for (Long otherUserId : allUserIds) {
|
||||
BigDecimal similarity = calculateCosineSimilarity(
|
||||
userProductScores.get(userId),
|
||||
userProductScores.get(otherUserId)
|
||||
);
|
||||
|
||||
if (similarity.compareTo(BigDecimal.ZERO) > 0) {
|
||||
UserSimilarity userSimilarity = new UserSimilarity();
|
||||
userSimilarity.setUser1Id(Math.min(userId, otherUserId));
|
||||
userSimilarity.setUser2Id(Math.max(userId, otherUserId));
|
||||
userSimilarity.setSimilarityScore(similarity);
|
||||
userSimilarityMapper.insertOrUpdate(userSimilarity);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建用户-商品评分矩阵
|
||||
*/
|
||||
private Map<Long, Map<Long, BigDecimal>> buildUserProductMatrix(Long userId, Set<Long> otherUserIds) {
|
||||
Map<Long, Map<Long, BigDecimal>> matrix = new HashMap<>();
|
||||
|
||||
// 当前用户的评分
|
||||
Map<Long, BigDecimal> userScores = new HashMap<>();
|
||||
List<UserBehavior> userBehaviors = userBehaviorMapper.selectByUserId(userId);
|
||||
for (UserBehavior behavior : userBehaviors) {
|
||||
BigDecimal score = calculateBehaviorScore(behavior);
|
||||
userScores.put(behavior.getProductId(), score);
|
||||
}
|
||||
matrix.put(userId, userScores);
|
||||
|
||||
// 其他用户的评分
|
||||
for (Long otherUserId : otherUserIds) {
|
||||
Map<Long, BigDecimal> otherScores = new HashMap<>();
|
||||
List<UserBehavior> otherBehaviors = userBehaviorMapper.selectByUserId(otherUserId);
|
||||
for (UserBehavior behavior : otherBehaviors) {
|
||||
BigDecimal score = calculateBehaviorScore(behavior);
|
||||
otherScores.put(behavior.getProductId(), score);
|
||||
}
|
||||
matrix.put(otherUserId, otherScores);
|
||||
}
|
||||
|
||||
return matrix;
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算行为评分(综合购买、评分、浏览)
|
||||
*/
|
||||
private BigDecimal calculateBehaviorScore(UserBehavior behavior) {
|
||||
BigDecimal score = BigDecimal.ZERO;
|
||||
|
||||
if ("PURCHASE".equals(behavior.getBehaviorType())) {
|
||||
score = score.add(new BigDecimal("3.0")); // 购买权重3
|
||||
} else if ("RATING".equals(behavior.getBehaviorType())) {
|
||||
score = score.add(behavior.getScore()); // 评分直接使用
|
||||
} else if ("VIEW".equals(behavior.getBehaviorType())) {
|
||||
score = score.add(new BigDecimal("1.0")); // 浏览权重1
|
||||
}
|
||||
|
||||
return score;
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算余弦相似度
|
||||
*/
|
||||
private BigDecimal calculateCosineSimilarity(Map<Long, BigDecimal> user1Scores, Map<Long, BigDecimal> user2Scores) {
|
||||
Set<Long> commonProducts = new HashSet<>(user1Scores.keySet());
|
||||
commonProducts.retainAll(user2Scores.keySet());
|
||||
|
||||
if (commonProducts.isEmpty()) {
|
||||
return BigDecimal.ZERO;
|
||||
}
|
||||
|
||||
BigDecimal dotProduct = BigDecimal.ZERO;
|
||||
BigDecimal norm1 = BigDecimal.ZERO;
|
||||
BigDecimal norm2 = BigDecimal.ZERO;
|
||||
|
||||
for (Long productId : commonProducts) {
|
||||
BigDecimal score1 = user1Scores.get(productId);
|
||||
BigDecimal score2 = user2Scores.get(productId);
|
||||
dotProduct = dotProduct.add(score1.multiply(score2));
|
||||
norm1 = norm1.add(score1.multiply(score1));
|
||||
norm2 = norm2.add(score2.multiply(score2));
|
||||
}
|
||||
|
||||
if (norm1.compareTo(BigDecimal.ZERO) == 0 || norm2.compareTo(BigDecimal.ZERO) == 0) {
|
||||
return BigDecimal.ZERO;
|
||||
}
|
||||
|
||||
BigDecimal denominator = BigDecimal.valueOf(Math.sqrt(norm1.doubleValue() * norm2.doubleValue()));
|
||||
return dotProduct.divide(denominator, 6, RoundingMode.HALF_UP);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取推荐商品列表
|
||||
*/
|
||||
public List<Product> getRecommendations(Long userId, int limit) {
|
||||
// 获取用户已购买的商品
|
||||
List<Long> purchasedProductIds = userBehaviorMapper.selectProductIdsByUserId(userId);
|
||||
Set<Long> purchasedSet = new HashSet<>(purchasedProductIds);
|
||||
|
||||
// 获取相似用户
|
||||
List<UserSimilarity> similarUsers = userSimilarityMapper.selectSimilarUsers(userId, 10);
|
||||
|
||||
// 用于存储已推荐的商品ID,避免重复
|
||||
Set<Long> recommendedProductIds = new HashSet<>();
|
||||
List<Product> recommendations = new ArrayList<>();
|
||||
|
||||
if (similarUsers.isEmpty()) {
|
||||
// 冷启动:返回热门商品(排除已购买的)
|
||||
List<Product> hotProducts = productMapper.selectHotProducts(limit * 2); // 多取一些,避免过滤后不足
|
||||
for (Product product : hotProducts) {
|
||||
if (recommendations.size() >= limit) {
|
||||
break;
|
||||
}
|
||||
if (!purchasedSet.contains(product.getId())
|
||||
&& product.getStatus() == 1
|
||||
&& !recommendedProductIds.contains(product.getId())) {
|
||||
recommendations.add(product);
|
||||
recommendedProductIds.add(product.getId());
|
||||
}
|
||||
}
|
||||
return recommendations;
|
||||
}
|
||||
|
||||
// 计算推荐分数
|
||||
Map<Long, BigDecimal> productScores = new HashMap<>();
|
||||
|
||||
for (UserSimilarity similarity : similarUsers) {
|
||||
Long similarUserId = similarity.getUser1Id().equals(userId) ?
|
||||
similarity.getUser2Id() : similarity.getUser1Id();
|
||||
|
||||
List<UserBehavior> behaviors = userBehaviorMapper.selectByUserId(similarUserId);
|
||||
for (UserBehavior behavior : behaviors) {
|
||||
if (!purchasedSet.contains(behavior.getProductId())) {
|
||||
BigDecimal score = calculateBehaviorScore(behavior)
|
||||
.multiply(similarity.getSimilarityScore());
|
||||
productScores.merge(behavior.getProductId(), score, BigDecimal::add);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 按分数排序,获取Top N
|
||||
List<Map.Entry<Long, BigDecimal>> sorted = productScores.entrySet().stream()
|
||||
.sorted((e1, e2) -> e2.getValue().compareTo(e1.getValue()))
|
||||
.limit(limit)
|
||||
.collect(Collectors.toList());
|
||||
|
||||
// 添加协同过滤推荐的商品
|
||||
for (Map.Entry<Long, BigDecimal> entry : sorted) {
|
||||
Product product = productMapper.selectById(entry.getKey());
|
||||
if (product != null && product.getStatus() == 1) {
|
||||
recommendations.add(product);
|
||||
recommendedProductIds.add(product.getId());
|
||||
}
|
||||
}
|
||||
|
||||
// 如果推荐数量不足,补充热门商品(排除已购买和已推荐的)
|
||||
if (recommendations.size() < limit) {
|
||||
int needCount = limit - recommendations.size();
|
||||
List<Product> hotProducts = productMapper.selectHotProducts(needCount * 2); // 多取一些,避免过滤后不足
|
||||
for (Product product : hotProducts) {
|
||||
if (recommendations.size() >= limit) {
|
||||
break;
|
||||
}
|
||||
if (!purchasedSet.contains(product.getId())
|
||||
&& product.getStatus() == 1
|
||||
&& !recommendedProductIds.contains(product.getId())) {
|
||||
recommendations.add(product);
|
||||
recommendedProductIds.add(product.getId());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return recommendations;
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新用户偏好(购买后)
|
||||
*/
|
||||
public void updateUserPreference(Long userId, Long productId) {
|
||||
if (userId == null || productId == null) {
|
||||
return; // 参数无效,不记录
|
||||
}
|
||||
|
||||
try {
|
||||
// 记录购买行为
|
||||
UserBehavior behavior = userBehaviorMapper.selectByUserAndProduct(userId, productId, "PURCHASE");
|
||||
if (behavior == null) {
|
||||
behavior = new UserBehavior();
|
||||
behavior.setUserId(userId);
|
||||
behavior.setProductId(productId);
|
||||
behavior.setBehaviorType("PURCHASE");
|
||||
behavior.setScore(new BigDecimal("3.0"));
|
||||
int result = userBehaviorMapper.insert(behavior);
|
||||
if (result <= 0) {
|
||||
System.err.println("警告: 用户购买行为记录插入失败 - userId: " + userId + ", productId: " + productId);
|
||||
}
|
||||
}
|
||||
|
||||
// 重新计算相似度
|
||||
calculateUserSimilarity(userId);
|
||||
} catch (Exception e) {
|
||||
// 记录行为失败不应该影响主流程,只打印错误日志
|
||||
System.err.println("记录用户购买行为失败: " + e.getMessage());
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 记录浏览行为
|
||||
*/
|
||||
public void recordView(Long userId, Long productId) {
|
||||
if (userId == null || productId == null) {
|
||||
return; // 参数无效,不记录
|
||||
}
|
||||
|
||||
try {
|
||||
UserBehavior behavior = userBehaviorMapper.selectByUserAndProduct(userId, productId, "VIEW");
|
||||
if (behavior == null) {
|
||||
behavior = new UserBehavior();
|
||||
behavior.setUserId(userId);
|
||||
behavior.setProductId(productId);
|
||||
behavior.setBehaviorType("VIEW");
|
||||
behavior.setScore(new BigDecimal("1.0"));
|
||||
int result = userBehaviorMapper.insert(behavior);
|
||||
if (result <= 0) {
|
||||
System.err.println("警告: 用户行为记录插入失败 - userId: " + userId + ", productId: " + productId);
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
// 记录行为失败不应该影响主流程,只打印错误日志
|
||||
System.err.println("记录用户浏览行为失败: " + e.getMessage());
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 记录评分行为
|
||||
*/
|
||||
public void recordRating(Long userId, Long productId, BigDecimal rating) {
|
||||
if (userId == null || productId == null || rating == null) {
|
||||
return; // 参数无效,不记录
|
||||
}
|
||||
|
||||
try {
|
||||
// 查找是否已有该商品的评分记录
|
||||
UserBehavior behavior = userBehaviorMapper.selectByUserAndProduct(userId, productId, "RATING");
|
||||
if (behavior == null) {
|
||||
// 新建评分记录
|
||||
behavior = new UserBehavior();
|
||||
behavior.setUserId(userId);
|
||||
behavior.setProductId(productId);
|
||||
behavior.setBehaviorType("RATING");
|
||||
behavior.setScore(rating);
|
||||
int result = userBehaviorMapper.insert(behavior);
|
||||
if (result <= 0) {
|
||||
System.err.println("警告: 用户评分行为记录插入失败 - userId: " + userId + ", productId: " + productId);
|
||||
}
|
||||
} else {
|
||||
// 更新已有评分记录
|
||||
behavior.setScore(rating);
|
||||
int result = userBehaviorMapper.update(behavior);
|
||||
if (result <= 0) {
|
||||
System.err.println("警告: 用户评分行为记录更新失败 - userId: " + userId + ", productId: " + productId);
|
||||
}
|
||||
}
|
||||
|
||||
// 重新计算相似度
|
||||
calculateUserSimilarity(userId);
|
||||
} catch (Exception e) {
|
||||
// 记录行为失败不应该影响主流程,只打印错误日志
|
||||
System.err.println("记录用户评分行为失败: " + e.getMessage());
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
package com.nlshop.service.merchant;
|
||||
|
||||
import com.nlshop.entity.Announcement;
|
||||
import com.nlshop.mapper.AnnouncementMapper;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Service
|
||||
public class AnnouncementService {
|
||||
|
||||
@Autowired
|
||||
private AnnouncementMapper announcementMapper;
|
||||
|
||||
public Announcement createAnnouncement(Long merchantId, Announcement announcement) {
|
||||
announcement.setMerchantId(merchantId);
|
||||
announcement.setStatus(1); // 默认发布
|
||||
announcementMapper.insert(announcement);
|
||||
return announcement;
|
||||
}
|
||||
|
||||
public List<Announcement> getAnnouncements(Long merchantId) {
|
||||
return announcementMapper.selectByMerchantId(merchantId);
|
||||
}
|
||||
|
||||
public Announcement getAnnouncement(Long id) {
|
||||
return announcementMapper.selectById(id);
|
||||
}
|
||||
|
||||
public void updateAnnouncement(Announcement announcement) {
|
||||
announcementMapper.update(announcement);
|
||||
}
|
||||
|
||||
public void deleteAnnouncement(Long id) {
|
||||
announcementMapper.delete(id);
|
||||
}
|
||||
|
||||
public List<Announcement> getPublishedAnnouncements() {
|
||||
return announcementMapper.selectPublished();
|
||||
}
|
||||
}
|
||||
109
src/main/java/com/nlshop/service/merchant/InventoryService.java
Normal file
109
src/main/java/com/nlshop/service/merchant/InventoryService.java
Normal file
@@ -0,0 +1,109 @@
|
||||
package com.nlshop.service.merchant;
|
||||
|
||||
import com.nlshop.entity.Inventory;
|
||||
import com.nlshop.entity.InventoryLog;
|
||||
import com.nlshop.exception.BusinessException;
|
||||
import com.nlshop.mapper.InventoryLogMapper;
|
||||
import com.nlshop.mapper.InventoryMapper;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
|
||||
@Service
|
||||
public class InventoryService {
|
||||
|
||||
@Autowired
|
||||
private InventoryMapper inventoryMapper;
|
||||
|
||||
@Autowired
|
||||
private InventoryLogMapper inventoryLogMapper;
|
||||
|
||||
public List<Inventory> getInventory(Long merchantId) {
|
||||
return inventoryMapper.selectByMerchantId(merchantId);
|
||||
}
|
||||
|
||||
public Inventory getInventoryById(Long inventoryId) {
|
||||
return inventoryMapper.selectById(inventoryId);
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public Inventory addInventory(Long merchantId, Inventory inventory) {
|
||||
// 检查是否已存在
|
||||
Inventory existing = inventoryMapper.selectByMerchantAndMaterial(
|
||||
merchantId, inventory.getMaterialName());
|
||||
|
||||
if (existing != null) {
|
||||
// 更新数量
|
||||
existing.setQuantity(existing.getQuantity().add(inventory.getQuantity()));
|
||||
existing.setLastInTime(LocalDateTime.now());
|
||||
inventoryMapper.update(existing);
|
||||
return existing;
|
||||
} else {
|
||||
// 新增
|
||||
inventory.setMerchantId(merchantId);
|
||||
inventory.setLastInTime(LocalDateTime.now());
|
||||
inventoryMapper.insert(inventory);
|
||||
return inventory;
|
||||
}
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public void stockIn(Long inventoryId, BigDecimal quantity, String operator) {
|
||||
Inventory inventory = inventoryMapper.selectById(inventoryId);
|
||||
if (inventory == null) {
|
||||
throw new BusinessException(404, "库存记录不存在");
|
||||
}
|
||||
|
||||
inventory.setQuantity(inventory.getQuantity().add(quantity));
|
||||
inventory.setLastInTime(LocalDateTime.now());
|
||||
inventoryMapper.update(inventory);
|
||||
|
||||
// 记录日志
|
||||
InventoryLog log = new InventoryLog();
|
||||
log.setInventoryId(inventoryId);
|
||||
log.setOperationType("IN");
|
||||
log.setQuantity(quantity);
|
||||
log.setOperator(operator);
|
||||
inventoryLogMapper.insert(log);
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public void stockOut(Long inventoryId, BigDecimal quantity, String operator) {
|
||||
Inventory inventory = inventoryMapper.selectById(inventoryId);
|
||||
if (inventory == null) {
|
||||
throw new BusinessException(404, "库存记录不存在");
|
||||
}
|
||||
|
||||
if (inventory.getQuantity().compareTo(quantity) < 0) {
|
||||
throw new BusinessException(400, "库存不足");
|
||||
}
|
||||
|
||||
inventory.setQuantity(inventory.getQuantity().subtract(quantity));
|
||||
inventory.setLastOutTime(LocalDateTime.now());
|
||||
inventoryMapper.update(inventory);
|
||||
|
||||
// 记录日志
|
||||
InventoryLog log = new InventoryLog();
|
||||
log.setInventoryId(inventoryId);
|
||||
log.setOperationType("OUT");
|
||||
log.setQuantity(quantity);
|
||||
log.setOperator(operator);
|
||||
inventoryLogMapper.insert(log);
|
||||
}
|
||||
|
||||
public List<Inventory> getLowStockAlerts(Long merchantId) {
|
||||
return inventoryMapper.selectLowStock(merchantId);
|
||||
}
|
||||
|
||||
public void updateInventory(Inventory inventory) {
|
||||
inventoryMapper.update(inventory);
|
||||
}
|
||||
|
||||
public void deleteInventory(Long inventoryId) {
|
||||
inventoryMapper.delete(inventoryId);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
package com.nlshop.service.merchant;
|
||||
|
||||
import com.nlshop.dto.request.MerchantRegisterRequest;
|
||||
import com.nlshop.entity.Merchant;
|
||||
import com.nlshop.exception.BusinessException;
|
||||
import com.nlshop.mapper.MerchantMapper;
|
||||
import com.nlshop.util.PasswordUtil;
|
||||
import org.springframework.beans.BeanUtils;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
@Service
|
||||
public class MerchantService {
|
||||
|
||||
@Autowired
|
||||
private MerchantMapper merchantMapper;
|
||||
|
||||
public Merchant register(MerchantRegisterRequest request) {
|
||||
if (merchantMapper.selectByUsername(request.getUsername()) != null) {
|
||||
throw new BusinessException(400, "用户名已存在");
|
||||
}
|
||||
|
||||
Merchant merchant = new Merchant();
|
||||
BeanUtils.copyProperties(request, merchant);
|
||||
merchant.setPassword(PasswordUtil.encrypt(request.getPassword()));
|
||||
merchantMapper.insert(merchant);
|
||||
return merchant;
|
||||
}
|
||||
|
||||
public Merchant login(String username, String password) {
|
||||
Merchant merchant = merchantMapper.selectByUsername(username);
|
||||
if (merchant == null || !PasswordUtil.verify(password, merchant.getPassword())) {
|
||||
throw new BusinessException(401, "用户名或密码错误");
|
||||
}
|
||||
return merchant;
|
||||
}
|
||||
|
||||
public Merchant getProfile(Long merchantId) {
|
||||
return merchantMapper.selectById(merchantId);
|
||||
}
|
||||
|
||||
public Merchant updateProfile(Long merchantId, Merchant merchant) {
|
||||
merchant.setId(merchantId);
|
||||
merchantMapper.update(merchant);
|
||||
return merchantMapper.selectById(merchantId);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
package com.nlshop.service.merchant;
|
||||
|
||||
import com.nlshop.entity.Message;
|
||||
import com.nlshop.mapper.MessageMapper;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
|
||||
@Service("merchantMessageService")
|
||||
public class MessageService {
|
||||
|
||||
@Autowired
|
||||
private MessageMapper messageMapper;
|
||||
|
||||
public List<Message> getMessages(Long merchantId) {
|
||||
return messageMapper.selectByMerchantId(merchantId);
|
||||
}
|
||||
|
||||
public Message getMessage(Long id) {
|
||||
return messageMapper.selectById(id);
|
||||
}
|
||||
|
||||
public void replyMessage(Long messageId, String reply) {
|
||||
Message message = messageMapper.selectById(messageId);
|
||||
if (message != null) {
|
||||
message.setReply(reply);
|
||||
message.setStatus(1);
|
||||
message.setReplyTime(LocalDateTime.now());
|
||||
messageMapper.update(message);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,126 @@
|
||||
package com.nlshop.service.merchant;
|
||||
|
||||
import com.github.pagehelper.PageHelper;
|
||||
import com.github.pagehelper.PageInfo;
|
||||
import com.nlshop.dto.response.OrderDetailResponse;
|
||||
import com.nlshop.dto.response.PageResponse;
|
||||
import com.nlshop.entity.Order;
|
||||
import com.nlshop.entity.OrderItem;
|
||||
import com.nlshop.entity.OrderTrack;
|
||||
import com.nlshop.exception.BusinessException;
|
||||
import com.nlshop.entity.Product;
|
||||
import com.nlshop.mapper.OrderItemMapper;
|
||||
import com.nlshop.mapper.OrderMapper;
|
||||
import com.nlshop.mapper.OrderTrackMapper;
|
||||
import com.nlshop.mapper.ProductMapper;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
@Service
|
||||
public class OrderManageService {
|
||||
|
||||
@Autowired
|
||||
private OrderMapper orderMapper;
|
||||
|
||||
@Autowired
|
||||
private OrderTrackMapper orderTrackMapper;
|
||||
|
||||
@Autowired
|
||||
private OrderItemMapper orderItemMapper;
|
||||
|
||||
@Autowired
|
||||
private ProductMapper productMapper;
|
||||
|
||||
public PageResponse<Order> getOrders(Long merchantId, String status, Integer pageNum, Integer pageSize) {
|
||||
PageHelper.startPage(pageNum, pageSize);
|
||||
List<Order> orders = orderMapper.selectByMerchantId(merchantId, status);
|
||||
PageInfo<Order> pageInfo = new PageInfo<>(orders);
|
||||
return PageResponse.of(orders, pageInfo.getTotal(), pageNum, pageSize);
|
||||
}
|
||||
|
||||
public OrderDetailResponse getOrderDetail(Long orderId, Long merchantId) {
|
||||
Order order = orderMapper.selectById(orderId);
|
||||
if (order == null || !order.getMerchantId().equals(merchantId)) {
|
||||
throw new BusinessException(404, "订单不存在");
|
||||
}
|
||||
List<OrderItem> items = orderItemMapper.selectByOrderId(orderId);
|
||||
List<OrderTrack> tracks = orderTrackMapper.selectByOrderId(orderId);
|
||||
|
||||
// 获取商品图片信息
|
||||
Map<Long, String> productImages = new HashMap<>();
|
||||
for (OrderItem item : items) {
|
||||
Product product = productMapper.selectById(item.getProductId());
|
||||
if (product != null) {
|
||||
productImages.put(item.getProductId(), product.getImage());
|
||||
}
|
||||
}
|
||||
|
||||
return OrderDetailResponse.of(order, items, tracks, productImages);
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public void acceptOrder(Long orderId, Long merchantId) {
|
||||
Order order = orderMapper.selectById(orderId);
|
||||
if (order == null || !order.getMerchantId().equals(merchantId)) {
|
||||
throw new BusinessException(404, "订单不存在");
|
||||
}
|
||||
if (!"MAKING".equals(order.getOrderStatus())) {
|
||||
throw new BusinessException(400, "订单状态不正确");
|
||||
}
|
||||
|
||||
// 订单状态已经是MAKING,这里可以添加其他业务逻辑
|
||||
addOrderTrack(orderId, "MAKING", "商家已接单,正在制作中");
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public void rejectOrder(Long orderId, Long merchantId) {
|
||||
Order order = orderMapper.selectById(orderId);
|
||||
if (order == null || !order.getMerchantId().equals(merchantId)) {
|
||||
throw new BusinessException(404, "订单不存在");
|
||||
}
|
||||
if (!"MAKING".equals(order.getOrderStatus())) {
|
||||
throw new BusinessException(400, "订单状态不正确");
|
||||
}
|
||||
|
||||
orderMapper.updateStatus(orderId, "CANCELLED");
|
||||
addOrderTrack(orderId, "CANCELLED", "商家拒单");
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public void updateOrderStatus(Long orderId, Long merchantId, String status) {
|
||||
Order order = orderMapper.selectById(orderId);
|
||||
if (order == null || !order.getMerchantId().equals(merchantId)) {
|
||||
throw new BusinessException(404, "订单不存在");
|
||||
}
|
||||
|
||||
String currentStatus = order.getOrderStatus();
|
||||
String description = "";
|
||||
|
||||
if ("READY".equals(status) && "MAKING".equals(currentStatus)) {
|
||||
description = "制作完成,等待取餐";
|
||||
} else if ("COMPLETED".equals(status) && "READY".equals(currentStatus)) {
|
||||
description = "订单已完成";
|
||||
order.setCompleteTime(LocalDateTime.now());
|
||||
orderMapper.update(order);
|
||||
} else {
|
||||
throw new BusinessException(400, "订单状态转换不正确");
|
||||
}
|
||||
|
||||
orderMapper.updateStatus(orderId, status);
|
||||
addOrderTrack(orderId, status, description);
|
||||
}
|
||||
|
||||
private void addOrderTrack(Long orderId, String status, String description) {
|
||||
OrderTrack track = new OrderTrack();
|
||||
track.setOrderId(orderId);
|
||||
track.setStatus(status);
|
||||
track.setDescription(description);
|
||||
orderTrackMapper.insert(track);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,161 @@
|
||||
package com.nlshop.service.merchant;
|
||||
|
||||
import com.github.pagehelper.PageHelper;
|
||||
import com.github.pagehelper.PageInfo;
|
||||
import com.nlshop.dto.response.PageResponse;
|
||||
import com.nlshop.entity.Product;
|
||||
import com.nlshop.entity.ProductCustom;
|
||||
import com.nlshop.entity.ProductSpec;
|
||||
import com.nlshop.entity.ProductTopping;
|
||||
import com.nlshop.mapper.ProductCustomMapper;
|
||||
import com.nlshop.mapper.ProductMapper;
|
||||
import com.nlshop.mapper.ProductSpecMapper;
|
||||
import com.nlshop.mapper.ProductToppingMapper;
|
||||
import com.nlshop.exception.BusinessException;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
@Service
|
||||
public class ProductManageService {
|
||||
|
||||
@Autowired
|
||||
private ProductMapper productMapper;
|
||||
|
||||
@Autowired
|
||||
private ProductSpecMapper productSpecMapper;
|
||||
|
||||
@Autowired
|
||||
private ProductToppingMapper productToppingMapper;
|
||||
|
||||
@Autowired
|
||||
private ProductCustomMapper productCustomMapper;
|
||||
|
||||
public PageResponse<Product> getProducts(Long merchantId, Integer pageNum, Integer pageSize) {
|
||||
PageHelper.startPage(pageNum, pageSize);
|
||||
List<Product> products = productMapper.selectByMerchantId(merchantId, null);
|
||||
PageInfo<Product> pageInfo = new PageInfo<>(products);
|
||||
return PageResponse.of(products, pageInfo.getTotal(), pageNum, pageSize);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取商品完整信息(包括规格、配料、定制选项)
|
||||
*/
|
||||
public Map<String, Object> getProduct(Long productId, Long merchantId) {
|
||||
Product product = productMapper.selectById(productId);
|
||||
if (product == null) {
|
||||
throw new BusinessException(404, "商品不存在");
|
||||
}
|
||||
|
||||
// 验证商品归属
|
||||
validateProductOwnership(productId, merchantId);
|
||||
|
||||
Map<String, Object> result = new HashMap<>();
|
||||
result.put("product", product);
|
||||
result.put("specs", productSpecMapper.selectByProductId(productId));
|
||||
result.put("toppings", productToppingMapper.selectByProductId(productId));
|
||||
result.put("customs", productCustomMapper.selectByProductId(productId));
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证商品是否属于指定商家
|
||||
*/
|
||||
public void validateProductOwnership(Long productId, Long merchantId) {
|
||||
Product product = productMapper.selectById(productId);
|
||||
if (product == null) {
|
||||
throw new BusinessException(404, "商品不存在");
|
||||
}
|
||||
if (!product.getMerchantId().equals(merchantId)) {
|
||||
throw new BusinessException(403, "无权操作此商品");
|
||||
}
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public Product createProduct(Long merchantId, Product product) {
|
||||
product.setMerchantId(merchantId);
|
||||
product.setStatus(1); // 默认上架
|
||||
productMapper.insert(product);
|
||||
return product;
|
||||
}
|
||||
|
||||
public Product updateProduct(Long productId, Long merchantId, Product product) {
|
||||
// 验证商品归属
|
||||
validateProductOwnership(productId, merchantId);
|
||||
|
||||
product.setId(productId);
|
||||
productMapper.update(product);
|
||||
return productMapper.selectById(productId);
|
||||
}
|
||||
|
||||
public void offlineProduct(Long productId, Long merchantId) {
|
||||
// 验证商品归属
|
||||
validateProductOwnership(productId, merchantId);
|
||||
productMapper.updateStatus(productId, 0);
|
||||
}
|
||||
|
||||
public void onlineProduct(Long productId, Long merchantId) {
|
||||
// 验证商品归属
|
||||
validateProductOwnership(productId, merchantId);
|
||||
productMapper.updateStatus(productId, 1);
|
||||
}
|
||||
|
||||
public List<ProductSpec> getSpecs(Long productId) {
|
||||
return productSpecMapper.selectByProductId(productId);
|
||||
}
|
||||
|
||||
public ProductSpec addSpec(Long productId, ProductSpec spec) {
|
||||
spec.setProductId(productId);
|
||||
productSpecMapper.insert(spec);
|
||||
return spec;
|
||||
}
|
||||
|
||||
public void updateSpec(ProductSpec spec) {
|
||||
productSpecMapper.update(spec);
|
||||
}
|
||||
|
||||
public void deleteSpec(Long specId) {
|
||||
productSpecMapper.delete(specId);
|
||||
}
|
||||
|
||||
public List<ProductTopping> getToppings(Long productId) {
|
||||
return productToppingMapper.selectByProductId(productId);
|
||||
}
|
||||
|
||||
public ProductTopping addTopping(Long productId, ProductTopping topping) {
|
||||
topping.setProductId(productId);
|
||||
productToppingMapper.insert(topping);
|
||||
return topping;
|
||||
}
|
||||
|
||||
public void updateTopping(ProductTopping topping) {
|
||||
productToppingMapper.update(topping);
|
||||
}
|
||||
|
||||
public void deleteTopping(Long toppingId) {
|
||||
productToppingMapper.delete(toppingId);
|
||||
}
|
||||
|
||||
public List<ProductCustom> getCustoms(Long productId) {
|
||||
return productCustomMapper.selectByProductId(productId);
|
||||
}
|
||||
|
||||
public ProductCustom addCustom(Long productId, ProductCustom custom) {
|
||||
custom.setProductId(productId);
|
||||
productCustomMapper.insert(custom);
|
||||
return custom;
|
||||
}
|
||||
|
||||
public void updateCustom(ProductCustom custom) {
|
||||
productCustomMapper.update(custom);
|
||||
}
|
||||
|
||||
public void deleteCustom(Long customId) {
|
||||
productCustomMapper.delete(customId);
|
||||
}
|
||||
}
|
||||
57
src/main/java/com/nlshop/service/merchant/ReviewService.java
Normal file
57
src/main/java/com/nlshop/service/merchant/ReviewService.java
Normal file
@@ -0,0 +1,57 @@
|
||||
package com.nlshop.service.merchant;
|
||||
|
||||
import com.nlshop.entity.Product;
|
||||
import com.nlshop.entity.Review;
|
||||
import com.nlshop.exception.BusinessException;
|
||||
import com.nlshop.mapper.ProductMapper;
|
||||
import com.nlshop.mapper.ReviewMapper;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
|
||||
@Service
|
||||
public class ReviewService {
|
||||
|
||||
@Autowired
|
||||
private ReviewMapper reviewMapper;
|
||||
|
||||
@Autowired
|
||||
private ProductMapper productMapper;
|
||||
|
||||
public List<Review> getReviews(Long merchantId) {
|
||||
return reviewMapper.selectByMerchantId(merchantId);
|
||||
}
|
||||
|
||||
public Review getReview(Long id) {
|
||||
return reviewMapper.selectById(id);
|
||||
}
|
||||
|
||||
public void replyReview(Long merchantId, Long reviewId, String reply) {
|
||||
Review review = reviewMapper.selectById(reviewId);
|
||||
if (review == null) {
|
||||
throw new BusinessException(404, "评价不存在");
|
||||
}
|
||||
|
||||
// 验证评价对应的商品是否属于当前商家
|
||||
Product product = productMapper.selectById(review.getProductId());
|
||||
if (product == null) {
|
||||
throw new BusinessException(404, "商品不存在");
|
||||
}
|
||||
if (!product.getMerchantId().equals(merchantId)) {
|
||||
throw new BusinessException(403, "无权回复该评价");
|
||||
}
|
||||
|
||||
review.setReply(reply);
|
||||
reviewMapper.update(review);
|
||||
}
|
||||
|
||||
public void handleComplaint(Long merchantId, Long reviewId, String reply) {
|
||||
replyReview(merchantId, reviewId, reply);
|
||||
}
|
||||
|
||||
public List<Review> getProductReviews(Long productId) {
|
||||
return reviewMapper.selectByProductId(productId);
|
||||
}
|
||||
}
|
||||
136
src/main/java/com/nlshop/service/merchant/StatisticsService.java
Normal file
136
src/main/java/com/nlshop/service/merchant/StatisticsService.java
Normal file
@@ -0,0 +1,136 @@
|
||||
package com.nlshop.service.merchant;
|
||||
|
||||
import com.nlshop.entity.Order;
|
||||
import com.nlshop.entity.OrderItem;
|
||||
import com.nlshop.entity.Product;
|
||||
import com.nlshop.mapper.OrderItemMapper;
|
||||
import com.nlshop.mapper.OrderMapper;
|
||||
import com.nlshop.mapper.ProductMapper;
|
||||
import com.nlshop.mapper.UserBehaviorMapper;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.math.RoundingMode;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@Service
|
||||
public class StatisticsService {
|
||||
|
||||
@Autowired
|
||||
private OrderMapper orderMapper;
|
||||
|
||||
@Autowired
|
||||
private OrderItemMapper orderItemMapper;
|
||||
|
||||
@Autowired
|
||||
private ProductMapper productMapper;
|
||||
|
||||
@Autowired
|
||||
private UserBehaviorMapper userBehaviorMapper;
|
||||
|
||||
public Map<String, Object> getSalesStatistics(Long merchantId, String period) {
|
||||
LocalDateTime startTime = getStartTime(period);
|
||||
List<Order> orders = orderMapper.selectByMerchantId(merchantId, null);
|
||||
|
||||
// 过滤时间范围和已完成订单
|
||||
orders = orders.stream()
|
||||
.filter(order -> order.getOrderStatus() != null &&
|
||||
order.getOrderStatus().equals("COMPLETED") &&
|
||||
order.getCompleteTime() != null &&
|
||||
order.getCompleteTime().isAfter(startTime))
|
||||
.collect(Collectors.toList());
|
||||
|
||||
BigDecimal totalAmount = orders.stream()
|
||||
.map(Order::getTotalAmount)
|
||||
.reduce(BigDecimal.ZERO, BigDecimal::add);
|
||||
|
||||
BigDecimal avgOrderAmount = orders.size() > 0 ?
|
||||
totalAmount.divide(new BigDecimal(orders.size()), 2, RoundingMode.HALF_UP) :
|
||||
BigDecimal.ZERO;
|
||||
|
||||
Map<String, Object> result = new HashMap<>();
|
||||
result.put("totalOrders", orders.size());
|
||||
result.put("totalAmount", totalAmount);
|
||||
result.put("avgOrderAmount", avgOrderAmount);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public Map<String, Object> getRevenueStatistics(Long merchantId, String period) {
|
||||
Map<String, Object> salesStats = getSalesStatistics(merchantId, period);
|
||||
BigDecimal totalRevenue = (BigDecimal) salesStats.get("totalAmount");
|
||||
|
||||
// 简化处理:假设成本为收入的70%(实际应该从库存等计算)
|
||||
BigDecimal totalCost = totalRevenue.multiply(new BigDecimal("0.7"));
|
||||
BigDecimal netProfit = totalRevenue.subtract(totalCost);
|
||||
|
||||
Map<String, Object> result = new HashMap<>();
|
||||
result.put("totalRevenue", totalRevenue);
|
||||
result.put("totalCost", totalCost);
|
||||
result.put("netProfit", netProfit);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public List<Map<String, Object>> getHotProducts(Long merchantId, int limit) {
|
||||
List<Order> orders = orderMapper.selectByMerchantId(merchantId, "COMPLETED");
|
||||
|
||||
// 统计商品销量和销售额
|
||||
Map<Long, Integer> productSales = new HashMap<>();
|
||||
Map<Long, BigDecimal> productAmounts = new HashMap<>();
|
||||
|
||||
for (Order order : orders) {
|
||||
List<OrderItem> items = orderItemMapper.selectByOrderId(order.getId());
|
||||
for (OrderItem item : items) {
|
||||
productSales.merge(item.getProductId(), item.getQuantity(), Integer::sum);
|
||||
BigDecimal itemAmount = item.getSubtotal();
|
||||
productAmounts.merge(item.getProductId(), itemAmount, BigDecimal::add);
|
||||
}
|
||||
}
|
||||
|
||||
// 按销量排序并获取商品信息
|
||||
return productSales.entrySet().stream()
|
||||
.sorted((e1, e2) -> e2.getValue().compareTo(e1.getValue()))
|
||||
.limit(limit)
|
||||
.map(entry -> {
|
||||
Long productId = entry.getKey();
|
||||
Product product = productMapper.selectById(productId);
|
||||
|
||||
Map<String, Object> map = new HashMap<>();
|
||||
map.put("productId", productId);
|
||||
map.put("productName", product != null ? product.getName() : "未知商品");
|
||||
map.put("salesCount", entry.getValue());
|
||||
map.put("salesAmount", productAmounts.getOrDefault(productId, BigDecimal.ZERO));
|
||||
return map;
|
||||
})
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
public List<Map<String, Object>> getActiveUsers(Long merchantId, int days) {
|
||||
return userBehaviorMapper.selectActiveUsers(merchantId, days);
|
||||
}
|
||||
|
||||
public List<Map<String, Object>> getStickyUsers(Long merchantId, int days, int minOrders) {
|
||||
return orderMapper.selectStickyUsers(merchantId, days, minOrders);
|
||||
}
|
||||
|
||||
private LocalDateTime getStartTime(String period) {
|
||||
LocalDateTime now = LocalDateTime.now();
|
||||
switch (period) {
|
||||
case "day":
|
||||
return now.minusDays(1);
|
||||
case "week":
|
||||
return now.minusWeeks(1);
|
||||
case "month":
|
||||
return now.minusMonths(1);
|
||||
default:
|
||||
return now.minusDays(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
package com.nlshop.service.merchant;
|
||||
|
||||
import com.github.pagehelper.PageHelper;
|
||||
import com.github.pagehelper.PageInfo;
|
||||
import com.nlshop.dto.response.PageResponse;
|
||||
import com.nlshop.entity.User;
|
||||
import com.nlshop.mapper.UserMapper;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Service
|
||||
public class UserManageService {
|
||||
|
||||
@Autowired
|
||||
private UserMapper userMapper;
|
||||
|
||||
public PageResponse<User> getUsers(Integer pageNum, Integer pageSize) {
|
||||
PageHelper.startPage(pageNum, pageSize);
|
||||
List<User> users = userMapper.selectAll();
|
||||
PageInfo<User> pageInfo = new PageInfo<>(users);
|
||||
return PageResponse.of(users, pageInfo.getTotal(), pageNum, pageSize);
|
||||
}
|
||||
}
|
||||
127
src/main/java/com/nlshop/service/user/CartService.java
Normal file
127
src/main/java/com/nlshop/service/user/CartService.java
Normal file
@@ -0,0 +1,127 @@
|
||||
package com.nlshop.service.user;
|
||||
|
||||
import com.nlshop.dto.request.CartAddRequest;
|
||||
import com.nlshop.entity.CartItem;
|
||||
import com.nlshop.mapper.CartMapper;
|
||||
import com.nlshop.service.algorithm.CollaborativeFilteringService;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Service
|
||||
public class CartService {
|
||||
|
||||
@Autowired
|
||||
private CartMapper cartMapper;
|
||||
|
||||
@Autowired
|
||||
private CollaborativeFilteringService cfService;
|
||||
|
||||
public CartItem addToCart(Long userId, CartAddRequest request) {
|
||||
// 检查是否已存在相同的商品配置
|
||||
CartItem existing = cartMapper.selectByUserAndProduct(
|
||||
userId, request.getProductId(), request.getSpecId(),
|
||||
request.getCustomSweetness(), request.getCustomIce(), request.getToppings()
|
||||
);
|
||||
|
||||
if (existing != null) {
|
||||
// 更新数量
|
||||
existing.setQuantity(existing.getQuantity() + request.getQuantity());
|
||||
cartMapper.updateQuantity(existing.getId(), existing.getQuantity());
|
||||
return cartMapper.selectById(existing.getId());
|
||||
} else {
|
||||
// 新增
|
||||
CartItem item = new CartItem();
|
||||
item.setUserId(userId);
|
||||
item.setProductId(request.getProductId());
|
||||
item.setSpecId(request.getSpecId());
|
||||
item.setQuantity(request.getQuantity());
|
||||
item.setCustomSweetness(request.getCustomSweetness());
|
||||
item.setCustomIce(request.getCustomIce());
|
||||
item.setToppings(request.getToppings());
|
||||
cartMapper.insert(item);
|
||||
|
||||
// 记录用户行为(加入购物车表示用户对商品有兴趣)
|
||||
// 如果用户已经浏览过该商品,recordView不会重复记录
|
||||
// 使用try-catch确保行为记录失败不影响购物车添加
|
||||
try {
|
||||
cfService.recordView(userId, request.getProductId());
|
||||
} catch (Exception e) {
|
||||
// 记录行为失败不应该影响购物车添加,只打印错误日志
|
||||
System.err.println("记录购物车行为失败: " + e.getMessage());
|
||||
}
|
||||
|
||||
return cartMapper.selectById(item.getId());
|
||||
}
|
||||
}
|
||||
|
||||
public List<CartItem> getCart(Long userId) {
|
||||
return cartMapper.selectByUserId(userId);
|
||||
}
|
||||
|
||||
public void updateQuantity(Long userId, Long cartId, Integer quantity) {
|
||||
// 验证购物车项是否属于当前用户
|
||||
CartItem item = cartMapper.selectById(cartId);
|
||||
if (item == null) {
|
||||
throw new RuntimeException("购物车项不存在");
|
||||
}
|
||||
if (!item.getUserId().equals(userId)) {
|
||||
throw new RuntimeException("无权修改该商品");
|
||||
}
|
||||
cartMapper.updateQuantity(cartId, quantity);
|
||||
}
|
||||
|
||||
public void deleteCartItem(Long userId, Long cartId) {
|
||||
// 验证购物车项是否属于当前用户
|
||||
CartItem item = cartMapper.selectById(cartId);
|
||||
if (item == null) {
|
||||
throw new RuntimeException("购物车项不存在");
|
||||
}
|
||||
if (!item.getUserId().equals(userId)) {
|
||||
throw new RuntimeException("无权删除该商品");
|
||||
}
|
||||
cartMapper.delete(cartId);
|
||||
}
|
||||
|
||||
public void deleteBatch(Long userId, List<Long> cartIds) {
|
||||
if (cartIds == null || cartIds.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
// 验证所有商品都属于当前用户
|
||||
List<CartItem> items = cartMapper.selectByUserId(userId);
|
||||
for (Long cartId : cartIds) {
|
||||
boolean belongsToUser = items.stream().anyMatch(item -> item.getId().equals(cartId));
|
||||
if (!belongsToUser) {
|
||||
throw new RuntimeException("无权删除该商品");
|
||||
}
|
||||
}
|
||||
// 执行批量删除
|
||||
cartMapper.deleteBatch(cartIds, userId);
|
||||
}
|
||||
|
||||
public void clearCart(Long userId) {
|
||||
cartMapper.deleteByUserId(userId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 从cookies同步购物车数据(登录后同步)
|
||||
* 相同商品会自动合并数量
|
||||
*/
|
||||
public void syncCartFromCookies(Long userId, List<CartAddRequest> items) {
|
||||
if (items == null || items.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 遍历每个商品,调用addToCart会自动合并相同商品
|
||||
for (CartAddRequest request : items) {
|
||||
try {
|
||||
addToCart(userId, request);
|
||||
} catch (Exception e) {
|
||||
// 单个商品同步失败不影响其他,只记录日志
|
||||
System.err.println("同步购物车商品失败: userId=" + userId +
|
||||
", productId=" + request.getProductId() + ", error=" + e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
31
src/main/java/com/nlshop/service/user/MessageService.java
Normal file
31
src/main/java/com/nlshop/service/user/MessageService.java
Normal file
@@ -0,0 +1,31 @@
|
||||
package com.nlshop.service.user;
|
||||
|
||||
import com.nlshop.entity.Message;
|
||||
import com.nlshop.mapper.MessageMapper;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
|
||||
@Service("userMessageService")
|
||||
public class MessageService {
|
||||
|
||||
@Autowired
|
||||
private MessageMapper messageMapper;
|
||||
|
||||
public Message createMessage(Long userId, Long merchantId, Long orderId, String content) {
|
||||
Message message = new Message();
|
||||
message.setUserId(userId);
|
||||
message.setMerchantId(merchantId);
|
||||
message.setOrderId(orderId);
|
||||
message.setContent(content);
|
||||
message.setStatus(0);
|
||||
messageMapper.insert(message);
|
||||
return message;
|
||||
}
|
||||
|
||||
public List<Message> getMessages(Long userId) {
|
||||
return messageMapper.selectByUserId(userId);
|
||||
}
|
||||
}
|
||||
239
src/main/java/com/nlshop/service/user/OrderService.java
Normal file
239
src/main/java/com/nlshop/service/user/OrderService.java
Normal file
@@ -0,0 +1,239 @@
|
||||
package com.nlshop.service.user;
|
||||
|
||||
import com.github.pagehelper.PageHelper;
|
||||
import com.github.pagehelper.PageInfo;
|
||||
import com.nlshop.dto.request.OrderCreateRequest;
|
||||
import com.nlshop.dto.request.PaymentRequest;
|
||||
import com.nlshop.dto.response.OrderDetailResponse;
|
||||
import com.nlshop.entity.*;
|
||||
import com.nlshop.exception.BusinessException;
|
||||
import com.alibaba.fastjson2.JSON;
|
||||
import com.alibaba.fastjson2.TypeReference;
|
||||
import com.nlshop.mapper.*;
|
||||
import com.nlshop.service.algorithm.CollaborativeFilteringService;
|
||||
import com.nlshop.util.OrderNoUtil;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
@Service
|
||||
public class OrderService {
|
||||
|
||||
@Autowired
|
||||
private OrderMapper orderMapper;
|
||||
|
||||
@Autowired
|
||||
private OrderItemMapper orderItemMapper;
|
||||
|
||||
@Autowired
|
||||
private OrderTrackMapper orderTrackMapper;
|
||||
|
||||
@Autowired
|
||||
private CartMapper cartMapper;
|
||||
|
||||
@Autowired
|
||||
private ProductMapper productMapper;
|
||||
|
||||
@Autowired
|
||||
private ProductSpecMapper productSpecMapper;
|
||||
|
||||
@Autowired
|
||||
private ProductToppingMapper productToppingMapper;
|
||||
|
||||
@Autowired
|
||||
private CollaborativeFilteringService cfService;
|
||||
|
||||
@Transactional
|
||||
public Order createOrder(Long userId, OrderCreateRequest request) {
|
||||
// 获取购物车商品
|
||||
List<CartItem> cartItems = cartMapper.selectByUserId(userId);
|
||||
if (cartItems.isEmpty()) {
|
||||
throw new BusinessException(400, "购物车为空");
|
||||
}
|
||||
|
||||
// 计算总金额
|
||||
BigDecimal totalAmount = BigDecimal.ZERO;
|
||||
Long merchantId = null;
|
||||
|
||||
for (CartItem item : cartItems) {
|
||||
Product product = productMapper.selectById(item.getProductId());
|
||||
if (product == null || product.getStatus() == 0) {
|
||||
throw new BusinessException(400, "商品不存在或已下架: " + product.getName());
|
||||
}
|
||||
|
||||
if (merchantId == null) {
|
||||
merchantId = product.getMerchantId();
|
||||
} else if (!merchantId.equals(product.getMerchantId())) {
|
||||
throw new BusinessException(400, "购物车中包含不同商家的商品");
|
||||
}
|
||||
|
||||
BigDecimal price = product.getBasePrice();
|
||||
if (item.getSpecId() != null) {
|
||||
ProductSpec spec = productSpecMapper.selectById(item.getSpecId());
|
||||
if (spec != null) {
|
||||
price = price.add(spec.getPriceAdjust());
|
||||
}
|
||||
}
|
||||
|
||||
// 计算配料价格
|
||||
if (item.getToppings() != null && !item.getToppings().isEmpty()) {
|
||||
try {
|
||||
List<Long> toppingIds = JSON.parseObject(item.getToppings(), new TypeReference<List<Long>>(){});
|
||||
if (toppingIds != null && !toppingIds.isEmpty()) {
|
||||
for (Long toppingId : toppingIds) {
|
||||
ProductTopping topping = productToppingMapper.selectById(toppingId);
|
||||
if (topping != null && topping.getPrice() != null) {
|
||||
price = price.add(topping.getPrice());
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
// 如果解析失败,忽略配料价格
|
||||
}
|
||||
}
|
||||
|
||||
totalAmount = totalAmount.add(price.multiply(new BigDecimal(item.getQuantity())));
|
||||
}
|
||||
|
||||
// 创建订单
|
||||
Order order = new Order();
|
||||
order.setOrderNo(OrderNoUtil.generate());
|
||||
order.setUserId(userId);
|
||||
order.setMerchantId(merchantId);
|
||||
order.setTotalAmount(totalAmount);
|
||||
order.setOrderStatus("PENDING_PAY");
|
||||
order.setPaymentStatus(0);
|
||||
order.setAddress(request.getAddress());
|
||||
order.setContactPhone(request.getContactPhone());
|
||||
orderMapper.insert(order);
|
||||
|
||||
// 创建订单明细
|
||||
for (CartItem item : cartItems) {
|
||||
Product product = productMapper.selectById(item.getProductId());
|
||||
BigDecimal price = product.getBasePrice();
|
||||
String specName = null;
|
||||
|
||||
if (item.getSpecId() != null) {
|
||||
ProductSpec spec = productSpecMapper.selectById(item.getSpecId());
|
||||
if (spec != null) {
|
||||
price = price.add(spec.getPriceAdjust());
|
||||
specName = spec.getSpecName();
|
||||
}
|
||||
}
|
||||
|
||||
// 计算配料价格
|
||||
if (item.getToppings() != null && !item.getToppings().isEmpty()) {
|
||||
try {
|
||||
List<Long> toppingIds = JSON.parseObject(item.getToppings(), new TypeReference<List<Long>>(){});
|
||||
if (toppingIds != null && !toppingIds.isEmpty()) {
|
||||
for (Long toppingId : toppingIds) {
|
||||
ProductTopping topping = productToppingMapper.selectById(toppingId);
|
||||
if (topping != null && topping.getPrice() != null) {
|
||||
price = price.add(topping.getPrice());
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
// 如果解析失败,忽略配料价格
|
||||
}
|
||||
}
|
||||
|
||||
OrderItem orderItem = new OrderItem();
|
||||
orderItem.setOrderId(order.getId());
|
||||
orderItem.setProductId(item.getProductId());
|
||||
orderItem.setProductName(product.getName());
|
||||
orderItem.setSpecName(specName);
|
||||
orderItem.setQuantity(item.getQuantity());
|
||||
orderItem.setPrice(price);
|
||||
orderItem.setCustomSweetness(item.getCustomSweetness());
|
||||
orderItem.setCustomIce(item.getCustomIce());
|
||||
orderItem.setToppings(item.getToppings());
|
||||
orderItem.setSubtotal(price.multiply(new BigDecimal(item.getQuantity())));
|
||||
orderItemMapper.insert(orderItem);
|
||||
}
|
||||
|
||||
// 记录订单跟踪
|
||||
addOrderTrack(order.getId(), "PENDING_PAY", "订单已创建,等待支付");
|
||||
|
||||
// 清空购物车
|
||||
cartMapper.deleteByUserId(userId);
|
||||
|
||||
return orderMapper.selectById(order.getId());
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public void payOrder(Long userId, Long orderId, PaymentRequest request) {
|
||||
Order order = orderMapper.selectById(orderId);
|
||||
if (order == null) {
|
||||
throw new BusinessException(404, "订单不存在");
|
||||
}
|
||||
if (!order.getUserId().equals(userId)) {
|
||||
throw new BusinessException(403, "无权操作该订单");
|
||||
}
|
||||
if (!"PENDING_PAY".equals(order.getOrderStatus())) {
|
||||
throw new BusinessException(400, "订单状态不正确");
|
||||
}
|
||||
|
||||
// 更新支付信息
|
||||
orderMapper.updatePayment(orderId, 1, request.getPaymentMethod());
|
||||
orderMapper.updateStatus(orderId, "MAKING");
|
||||
|
||||
// 记录订单跟踪
|
||||
addOrderTrack(orderId, "MAKING", "支付成功,商家开始制作");
|
||||
|
||||
// 更新用户偏好(协同过滤)
|
||||
List<OrderItem> items = orderItemMapper.selectByOrderId(orderId);
|
||||
for (OrderItem item : items) {
|
||||
cfService.updateUserPreference(order.getUserId(), item.getProductId());
|
||||
}
|
||||
}
|
||||
|
||||
public List<Order> getOrders(Long userId, String status) {
|
||||
return orderMapper.selectByUserId(userId, status);
|
||||
}
|
||||
|
||||
public OrderDetailResponse getOrderDetail(Long orderId, Long userId) {
|
||||
Order order = orderMapper.selectById(orderId);
|
||||
if (order == null || !order.getUserId().equals(userId)) {
|
||||
throw new BusinessException(404, "订单不存在");
|
||||
}
|
||||
List<OrderItem> items = orderItemMapper.selectByOrderId(orderId);
|
||||
List<OrderTrack> tracks = orderTrackMapper.selectByOrderId(orderId);
|
||||
|
||||
// 获取商品图片信息
|
||||
Map<Long, String> productImages = new java.util.HashMap<>();
|
||||
for (OrderItem item : items) {
|
||||
Product product = productMapper.selectById(item.getProductId());
|
||||
if (product != null) {
|
||||
productImages.put(item.getProductId(), product.getImage());
|
||||
}
|
||||
}
|
||||
|
||||
return OrderDetailResponse.of(order, items, tracks, productImages);
|
||||
}
|
||||
|
||||
public List<OrderTrack> getOrderTrack(Long userId, Long orderId) {
|
||||
// 验证订单是否属于当前用户
|
||||
Order order = orderMapper.selectById(orderId);
|
||||
if (order == null) {
|
||||
throw new BusinessException(404, "订单不存在");
|
||||
}
|
||||
if (!order.getUserId().equals(userId)) {
|
||||
throw new BusinessException(403, "无权查看该订单");
|
||||
}
|
||||
return orderTrackMapper.selectByOrderId(orderId);
|
||||
}
|
||||
|
||||
private void addOrderTrack(Long orderId, String status, String description) {
|
||||
OrderTrack track = new OrderTrack();
|
||||
track.setOrderId(orderId);
|
||||
track.setStatus(status);
|
||||
track.setDescription(description);
|
||||
orderTrackMapper.insert(track);
|
||||
}
|
||||
}
|
||||
77
src/main/java/com/nlshop/service/user/ProductService.java
Normal file
77
src/main/java/com/nlshop/service/user/ProductService.java
Normal file
@@ -0,0 +1,77 @@
|
||||
package com.nlshop.service.user;
|
||||
|
||||
import com.github.pagehelper.PageHelper;
|
||||
import com.github.pagehelper.PageInfo;
|
||||
import com.nlshop.dto.response.PageResponse;
|
||||
import com.nlshop.entity.Product;
|
||||
import com.nlshop.entity.ProductCustom;
|
||||
import com.nlshop.entity.ProductSpec;
|
||||
import com.nlshop.entity.ProductTopping;
|
||||
import com.nlshop.mapper.ProductCustomMapper;
|
||||
import com.nlshop.mapper.ProductMapper;
|
||||
import com.nlshop.mapper.ProductSpecMapper;
|
||||
import com.nlshop.mapper.ProductToppingMapper;
|
||||
import com.nlshop.service.algorithm.CollaborativeFilteringService;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
@Service
|
||||
public class ProductService {
|
||||
|
||||
@Autowired
|
||||
private ProductMapper productMapper;
|
||||
|
||||
@Autowired
|
||||
private ProductSpecMapper productSpecMapper;
|
||||
|
||||
@Autowired
|
||||
private ProductToppingMapper productToppingMapper;
|
||||
|
||||
@Autowired
|
||||
private ProductCustomMapper productCustomMapper;
|
||||
|
||||
@Autowired
|
||||
private CollaborativeFilteringService cfService;
|
||||
|
||||
public PageResponse<Product> getProducts(String category, Integer pageNum, Integer pageSize) {
|
||||
PageHelper.startPage(pageNum, pageSize);
|
||||
List<Product> products = productMapper.selectAll(category, 1); // 只查询上架商品
|
||||
PageInfo<Product> pageInfo = new PageInfo<>(products);
|
||||
return PageResponse.of(products, pageInfo.getTotal(), pageNum, pageSize);
|
||||
}
|
||||
|
||||
public Map<String, Object> getProductDetail(Long productId, Long userId) {
|
||||
Product product = productMapper.selectById(productId);
|
||||
if (product == null || product.getStatus() == 0) {
|
||||
throw new com.nlshop.exception.BusinessException(404, "商品不存在或已下架");
|
||||
}
|
||||
|
||||
// 记录浏览行为
|
||||
if (userId != null) {
|
||||
cfService.recordView(userId, productId);
|
||||
}
|
||||
|
||||
Map<String, Object> result = new HashMap<>();
|
||||
result.put("product", product);
|
||||
result.put("specs", productSpecMapper.selectByProductId(productId));
|
||||
result.put("toppings", productToppingMapper.selectByProductId(productId));
|
||||
|
||||
// 获取定制选项
|
||||
List<ProductCustom> customs = productCustomMapper.selectByProductId(productId);
|
||||
Map<String, List<ProductCustom>> customMap = new HashMap<>();
|
||||
for (ProductCustom custom : customs) {
|
||||
customMap.computeIfAbsent(custom.getOptionType(), k -> new java.util.ArrayList<>()).add(custom);
|
||||
}
|
||||
result.put("customs", customMap);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public List<Product> getHotProducts(int limit) {
|
||||
return productMapper.selectHotProducts(limit);
|
||||
}
|
||||
}
|
||||
19
src/main/java/com/nlshop/service/user/RecommendService.java
Normal file
19
src/main/java/com/nlshop/service/user/RecommendService.java
Normal file
@@ -0,0 +1,19 @@
|
||||
package com.nlshop.service.user;
|
||||
|
||||
import com.nlshop.entity.Product;
|
||||
import com.nlshop.service.algorithm.CollaborativeFilteringService;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Service
|
||||
public class RecommendService {
|
||||
|
||||
@Autowired
|
||||
private CollaborativeFilteringService cfService;
|
||||
|
||||
public List<Product> getRecommendations(Long userId, int limit) {
|
||||
return cfService.getRecommendations(userId, limit);
|
||||
}
|
||||
}
|
||||
113
src/main/java/com/nlshop/service/user/UserService.java
Normal file
113
src/main/java/com/nlshop/service/user/UserService.java
Normal file
@@ -0,0 +1,113 @@
|
||||
package com.nlshop.service.user;
|
||||
|
||||
import com.nlshop.dto.request.UserRegisterRequest;
|
||||
import com.nlshop.entity.User;
|
||||
import com.nlshop.entity.UserAddress;
|
||||
import com.nlshop.exception.BusinessException;
|
||||
import com.nlshop.mapper.UserAddressMapper;
|
||||
import com.nlshop.mapper.UserMapper;
|
||||
import com.nlshop.util.PasswordUtil;
|
||||
import org.springframework.beans.BeanUtils;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Service
|
||||
public class UserService {
|
||||
|
||||
@Autowired
|
||||
private UserMapper userMapper;
|
||||
|
||||
@Autowired
|
||||
private UserAddressMapper userAddressMapper;
|
||||
|
||||
public User register(UserRegisterRequest request) {
|
||||
// 检查用户名是否已存在
|
||||
if (userMapper.selectByUsername(request.getUsername()) != null) {
|
||||
throw new BusinessException(400, "用户名已存在");
|
||||
}
|
||||
|
||||
User user = new User();
|
||||
BeanUtils.copyProperties(request, user);
|
||||
user.setPassword(PasswordUtil.encrypt(request.getPassword()));
|
||||
userMapper.insert(user);
|
||||
return user;
|
||||
}
|
||||
|
||||
public User login(String username, String password) {
|
||||
User user = userMapper.selectByUsername(username);
|
||||
if (user == null || !PasswordUtil.verify(password, user.getPassword())) {
|
||||
throw new BusinessException(401, "用户名或密码错误");
|
||||
}
|
||||
return user;
|
||||
}
|
||||
|
||||
public User getProfile(Long userId) {
|
||||
return userMapper.selectById(userId);
|
||||
}
|
||||
|
||||
public User updateProfile(Long userId, User user) {
|
||||
user.setId(userId);
|
||||
userMapper.update(user);
|
||||
return userMapper.selectById(userId);
|
||||
}
|
||||
|
||||
public List<UserAddress> getAddresses(Long userId) {
|
||||
return userAddressMapper.selectByUserId(userId);
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public UserAddress addAddress(Long userId, UserAddress address) {
|
||||
address.setUserId(userId);
|
||||
// 如果设置为默认地址,取消其他默认地址
|
||||
if (address.getIsDefault() != null && address.getIsDefault() == 1) {
|
||||
UserAddress defaultAddr = userAddressMapper.selectDefaultByUserId(userId);
|
||||
if (defaultAddr != null) {
|
||||
defaultAddr.setIsDefault(0);
|
||||
userAddressMapper.update(defaultAddr);
|
||||
}
|
||||
}
|
||||
userAddressMapper.insert(address);
|
||||
return address;
|
||||
}
|
||||
|
||||
public UserAddress updateAddress(Long userId, Long addressId, UserAddress address) {
|
||||
// 验证地址是否属于当前用户
|
||||
UserAddress existing = userAddressMapper.selectById(addressId);
|
||||
if (existing == null) {
|
||||
throw new BusinessException(404, "地址不存在");
|
||||
}
|
||||
if (!existing.getUserId().equals(userId)) {
|
||||
throw new BusinessException(403, "无权修改该地址");
|
||||
}
|
||||
|
||||
address.setId(addressId);
|
||||
address.setUserId(userId);
|
||||
|
||||
// 如果设置为默认地址,取消其他默认地址
|
||||
if (address.getIsDefault() != null && address.getIsDefault() == 1) {
|
||||
UserAddress defaultAddr = userAddressMapper.selectDefaultByUserId(userId);
|
||||
if (defaultAddr != null && !defaultAddr.getId().equals(addressId)) {
|
||||
defaultAddr.setIsDefault(0);
|
||||
userAddressMapper.update(defaultAddr);
|
||||
}
|
||||
}
|
||||
|
||||
userAddressMapper.update(address);
|
||||
return userAddressMapper.selectById(addressId);
|
||||
}
|
||||
|
||||
public void deleteAddress(Long userId, Long addressId) {
|
||||
// 验证地址是否属于当前用户
|
||||
UserAddress existing = userAddressMapper.selectById(addressId);
|
||||
if (existing == null) {
|
||||
throw new BusinessException(404, "地址不存在");
|
||||
}
|
||||
if (!existing.getUserId().equals(userId)) {
|
||||
throw new BusinessException(403, "无权删除该地址");
|
||||
}
|
||||
userAddressMapper.delete(addressId);
|
||||
}
|
||||
}
|
||||
70
src/main/java/com/nlshop/util/FileUploadUtil.java
Normal file
70
src/main/java/com/nlshop/util/FileUploadUtil.java
Normal file
@@ -0,0 +1,70 @@
|
||||
package com.nlshop.util;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.UUID;
|
||||
|
||||
@Component
|
||||
public class FileUploadUtil {
|
||||
|
||||
@Value("${file.upload-path}")
|
||||
private String uploadPath;
|
||||
|
||||
public String uploadAvatar(MultipartFile file) throws IOException {
|
||||
return uploadFile(file, "avatar");
|
||||
}
|
||||
|
||||
public String uploadProductImage(MultipartFile file) throws IOException {
|
||||
return uploadFile(file, "product");
|
||||
}
|
||||
|
||||
private String uploadFile(MultipartFile file, String subDir) throws IOException {
|
||||
if (file == null || file.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// 创建目录
|
||||
String dir = uploadPath + File.separator + subDir;
|
||||
Path path = Paths.get(dir);
|
||||
if (!Files.exists(path)) {
|
||||
Files.createDirectories(path);
|
||||
}
|
||||
|
||||
// 生成文件名
|
||||
String originalFilename = file.getOriginalFilename();
|
||||
String extension = "";
|
||||
if (originalFilename != null && originalFilename.contains(".")) {
|
||||
extension = originalFilename.substring(originalFilename.lastIndexOf("."));
|
||||
}
|
||||
String filename = UUID.randomUUID().toString() + extension;
|
||||
|
||||
// 保存文件
|
||||
Path filePath = Paths.get(dir, filename);
|
||||
Files.write(filePath, file.getBytes());
|
||||
|
||||
// 返回相对路径
|
||||
return "/upload/" + subDir + "/" + filename;
|
||||
}
|
||||
|
||||
public boolean deleteFile(String filePath) {
|
||||
if (filePath == null || filePath.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
// 移除开头的 /upload/
|
||||
String relativePath = filePath.replace("/upload/", "");
|
||||
Path path = Paths.get(uploadPath, relativePath);
|
||||
return Files.deleteIfExists(path);
|
||||
} catch (IOException e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
16
src/main/java/com/nlshop/util/OrderNoUtil.java
Normal file
16
src/main/java/com/nlshop/util/OrderNoUtil.java
Normal file
@@ -0,0 +1,16 @@
|
||||
package com.nlshop.util;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
public class OrderNoUtil {
|
||||
private static final AtomicInteger counter = new AtomicInteger(0);
|
||||
private static final DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyyMMddHHmmss");
|
||||
|
||||
public static String generate() {
|
||||
String timestamp = LocalDateTime.now().format(formatter);
|
||||
int seq = counter.incrementAndGet() % 10000;
|
||||
return "ORD" + timestamp + String.format("%04d", seq);
|
||||
}
|
||||
}
|
||||
25
src/main/java/com/nlshop/util/PasswordUtil.java
Normal file
25
src/main/java/com/nlshop/util/PasswordUtil.java
Normal file
@@ -0,0 +1,25 @@
|
||||
package com.nlshop.util;
|
||||
|
||||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
|
||||
public class PasswordUtil {
|
||||
|
||||
public static String encrypt(String password) {
|
||||
try {
|
||||
MessageDigest md = MessageDigest.getInstance("MD5");
|
||||
byte[] bytes = md.digest(password.getBytes());
|
||||
StringBuilder sb = new StringBuilder();
|
||||
for (byte b : bytes) {
|
||||
sb.append(String.format("%02x", b));
|
||||
}
|
||||
return sb.toString();
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
throw new RuntimeException("密码加密失败", e);
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean verify(String password, String encryptedPassword) {
|
||||
return encrypt(password).equals(encryptedPassword);
|
||||
}
|
||||
}
|
||||
5
src/main/resources/application-dev.yml
Normal file
5
src/main/resources/application-dev.yml
Normal file
@@ -0,0 +1,5 @@
|
||||
spring:
|
||||
datasource:
|
||||
url: jdbc:mysql://localhost:3306/nl_shop?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai
|
||||
username: root
|
||||
password: root
|
||||
47
src/main/resources/application.yml
Normal file
47
src/main/resources/application.yml
Normal file
@@ -0,0 +1,47 @@
|
||||
server:
|
||||
port: 14001
|
||||
|
||||
spring:
|
||||
application:
|
||||
name: nl-shop
|
||||
datasource:
|
||||
driver-class-name: com.mysql.cj.jdbc.Driver
|
||||
url: jdbc:mysql://127.0.0.1:3306/z_wb_nc_shop?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true
|
||||
username: root
|
||||
password: root
|
||||
redis:
|
||||
host: 127.0.0.1
|
||||
port: 6379
|
||||
password: redis_pBjaRs
|
||||
timeout: 3000ms
|
||||
lettuce:
|
||||
pool:
|
||||
max-active: 8
|
||||
max-idle: 8
|
||||
min-idle: 0
|
||||
servlet:
|
||||
multipart:
|
||||
enabled: true
|
||||
max-file-size: 10MB
|
||||
max-request-size: 10MB
|
||||
thymeleaf:
|
||||
prefix: classpath:/templates/
|
||||
suffix: .html
|
||||
mode: HTML
|
||||
encoding: UTF-8
|
||||
cache: false
|
||||
|
||||
mybatis:
|
||||
mapper-locations: classpath:mapper/*.xml
|
||||
type-aliases-package: com.nlshop.entity
|
||||
configuration:
|
||||
map-underscore-to-camel-case: true
|
||||
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
|
||||
|
||||
pagehelper:
|
||||
helper-dialect: mysql
|
||||
reasonable: true
|
||||
support-methods-arguments: true
|
||||
|
||||
file:
|
||||
upload-path: ${user.dir}/src/main/resources/static/upload
|
||||
248
src/main/resources/db/schema.sql
Normal file
248
src/main/resources/db/schema.sql
Normal file
@@ -0,0 +1,248 @@
|
||||
-- 奶茶店管理系统数据库表结构
|
||||
|
||||
-- 用户表
|
||||
CREATE TABLE IF NOT EXISTS `user` (
|
||||
`id` BIGINT PRIMARY KEY AUTO_INCREMENT,
|
||||
`username` VARCHAR(50) UNIQUE NOT NULL COMMENT '用户名',
|
||||
`password` VARCHAR(255) NOT NULL COMMENT '密码(加密)',
|
||||
`name` VARCHAR(50) COMMENT '姓名',
|
||||
`gender` TINYINT COMMENT '性别:0-女,1-男',
|
||||
`phone` VARCHAR(20) COMMENT '手机号',
|
||||
`email` VARCHAR(100) COMMENT '邮箱',
|
||||
`avatar` VARCHAR(255) COMMENT '头像路径',
|
||||
`address` VARCHAR(255) COMMENT '默认地址',
|
||||
`create_time` DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
|
||||
`update_time` DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间'
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户表';
|
||||
|
||||
-- 用户地址表
|
||||
CREATE TABLE IF NOT EXISTS `user_address` (
|
||||
`id` BIGINT PRIMARY KEY AUTO_INCREMENT,
|
||||
`user_id` BIGINT NOT NULL COMMENT '用户ID',
|
||||
`address` VARCHAR(255) NOT NULL COMMENT '详细地址',
|
||||
`contact_name` VARCHAR(50) COMMENT '联系人姓名',
|
||||
`contact_phone` VARCHAR(20) COMMENT '联系人电话',
|
||||
`is_default` TINYINT DEFAULT 0 COMMENT '是否默认:0-否,1-是',
|
||||
`create_time` DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
FOREIGN KEY (`user_id`) REFERENCES `user`(`id`) ON DELETE CASCADE
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户地址表';
|
||||
|
||||
-- 商家表
|
||||
CREATE TABLE IF NOT EXISTS `merchant` (
|
||||
`id` BIGINT PRIMARY KEY AUTO_INCREMENT,
|
||||
`username` VARCHAR(50) UNIQUE NOT NULL COMMENT '用户名',
|
||||
`password` VARCHAR(255) NOT NULL COMMENT '密码(加密)',
|
||||
`store_name` VARCHAR(100) NOT NULL COMMENT '门店名称',
|
||||
`address` VARCHAR(255) COMMENT '门店地址',
|
||||
`phone` VARCHAR(20) COMMENT '联系电话',
|
||||
`email` VARCHAR(100) COMMENT '邮箱',
|
||||
`manager_name` VARCHAR(50) COMMENT '负责人姓名',
|
||||
`avatar` VARCHAR(255) COMMENT '头像路径',
|
||||
`create_time` DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
`update_time` DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='商家表';
|
||||
|
||||
-- 商品表
|
||||
CREATE TABLE IF NOT EXISTS `product` (
|
||||
`id` BIGINT PRIMARY KEY AUTO_INCREMENT,
|
||||
`merchant_id` BIGINT NOT NULL COMMENT '商家ID',
|
||||
`name` VARCHAR(100) NOT NULL COMMENT '商品名称',
|
||||
`category` VARCHAR(50) COMMENT '分类',
|
||||
`description` TEXT COMMENT '商品描述',
|
||||
`base_price` DECIMAL(10,2) NOT NULL COMMENT '基础价格',
|
||||
`image` VARCHAR(255) COMMENT '商品图片',
|
||||
`status` TINYINT DEFAULT 1 COMMENT '状态:0-下架,1-上架',
|
||||
`create_time` DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
FOREIGN KEY (`merchant_id`) REFERENCES `merchant`(`id`) ON DELETE CASCADE
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='商品表';
|
||||
|
||||
-- 商品规格表
|
||||
CREATE TABLE IF NOT EXISTS `product_spec` (
|
||||
`id` BIGINT PRIMARY KEY AUTO_INCREMENT,
|
||||
`product_id` BIGINT NOT NULL COMMENT '商品ID',
|
||||
`spec_name` VARCHAR(50) NOT NULL COMMENT '规格名称(大杯/中杯/小杯)',
|
||||
`price_adjust` DECIMAL(10,2) DEFAULT 0 COMMENT '价格调整(相对于基础价格)',
|
||||
FOREIGN KEY (`product_id`) REFERENCES `product`(`id`) ON DELETE CASCADE
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='商品规格表';
|
||||
|
||||
-- 配料表
|
||||
CREATE TABLE IF NOT EXISTS `product_topping` (
|
||||
`id` BIGINT PRIMARY KEY AUTO_INCREMENT,
|
||||
`product_id` BIGINT NOT NULL COMMENT '商品ID',
|
||||
`topping_name` VARCHAR(50) NOT NULL COMMENT '配料名称',
|
||||
`price` DECIMAL(10,2) DEFAULT 0 COMMENT '配料价格',
|
||||
FOREIGN KEY (`product_id`) REFERENCES `product`(`id`) ON DELETE CASCADE
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='配料表';
|
||||
|
||||
-- 商品定制选项表
|
||||
CREATE TABLE IF NOT EXISTS `product_custom` (
|
||||
`id` BIGINT PRIMARY KEY AUTO_INCREMENT,
|
||||
`product_id` BIGINT NOT NULL COMMENT '商品ID',
|
||||
`option_type` VARCHAR(20) NOT NULL COMMENT '选项类型:sweetness-甜度,ice-冰度',
|
||||
`option_value` VARCHAR(50) NOT NULL COMMENT '选项值',
|
||||
`price_adjust` DECIMAL(10,2) DEFAULT 0 COMMENT '价格调整',
|
||||
FOREIGN KEY (`product_id`) REFERENCES `product`(`id`) ON DELETE CASCADE
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='商品定制选项表';
|
||||
|
||||
-- 购物车表
|
||||
CREATE TABLE IF NOT EXISTS `cart` (
|
||||
`id` BIGINT PRIMARY KEY AUTO_INCREMENT,
|
||||
`user_id` BIGINT NOT NULL COMMENT '用户ID',
|
||||
`product_id` BIGINT NOT NULL COMMENT '商品ID',
|
||||
`spec_id` BIGINT COMMENT '规格ID',
|
||||
`quantity` INT DEFAULT 1 COMMENT '数量',
|
||||
`custom_sweetness` VARCHAR(20) COMMENT '甜度定制',
|
||||
`custom_ice` VARCHAR(20) COMMENT '冰度定制',
|
||||
`toppings` TEXT COMMENT '配料JSON',
|
||||
`create_time` DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
FOREIGN KEY (`user_id`) REFERENCES `user`(`id`) ON DELETE CASCADE,
|
||||
FOREIGN KEY (`product_id`) REFERENCES `product`(`id`) ON DELETE CASCADE
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='购物车表';
|
||||
|
||||
-- 订单表
|
||||
CREATE TABLE IF NOT EXISTS `order` (
|
||||
`id` BIGINT PRIMARY KEY AUTO_INCREMENT,
|
||||
`order_no` VARCHAR(50) UNIQUE NOT NULL COMMENT '订单号',
|
||||
`user_id` BIGINT NOT NULL COMMENT '用户ID',
|
||||
`merchant_id` BIGINT NOT NULL COMMENT '商家ID',
|
||||
`total_amount` DECIMAL(10,2) NOT NULL COMMENT '订单总金额',
|
||||
`payment_method` VARCHAR(20) COMMENT '支付方式:wechat/alipay',
|
||||
`payment_status` TINYINT DEFAULT 0 COMMENT '支付状态:0-未支付,1-已支付',
|
||||
`order_status` VARCHAR(20) DEFAULT 'PENDING_PAY' COMMENT '订单状态:PENDING_PAY-待支付,MAKING-制作中,READY-待取餐,COMPLETED-已完成,CANCELLED-已取消',
|
||||
`address` VARCHAR(255) NOT NULL COMMENT '收货地址',
|
||||
`contact_phone` VARCHAR(20) NOT NULL COMMENT '联系电话',
|
||||
`create_time` DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
|
||||
`pay_time` DATETIME COMMENT '支付时间',
|
||||
`complete_time` DATETIME COMMENT '完成时间',
|
||||
FOREIGN KEY (`user_id`) REFERENCES `user`(`id`),
|
||||
FOREIGN KEY (`merchant_id`) REFERENCES `merchant`(`id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='订单表';
|
||||
|
||||
-- 订单明细表
|
||||
CREATE TABLE IF NOT EXISTS `order_item` (
|
||||
`id` BIGINT PRIMARY KEY AUTO_INCREMENT,
|
||||
`order_id` BIGINT NOT NULL COMMENT '订单ID',
|
||||
`product_id` BIGINT NOT NULL COMMENT '商品ID',
|
||||
`product_name` VARCHAR(100) NOT NULL COMMENT '商品名称',
|
||||
`spec_name` VARCHAR(50) COMMENT '规格名称',
|
||||
`quantity` INT NOT NULL COMMENT '数量',
|
||||
`price` DECIMAL(10,2) NOT NULL COMMENT '单价',
|
||||
`custom_sweetness` VARCHAR(20) COMMENT '甜度',
|
||||
`custom_ice` VARCHAR(20) COMMENT '冰度',
|
||||
`toppings` TEXT COMMENT '配料JSON',
|
||||
`subtotal` DECIMAL(10,2) NOT NULL COMMENT '小计',
|
||||
FOREIGN KEY (`order_id`) REFERENCES `order`(`id`) ON DELETE CASCADE,
|
||||
FOREIGN KEY (`product_id`) REFERENCES `product`(`id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='订单明细表';
|
||||
|
||||
-- 订单跟踪表
|
||||
CREATE TABLE IF NOT EXISTS `order_track` (
|
||||
`id` BIGINT PRIMARY KEY AUTO_INCREMENT,
|
||||
`order_id` BIGINT NOT NULL COMMENT '订单ID',
|
||||
`status` VARCHAR(20) NOT NULL COMMENT '状态',
|
||||
`description` VARCHAR(255) COMMENT '描述',
|
||||
`create_time` DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
FOREIGN KEY (`order_id`) REFERENCES `order`(`id`) ON DELETE CASCADE
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='订单跟踪表';
|
||||
|
||||
-- 公告表
|
||||
CREATE TABLE IF NOT EXISTS `announcement` (
|
||||
`id` BIGINT PRIMARY KEY AUTO_INCREMENT,
|
||||
`merchant_id` BIGINT NOT NULL COMMENT '商家ID',
|
||||
`title` VARCHAR(200) NOT NULL COMMENT '标题',
|
||||
`content` TEXT COMMENT '内容',
|
||||
`type` VARCHAR(20) DEFAULT 'NOTICE' COMMENT '类型:ACTIVITY-活动,NOTICE-通知',
|
||||
`status` TINYINT DEFAULT 1 COMMENT '状态:0-下架,1-发布',
|
||||
`create_time` DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
FOREIGN KEY (`merchant_id`) REFERENCES `merchant`(`id`) ON DELETE CASCADE
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='公告表';
|
||||
|
||||
-- 留言反馈表
|
||||
CREATE TABLE IF NOT EXISTS `message` (
|
||||
`id` BIGINT PRIMARY KEY AUTO_INCREMENT,
|
||||
`user_id` BIGINT NOT NULL COMMENT '用户ID',
|
||||
`merchant_id` BIGINT NOT NULL COMMENT '商家ID',
|
||||
`order_id` BIGINT COMMENT '订单ID',
|
||||
`content` TEXT NOT NULL COMMENT '留言内容',
|
||||
`reply` TEXT COMMENT '回复内容',
|
||||
`status` TINYINT DEFAULT 0 COMMENT '状态:0-未回复,1-已回复',
|
||||
`create_time` DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
`reply_time` DATETIME COMMENT '回复时间',
|
||||
FOREIGN KEY (`user_id`) REFERENCES `user`(`id`),
|
||||
FOREIGN KEY (`merchant_id`) REFERENCES `merchant`(`id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='留言反馈表';
|
||||
|
||||
-- 评价表
|
||||
CREATE TABLE IF NOT EXISTS `review` (
|
||||
`id` BIGINT PRIMARY KEY AUTO_INCREMENT,
|
||||
`user_id` BIGINT NOT NULL COMMENT '用户ID',
|
||||
`order_id` BIGINT NOT NULL COMMENT '订单ID',
|
||||
`product_id` BIGINT NOT NULL COMMENT '商品ID',
|
||||
`rating` TINYINT NOT NULL COMMENT '评分:1-5',
|
||||
`content` TEXT COMMENT '评价内容',
|
||||
`reply` TEXT COMMENT '商家回复',
|
||||
`create_time` DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
FOREIGN KEY (`user_id`) REFERENCES `user`(`id`),
|
||||
FOREIGN KEY (`order_id`) REFERENCES `order`(`id`),
|
||||
FOREIGN KEY (`product_id`) REFERENCES `product`(`id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='评价表';
|
||||
|
||||
-- 库存表
|
||||
CREATE TABLE IF NOT EXISTS `inventory` (
|
||||
`id` BIGINT PRIMARY KEY AUTO_INCREMENT,
|
||||
`merchant_id` BIGINT NOT NULL COMMENT '商家ID',
|
||||
`material_name` VARCHAR(100) NOT NULL COMMENT '原料名称',
|
||||
`quantity` DECIMAL(10,2) NOT NULL COMMENT '数量',
|
||||
`unit` VARCHAR(20) COMMENT '单位',
|
||||
`min_threshold` DECIMAL(10,2) DEFAULT 0 COMMENT '最低库存阈值',
|
||||
`last_in_time` DATETIME COMMENT '最后入库时间',
|
||||
`last_out_time` DATETIME COMMENT '最后出库时间',
|
||||
FOREIGN KEY (`merchant_id`) REFERENCES `merchant`(`id`) ON DELETE CASCADE
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='库存表';
|
||||
|
||||
-- 库存操作日志表
|
||||
CREATE TABLE IF NOT EXISTS `inventory_log` (
|
||||
`id` BIGINT PRIMARY KEY AUTO_INCREMENT,
|
||||
`inventory_id` BIGINT NOT NULL COMMENT '库存ID',
|
||||
`operation_type` VARCHAR(20) NOT NULL COMMENT '操作类型:IN-入库,OUT-出库',
|
||||
`quantity` DECIMAL(10,2) NOT NULL COMMENT '操作数量',
|
||||
`operator` VARCHAR(50) COMMENT '操作人',
|
||||
`create_time` DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
FOREIGN KEY (`inventory_id`) REFERENCES `inventory`(`id`) ON DELETE CASCADE
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='库存操作日志表';
|
||||
|
||||
-- 用户行为表(用于协同过滤)
|
||||
CREATE TABLE IF NOT EXISTS `user_behavior` (
|
||||
`id` BIGINT PRIMARY KEY AUTO_INCREMENT,
|
||||
`user_id` BIGINT NOT NULL COMMENT '用户ID',
|
||||
`product_id` BIGINT NOT NULL COMMENT '商品ID',
|
||||
`behavior_type` VARCHAR(20) NOT NULL COMMENT '行为类型:VIEW-浏览,PURCHASE-购买,RATING-评分',
|
||||
`score` DECIMAL(5,2) DEFAULT 0 COMMENT '评分(0-5)',
|
||||
`create_time` DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
FOREIGN KEY (`user_id`) REFERENCES `user`(`id`) ON DELETE CASCADE,
|
||||
FOREIGN KEY (`product_id`) REFERENCES `product`(`id`) ON DELETE CASCADE
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户行为表';
|
||||
|
||||
-- 用户相似度缓存表(可选)
|
||||
CREATE TABLE IF NOT EXISTS `user_similarity` (
|
||||
`user1_id` BIGINT NOT NULL COMMENT '用户1ID',
|
||||
`user2_id` BIGINT NOT NULL COMMENT '用户2ID',
|
||||
`similarity_score` DECIMAL(10,6) NOT NULL COMMENT '相似度分数',
|
||||
`update_time` DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
PRIMARY KEY (`user1_id`, `user2_id`),
|
||||
FOREIGN KEY (`user1_id`) REFERENCES `user`(`id`) ON DELETE CASCADE,
|
||||
FOREIGN KEY (`user2_id`) REFERENCES `user`(`id`) ON DELETE CASCADE
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户相似度缓存表';
|
||||
|
||||
-- 创建索引
|
||||
CREATE INDEX idx_user_username ON `user`(`username`);
|
||||
CREATE INDEX idx_merchant_username ON `merchant`(`username`);
|
||||
CREATE INDEX idx_product_merchant ON `product`(`merchant_id`);
|
||||
CREATE INDEX idx_product_status ON `product`(`status`);
|
||||
CREATE INDEX idx_cart_user ON `cart`(`user_id`);
|
||||
CREATE INDEX idx_order_user ON `order`(`user_id`);
|
||||
CREATE INDEX idx_order_merchant ON `order`(`merchant_id`);
|
||||
CREATE INDEX idx_order_status ON `order`(`order_status`);
|
||||
CREATE INDEX idx_order_no ON `order`(`order_no`);
|
||||
CREATE INDEX idx_user_behavior_user ON `user_behavior`(`user_id`);
|
||||
CREATE INDEX idx_user_behavior_product ON `user_behavior`(`product_id`);
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user