Compare commits
22 Commits
9e46230b70
...
043483a076
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
043483a076 | ||
|
|
29da6b906b | ||
|
|
e6b71cd897 | ||
|
|
190f77529a | ||
|
|
b803e836eb | ||
|
|
e777adf6b0 | ||
|
|
e391058b30 | ||
|
|
099546b0a7 | ||
|
|
4e456a3157 | ||
|
|
b4d9a0285b | ||
|
|
ffcb5eae20 | ||
|
|
4bc5b67937 | ||
|
|
de54406d19 | ||
|
|
5a50114b59 | ||
|
|
295400b83e | ||
|
|
6194c64b4f | ||
|
|
ea0490e141 | ||
|
|
b96fdc6427 | ||
|
|
dd2342a234 | ||
|
|
102608b85c | ||
|
|
cd0449d6af | ||
|
|
197ca509c5 |
59
.dockerignore
Normal file
59
.dockerignore
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
# Build artifacts and dependencies
|
||||||
|
target/
|
||||||
|
.mvn/
|
||||||
|
*.iml
|
||||||
|
*.ipr
|
||||||
|
*.iws
|
||||||
|
|
||||||
|
# IDE files
|
||||||
|
.idea/
|
||||||
|
.vscode/
|
||||||
|
*.sublime-*
|
||||||
|
.eclipse
|
||||||
|
.project
|
||||||
|
.classpath
|
||||||
|
.settings/
|
||||||
|
|
||||||
|
# OS files
|
||||||
|
.DS_Store
|
||||||
|
Thumbs.db
|
||||||
|
|
||||||
|
# Version control
|
||||||
|
.git/
|
||||||
|
.gitignore
|
||||||
|
.gitattributes
|
||||||
|
|
||||||
|
# Documentation and scripts (comment out if needed in container)
|
||||||
|
README.md
|
||||||
|
*.md
|
||||||
|
deploy.sh
|
||||||
|
rollback.sh
|
||||||
|
fetch-log.sh
|
||||||
|
|
||||||
|
# Docker files (avoid recursion)
|
||||||
|
docker/
|
||||||
|
Dockerfile*
|
||||||
|
docker-compose*
|
||||||
|
|
||||||
|
# Logs and temp files
|
||||||
|
log/
|
||||||
|
logs/
|
||||||
|
*.log
|
||||||
|
*.tmp
|
||||||
|
|
||||||
|
# Backup files
|
||||||
|
backup/
|
||||||
|
*.backup
|
||||||
|
*.bak
|
||||||
|
|
||||||
|
# Environment files
|
||||||
|
.env
|
||||||
|
.env.*
|
||||||
|
|
||||||
|
# Test files
|
||||||
|
**/src/test/
|
||||||
|
|
||||||
|
# Maven wrapper (we install Maven in container)
|
||||||
|
.mvn/
|
||||||
|
mvnw
|
||||||
|
mvnw.cmd
|
||||||
3
.gitignore
vendored
3
.gitignore
vendored
@@ -50,3 +50,6 @@ nbproject/
|
|||||||
/var/log/*
|
/var/log/*
|
||||||
*.factorypath
|
*.factorypath
|
||||||
/storage/
|
/storage/
|
||||||
|
|
||||||
|
# Docker buildx cache
|
||||||
|
.buildx-cache/
|
||||||
119
CLAUDE.md
Normal file
119
CLAUDE.md
Normal file
@@ -0,0 +1,119 @@
|
|||||||
|
# CLAUDE.md
|
||||||
|
|
||||||
|
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
||||||
|
|
||||||
|
## Project Overview
|
||||||
|
|
||||||
|
This is a Spring Boot multi-module Java backend application called "peipei-backend" that appears to be a social platform/gaming system with WeChat Mini Program integration. The system supports clerk management, customer interactions, orders, gifts, and various social features.
|
||||||
|
|
||||||
|
## Project Structure
|
||||||
|
|
||||||
|
The project is organized as a Maven multi-module application:
|
||||||
|
|
||||||
|
- **play-admin**: Main backend API module containing REST controllers and business logic
|
||||||
|
- **play-common**: Shared utilities, configurations, and common components
|
||||||
|
- **play-generator**: Code generation tool for creating CRUD operations from database tables
|
||||||
|
|
||||||
|
## Technology Stack
|
||||||
|
|
||||||
|
- **Java 11** with Spring Boot 2.5.4
|
||||||
|
- **MyBatis Plus 3.5.3** with join support for database operations
|
||||||
|
- **MySQL 8** with Flyway migrations
|
||||||
|
- **Redis** for caching and session management
|
||||||
|
- **JWT** for authentication
|
||||||
|
- **WeChat Mini Program SDK** for WeChat integration
|
||||||
|
- **Aliyun OSS** for file storage
|
||||||
|
- **Lombok** for reducing boilerplate code
|
||||||
|
- **Knife4j/Swagger** for API documentation
|
||||||
|
|
||||||
|
## Development Commands
|
||||||
|
|
||||||
|
### Building and Running
|
||||||
|
```bash
|
||||||
|
# Build the entire project
|
||||||
|
mvn clean compile
|
||||||
|
|
||||||
|
# Package the application
|
||||||
|
mvn clean package
|
||||||
|
|
||||||
|
# Run the main application (play-admin module)
|
||||||
|
cd play-admin
|
||||||
|
mvn spring-boot:run
|
||||||
|
|
||||||
|
# Or run the packaged jar
|
||||||
|
java -jar play-admin/target/play-admin-1.0.jar
|
||||||
|
```
|
||||||
|
|
||||||
|
### Database Migrations
|
||||||
|
```bash
|
||||||
|
# Run Flyway migrations
|
||||||
|
mvn flyway:migrate
|
||||||
|
|
||||||
|
# Check migration status
|
||||||
|
mvn flyway:info
|
||||||
|
```
|
||||||
|
|
||||||
|
### Code Generation
|
||||||
|
```bash
|
||||||
|
# Generate CRUD code from database tables
|
||||||
|
cd play-generator
|
||||||
|
mvn clean compile exec:java
|
||||||
|
# Or run directly: ./run.sh (Linux/Mac) or run.bat (Windows)
|
||||||
|
```
|
||||||
|
|
||||||
|
## Configuration
|
||||||
|
|
||||||
|
The application uses Spring profiles with separate configuration files:
|
||||||
|
- `application.yml`: Main configuration
|
||||||
|
- `application-dev.yml`: Development environment
|
||||||
|
- `application-test.yml`: Test environment
|
||||||
|
- `application-prod.yml`: Production environment
|
||||||
|
|
||||||
|
Default active profile is `test`. Change via `spring.profiles.active` property.
|
||||||
|
|
||||||
|
## Architecture
|
||||||
|
|
||||||
|
### Module Structure
|
||||||
|
- **Controllers**: Located in `modules/{domain}/controller/` - Handle HTTP requests and responses
|
||||||
|
- **Services**: Located in `modules/{domain}/service/` - Business logic layer
|
||||||
|
- **Mappers**: Located in `modules/{domain}/mapper/` - Database access layer using MyBatis Plus
|
||||||
|
- **Entities**: Domain objects representing database tables
|
||||||
|
|
||||||
|
### Key Domains
|
||||||
|
- **clerk**: Clerk/staff management and operations
|
||||||
|
- **custom**: Customer management and interactions
|
||||||
|
- **order**: Order processing and management
|
||||||
|
- **shop**: Product catalog and commerce features
|
||||||
|
- **system**: System administration and user management
|
||||||
|
- **weichat**: WeChat Mini Program integration
|
||||||
|
|
||||||
|
### Authentication & Security
|
||||||
|
- JWT-based authentication with Spring Security
|
||||||
|
- Multi-tenant architecture support
|
||||||
|
- Role-based access control
|
||||||
|
- XSS protection and input validation
|
||||||
|
|
||||||
|
### Database
|
||||||
|
- Uses MyBatis Plus for ORM with automatic CRUD generation
|
||||||
|
- Flyway for database migration management
|
||||||
|
- Logical deletion support (soft delete)
|
||||||
|
- Multi-tenant data isolation
|
||||||
|
|
||||||
|
## Code Generation Tool
|
||||||
|
|
||||||
|
The project includes a powerful code generator (`play-generator`) that can:
|
||||||
|
- Read MySQL table structures
|
||||||
|
- Generate Entity, Mapper, Service, Controller classes
|
||||||
|
- Create MyBatis XML mapping files
|
||||||
|
- Support batch generation for multiple tables
|
||||||
|
|
||||||
|
Configure database connection in `play-generator/src/main/resources/config.properties` and specify table names to generate complete CRUD operations.
|
||||||
|
|
||||||
|
## Deployment
|
||||||
|
|
||||||
|
The project includes a deployment script (`deploy.sh`) that:
|
||||||
|
- Builds and packages the application
|
||||||
|
- Deploys to remote server via SCP
|
||||||
|
- Restarts the application service
|
||||||
|
|
||||||
|
Server runs on port 7002 with context path `/api`.
|
||||||
70
README.md
70
README.md
@@ -134,6 +134,76 @@ mvn spotless:apply compile
|
|||||||
mvn spotless:apply checkstyle:check compile
|
mvn spotless:apply checkstyle:check compile
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## 部署说明
|
||||||
|
|
||||||
|
### Docker 构建和推送
|
||||||
|
|
||||||
|
项目支持多架构 Docker 构建,特别适合在 Apple Silicon Mac 上为 Linux 服务器构建镜像。
|
||||||
|
|
||||||
|
#### 构建镜像
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 构建服务器部署镜像 (Linux amd64)
|
||||||
|
./build-docker.sh amd64
|
||||||
|
|
||||||
|
# 构建本地开发镜像 (Apple Silicon arm64)
|
||||||
|
./build-docker.sh arm64
|
||||||
|
|
||||||
|
# 自动检测架构构建
|
||||||
|
./build-docker.sh
|
||||||
|
|
||||||
|
# 查看帮助
|
||||||
|
./build-docker.sh -h
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 推送到私有仓库
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 推送 amd64 镜像到私有仓库 (用于服务器部署)
|
||||||
|
./push-docker.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
### 服务器部署
|
||||||
|
|
||||||
|
#### 部署环境
|
||||||
|
- **服务器**: CentOS Linux
|
||||||
|
- **架构**: amd64
|
||||||
|
- **容器**: Docker + Docker Compose
|
||||||
|
|
||||||
|
#### 部署步骤
|
||||||
|
|
||||||
|
1. **服务器上的配置文件**
|
||||||
|
```bash
|
||||||
|
# 服务器主目录有专门的 docker-compose 文件
|
||||||
|
~/docker-compose.yml # 为 CentOS 环境优化的配置
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **启动服务**
|
||||||
|
```bash
|
||||||
|
# 在服务器主目录执行
|
||||||
|
cd ~
|
||||||
|
docker-compose up -d
|
||||||
|
```
|
||||||
|
|
||||||
|
3. **查看日志**
|
||||||
|
```bash
|
||||||
|
# 应用日志位置
|
||||||
|
~/log/ # 应用日志目录
|
||||||
|
|
||||||
|
# 查看实时日志
|
||||||
|
tail -f ~/log/detail.log
|
||||||
|
tail -f ~/log/error.log
|
||||||
|
|
||||||
|
# 查看容器日志
|
||||||
|
docker-compose logs -f
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 部署文件说明
|
||||||
|
|
||||||
|
- **~/docker-compose.yml**: 为 CentOS 环境定制的 Docker Compose 配置
|
||||||
|
- **~/log/**: 应用日志输出目录
|
||||||
|
- 配置文件已针对服务器环境进行优化,可直接使用
|
||||||
|
|
||||||
## 模块介绍
|
## 模块介绍
|
||||||
|
|
||||||
### play-admin
|
### play-admin
|
||||||
|
|||||||
67364
backend.txt
67364
backend.txt
File diff suppressed because it is too large
Load Diff
162
build-docker.sh
Executable file
162
build-docker.sh
Executable file
@@ -0,0 +1,162 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# 构建 Docker 镜像脚本
|
||||||
|
# 用法: ./build-docker.sh [amd64|arm64]
|
||||||
|
# 如果不指定架构,将使用本机架构
|
||||||
|
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
# 颜色输出
|
||||||
|
RED='\033[0;31m'
|
||||||
|
GREEN='\033[0;32m'
|
||||||
|
YELLOW='\033[1;33m'
|
||||||
|
NC='\033[0m' # No Color
|
||||||
|
|
||||||
|
echo -e "${GREEN}=== 开始构建 PeiPei 后端 Docker 镜像 ===${NC}"
|
||||||
|
|
||||||
|
# 检测本机架构
|
||||||
|
NATIVE_ARCH=$(uname -m)
|
||||||
|
if [[ "$NATIVE_ARCH" == "x86_64" ]]; then
|
||||||
|
NATIVE_ARCH="amd64"
|
||||||
|
elif [[ "$NATIVE_ARCH" == "arm64" ]] || [[ "$NATIVE_ARCH" == "aarch64" ]]; then
|
||||||
|
NATIVE_ARCH="arm64"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 处理命令行参数(兼容未提供参数时的 set -u)
|
||||||
|
ARG1="${1-}"
|
||||||
|
if [[ "$ARG1" == "-h" || "$ARG1" == "--help" ]]; then
|
||||||
|
echo -e "${GREEN}PeiPei 后端 Docker 镜像构建脚本${NC}"
|
||||||
|
echo ""
|
||||||
|
echo -e "${YELLOW}用法:${NC}"
|
||||||
|
echo " ./build-docker.sh [选项] [架构]"
|
||||||
|
echo ""
|
||||||
|
echo -e "${YELLOW}选项:${NC}"
|
||||||
|
echo " -h, --help 显示此帮助信息"
|
||||||
|
echo ""
|
||||||
|
echo -e "${YELLOW}架构:${NC}"
|
||||||
|
echo " amd64 构建 Linux amd64 镜像 (适用于服务器)"
|
||||||
|
echo " arm64 构建 Linux arm64 镜像 (适用于 Apple Silicon Mac)"
|
||||||
|
echo " [空] 自动检测本机架构"
|
||||||
|
echo ""
|
||||||
|
echo -e "${YELLOW}示例:${NC}"
|
||||||
|
echo " ./build-docker.sh # 自动检测架构构建"
|
||||||
|
echo " ./build-docker.sh amd64 # 构建服务器镜像"
|
||||||
|
echo " ./build-docker.sh arm64 # 构建 Mac 镜像"
|
||||||
|
echo " ./build-docker.sh -h # 显示帮助"
|
||||||
|
echo ""
|
||||||
|
echo -e "${YELLOW}说明:${NC}"
|
||||||
|
echo " - 本机架构: ${NATIVE_ARCH}"
|
||||||
|
echo " - 使用 Docker Buildx 进行构建"
|
||||||
|
echo " - 镜像将带有架构后缀标签 (如: latest-amd64)"
|
||||||
|
echo " - 构建缓存存储在: .buildx-cache/"
|
||||||
|
echo " - 确保 Dockerfile 使用: FROM --platform=\$TARGETPLATFORM"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
TARGET_ARCH="${ARG1:-$NATIVE_ARCH}"
|
||||||
|
|
||||||
|
# 验证架构参数
|
||||||
|
if [[ "$TARGET_ARCH" != "amd64" && "$TARGET_ARCH" != "arm64" ]]; then
|
||||||
|
echo -e "${RED}错误: 不支持的架构 '$TARGET_ARCH'${NC}"
|
||||||
|
echo -e "${YELLOW}支持的架构: amd64, arm64${NC}"
|
||||||
|
echo -e "${YELLOW}用法: ./build-docker.sh [amd64|arm64]${NC}"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo -e "${YELLOW}目标架构: ${TARGET_ARCH}${NC}"
|
||||||
|
echo -e "${YELLOW}本机架构: ${NATIVE_ARCH}${NC}"
|
||||||
|
|
||||||
|
# 检查是否在正确的目录
|
||||||
|
if [ ! -f "docker/Dockerfile" ]; then
|
||||||
|
echo -e "${RED}错误: 请在项目根目录执行此脚本${NC}"
|
||||||
|
echo -e "${YELLOW}当前目录: $(pwd)${NC}"
|
||||||
|
echo -e "${YELLOW}期望目录应包含: docker/Dockerfile${NC}"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 获取 UTC+8 时间戳
|
||||||
|
TIMESTAMP=$(TZ='Asia/Shanghai' date +"%Y-%m-%d-%Hh-%Mm")
|
||||||
|
echo -e "${YELLOW}构建时间戳 (UTC+8): ${TIMESTAMP}${NC}"
|
||||||
|
|
||||||
|
# 镜像名称和标签
|
||||||
|
IMAGE_NAME="peipei-backend"
|
||||||
|
VERSION_TAG="${TIMESTAMP}-${TARGET_ARCH}"
|
||||||
|
LATEST_TAG="latest-${TARGET_ARCH}"
|
||||||
|
|
||||||
|
echo -e "${YELLOW}镜像名称: ${IMAGE_NAME}${NC}"
|
||||||
|
echo -e "${YELLOW}版本标签: ${VERSION_TAG}${NC}"
|
||||||
|
|
||||||
|
# 构建 Docker 镜像
|
||||||
|
echo -e "${GREEN}开始构建镜像...${NC}"
|
||||||
|
|
||||||
|
# 确保 buildx 可用
|
||||||
|
if ! docker buildx version >/dev/null 2>&1; then
|
||||||
|
echo -e "${RED}错误: 需要 Docker Buildx 支持${NC}"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 创建并使用 builder(如果不存在)
|
||||||
|
if ! docker buildx inspect peipei-builder >/dev/null 2>&1; then
|
||||||
|
echo -e "${YELLOW}创建 buildx builder...${NC}"
|
||||||
|
docker buildx create --name peipei-builder --use
|
||||||
|
else
|
||||||
|
docker buildx use peipei-builder
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 启动 builder 和 QEMU(用于跨架构支持)
|
||||||
|
echo -e "${YELLOW}初始化 buildx builder...${NC}"
|
||||||
|
docker buildx inspect --bootstrap >/dev/null
|
||||||
|
|
||||||
|
# 显示构建类型
|
||||||
|
if [[ "$TARGET_ARCH" != "$NATIVE_ARCH" ]]; then
|
||||||
|
echo -e "${YELLOW}跨平台构建: ${NATIVE_ARCH} -> ${TARGET_ARCH}${NC}"
|
||||||
|
else
|
||||||
|
echo -e "${YELLOW}本机架构构建: ${TARGET_ARCH}${NC}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 创建缓存目录(可选优化)
|
||||||
|
CACHE_DIR=".buildx-cache"
|
||||||
|
mkdir -p "$CACHE_DIR"
|
||||||
|
|
||||||
|
# 始终使用 buildx 以保持行为一致
|
||||||
|
echo -e "${GREEN}执行构建...${NC}"
|
||||||
|
if docker buildx build \
|
||||||
|
--platform "linux/${TARGET_ARCH}" \
|
||||||
|
--load \
|
||||||
|
--cache-from="type=local,src=${CACHE_DIR}" \
|
||||||
|
--cache-to="type=local,dest=${CACHE_DIR}" \
|
||||||
|
-f docker/Dockerfile \
|
||||||
|
-t "${IMAGE_NAME}:${VERSION_TAG}" \
|
||||||
|
-t "${IMAGE_NAME}:${LATEST_TAG}" \
|
||||||
|
.; then
|
||||||
|
BUILD_SUCCESS=true
|
||||||
|
else
|
||||||
|
BUILD_SUCCESS=false
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 检查构建结果
|
||||||
|
if [[ "$BUILD_SUCCESS" == "true" ]]; then
|
||||||
|
echo -e "${GREEN}✅ Docker 镜像构建成功!${NC}"
|
||||||
|
echo -e "${GREEN}镜像标签:${NC}"
|
||||||
|
echo -e " - ${IMAGE_NAME}:${VERSION_TAG}"
|
||||||
|
echo -e " - ${IMAGE_NAME}:${LATEST_TAG}"
|
||||||
|
|
||||||
|
echo -e "\n${YELLOW}镜像信息:${NC}"
|
||||||
|
docker images | grep -E "^${IMAGE_NAME}\s"
|
||||||
|
|
||||||
|
echo -e "\n${GREEN}使用说明:${NC}"
|
||||||
|
if [[ "$TARGET_ARCH" == "amd64" ]]; then
|
||||||
|
echo -e " - 该镜像适用于 Linux amd64 服务器部署"
|
||||||
|
echo -e " - 推送到服务器: ./push-docker.sh"
|
||||||
|
elif [[ "$TARGET_ARCH" == "arm64" ]]; then
|
||||||
|
echo -e " - 该镜像适用于 Apple Silicon Mac 本地运行"
|
||||||
|
echo -e " - 本地测试: docker run ${IMAGE_NAME}:${LATEST_TAG}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo -e "\n${YELLOW}构建其他架构:${NC}"
|
||||||
|
echo -e " - 构建 amd64 (服务器): ./build-docker.sh amd64"
|
||||||
|
echo -e " - 构建 arm64 (Mac): ./build-docker.sh arm64"
|
||||||
|
echo -e " - 自动检测架构: ./build-docker.sh"
|
||||||
|
else
|
||||||
|
echo -e "${RED}❌ Docker 镜像构建失败!${NC}"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
219
db_workflow.md
Normal file
219
db_workflow.md
Normal file
@@ -0,0 +1,219 @@
|
|||||||
|
# Database Development Workflow
|
||||||
|
|
||||||
|
This document outlines the recommended workflow for database schema changes and code generation in the peipei-backend project.
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
The project uses a **Database-First** approach with the following flow:
|
||||||
|
```
|
||||||
|
Flyway Migration → Database Schema → Code Generator → Java Code
|
||||||
|
```
|
||||||
|
|
||||||
|
## Step-by-Step Workflow
|
||||||
|
|
||||||
|
### 1. Create Flyway Migration
|
||||||
|
|
||||||
|
Create a new migration file in `/play-admin/src/main/resources/db/migration/`:
|
||||||
|
|
||||||
|
```sql
|
||||||
|
-- V{version}__{description}.sql
|
||||||
|
-- Example: V2__add_new_feature_table.sql
|
||||||
|
|
||||||
|
CREATE TABLE `play_new_feature` (
|
||||||
|
`id` varchar(32) NOT NULL COMMENT 'UUID',
|
||||||
|
`feature_name` varchar(100) COMMENT '功能名称',
|
||||||
|
`feature_type` varchar(20) DEFAULT NULL COMMENT '功能类型',
|
||||||
|
`status` char(1) DEFAULT '0' COMMENT '状态(0正常 1停用)',
|
||||||
|
`tenant_id` varchar(32) NOT NULL COMMENT '租户ID',
|
||||||
|
`created_by` varchar(32) DEFAULT NULL COMMENT '创建人的id',
|
||||||
|
`created_time` datetime DEFAULT NULL COMMENT '创建时间',
|
||||||
|
`updated_by` varchar(32) DEFAULT NULL COMMENT '修改人的id',
|
||||||
|
`updated_time` datetime DEFAULT NULL COMMENT '修改时间',
|
||||||
|
`deleted` tinyint(1) NOT NULL DEFAULT '0' COMMENT '逻辑删除',
|
||||||
|
`version` int(11) NOT NULL DEFAULT '1' COMMENT '数据版本',
|
||||||
|
PRIMARY KEY (`id`)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='新功能表';
|
||||||
|
```
|
||||||
|
|
||||||
|
**Migration Naming Convention:**
|
||||||
|
- `V{version}__{description}.sql`
|
||||||
|
- Version format: `V1.2025.0609.10.11` (timestamp-based)
|
||||||
|
- Description: snake_case with double underscore
|
||||||
|
|
||||||
|
### 2. Run Flyway Migration
|
||||||
|
|
||||||
|
Apply the migration to update database schema:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Run from project root
|
||||||
|
mvn flyway:migrate
|
||||||
|
|
||||||
|
# Or check migration status
|
||||||
|
mvn flyway:info
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. Configure Code Generator
|
||||||
|
|
||||||
|
Edit `/play-generator/src/main/resources/config.properties`:
|
||||||
|
|
||||||
|
```properties
|
||||||
|
# Database configuration (should match main app)
|
||||||
|
db.url=jdbc:mysql://localhost:3306/play-with?useUnicode=true&characterEncoding=utf-8&serverTimezone=Asia/Shanghai
|
||||||
|
db.driver=com.mysql.cj.jdbc.Driver
|
||||||
|
db.username=root
|
||||||
|
db.password=your_password
|
||||||
|
|
||||||
|
# Code generation configuration
|
||||||
|
gen.author=your_name
|
||||||
|
gen.packageName=com.starry.admin
|
||||||
|
gen.outputDir=./generated-code
|
||||||
|
gen.autoRemovePre=false
|
||||||
|
gen.tablePrefix=play_
|
||||||
|
gen.tplCategory=crud
|
||||||
|
|
||||||
|
# Specify the new table(s) to generate
|
||||||
|
gen.tableNames=play_new_feature
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4. Run Code Generator
|
||||||
|
|
||||||
|
Generate Java code from the new table structure:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd play-generator
|
||||||
|
./run.sh
|
||||||
|
# Or manually: mvn clean compile exec:java
|
||||||
|
```
|
||||||
|
|
||||||
|
### 5. Review Generated Code
|
||||||
|
|
||||||
|
Check the generated files in `./generated-code/`:
|
||||||
|
|
||||||
|
```
|
||||||
|
generated-code/
|
||||||
|
├── src/main/java/com/starry/admin/
|
||||||
|
│ ├── entity/PlayNewFeatureEntity.java
|
||||||
|
│ ├── mapper/PlayNewFeatureMapper.java
|
||||||
|
│ ├── service/IPlayNewFeatureService.java
|
||||||
|
│ ├── service/impl/PlayNewFeatureServiceImpl.java
|
||||||
|
│ └── controller/PlayNewFeatureController.java
|
||||||
|
└── src/main/resources/mapper/PlayNewFeatureMapper.xml
|
||||||
|
```
|
||||||
|
|
||||||
|
### 6. Integrate Generated Code
|
||||||
|
|
||||||
|
Copy generated files to the appropriate module in play-admin:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Create new module directory structure
|
||||||
|
mkdir -p play-admin/src/main/java/com/starry/admin/modules/newfeature/{controller,service,mapper,module/entity}
|
||||||
|
|
||||||
|
# Copy generated files to appropriate locations
|
||||||
|
cp generated-code/src/main/java/com/starry/admin/controller/* \
|
||||||
|
play-admin/src/main/java/com/starry/admin/modules/newfeature/controller/
|
||||||
|
|
||||||
|
cp generated-code/src/main/java/com/starry/admin/service/* \
|
||||||
|
play-admin/src/main/java/com/starry/admin/modules/newfeature/service/
|
||||||
|
|
||||||
|
cp generated-code/src/main/java/com/starry/admin/mapper/* \
|
||||||
|
play-admin/src/main/java/com/starry/admin/modules/newfeature/mapper/
|
||||||
|
|
||||||
|
cp generated-code/src/main/java/com/starry/admin/entity/* \
|
||||||
|
play-admin/src/main/java/com/starry/admin/modules/newfeature/module/entity/
|
||||||
|
|
||||||
|
cp generated-code/src/main/resources/mapper/* \
|
||||||
|
play-admin/src/main/resources/mapper/newfeature/
|
||||||
|
```
|
||||||
|
|
||||||
|
### 7. Customize and Test
|
||||||
|
|
||||||
|
- **Review generated code** for any needed customizations
|
||||||
|
- **Add business logic** to Service implementations
|
||||||
|
- **Customize validation** in Controllers
|
||||||
|
- **Add custom queries** in Mapper if needed
|
||||||
|
- **Test the new endpoints** via Swagger UI
|
||||||
|
- **Run application** to ensure everything works
|
||||||
|
|
||||||
|
## Database Design Best Practices
|
||||||
|
|
||||||
|
### Table Naming Convention
|
||||||
|
- Use `play_` prefix for all tables
|
||||||
|
- Use snake_case for table names
|
||||||
|
- Example: `play_order_info`, `play_clerk_level_info`
|
||||||
|
|
||||||
|
### Required Standard Columns
|
||||||
|
All tables should include these standard columns:
|
||||||
|
```sql
|
||||||
|
`id` varchar(32) NOT NULL COMMENT 'UUID',
|
||||||
|
`tenant_id` varchar(32) NOT NULL COMMENT '租户ID',
|
||||||
|
`created_by` varchar(32) DEFAULT NULL COMMENT '创建人的id',
|
||||||
|
`created_time` datetime DEFAULT NULL COMMENT '创建时间',
|
||||||
|
`updated_by` varchar(32) DEFAULT NULL COMMENT '修改人的id',
|
||||||
|
`updated_time` datetime DEFAULT NULL COMMENT '修改时间',
|
||||||
|
`deleted` tinyint(1) NOT NULL DEFAULT '0' COMMENT '逻辑删除',
|
||||||
|
`version` int(11) NOT NULL DEFAULT '1' COMMENT '数据版本',
|
||||||
|
```
|
||||||
|
|
||||||
|
### Column Guidelines
|
||||||
|
- Always add meaningful `COMMENT` to columns
|
||||||
|
- Use appropriate data types (`varchar`, `int`, `decimal`, `datetime`)
|
||||||
|
- Set proper defaults where applicable
|
||||||
|
- Consider indexing for foreign keys and frequently queried columns
|
||||||
|
|
||||||
|
## Code Generation Notes
|
||||||
|
|
||||||
|
### What Gets Generated
|
||||||
|
- **Entity**: MyBatis Plus entity with Lombok annotations
|
||||||
|
- **Mapper**: Database access interface extending BaseMapper
|
||||||
|
- **Service**: Business logic interface with standard CRUD operations
|
||||||
|
- **ServiceImpl**: Service implementation with basic CRUD logic
|
||||||
|
- **Controller**: REST API endpoints with Swagger documentation
|
||||||
|
- **Mapper XML**: MyBatis SQL mapping files
|
||||||
|
|
||||||
|
### Generated Code Features
|
||||||
|
- Swagger API documentation
|
||||||
|
- Spring Security integration (`@PreAuthorize`)
|
||||||
|
- Audit logging (`@Log`)
|
||||||
|
- Input validation
|
||||||
|
- Pagination support
|
||||||
|
- Logical deletion support
|
||||||
|
- Multi-tenant support
|
||||||
|
|
||||||
|
## Troubleshooting
|
||||||
|
|
||||||
|
### Common Issues
|
||||||
|
|
||||||
|
**Migration Fails**:
|
||||||
|
- Check database connection settings
|
||||||
|
- Verify migration SQL syntax
|
||||||
|
- Ensure proper permissions
|
||||||
|
|
||||||
|
**Generator Fails**:
|
||||||
|
- Verify database connection in config.properties
|
||||||
|
- Check if table exists after migration
|
||||||
|
- Ensure table follows naming conventions
|
||||||
|
|
||||||
|
**Generated Code Issues**:
|
||||||
|
- Review column comments (used for Java doc)
|
||||||
|
- Check data type mappings
|
||||||
|
- Verify package naming in config
|
||||||
|
|
||||||
|
### Database Type Mappings
|
||||||
|
|
||||||
|
| MySQL Type | Java Type | Notes |
|
||||||
|
|------------|-----------|-------|
|
||||||
|
| `varchar(n)` | `String` | |
|
||||||
|
| `int`, `bigint` | `Integer`, `Long` | |
|
||||||
|
| `decimal(p,s)` | `BigDecimal` | Precise decimal calculations |
|
||||||
|
| `datetime` | `Date` | Or `LocalDateTime` |
|
||||||
|
| `tinyint(1)` | `Boolean` | For flags/status |
|
||||||
|
| `char(1)` | `String` | For status codes |
|
||||||
|
|
||||||
|
## Tips
|
||||||
|
|
||||||
|
1. **Always create migration first**, then generate code
|
||||||
|
2. **Use descriptive table and column names** - they become class and field names
|
||||||
|
3. **Test migrations on dev environment** before applying to production
|
||||||
|
4. **Review generated code** before committing - customize as needed
|
||||||
|
5. **Follow existing module patterns** when organizing generated code
|
||||||
|
6. **Backup database** before running migrations in production
|
||||||
16
deploy-docker.sh
Executable file
16
deploy-docker.sh
Executable file
@@ -0,0 +1,16 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
# Docker deployment script
|
||||||
|
set -e
|
||||||
|
|
||||||
|
# Get current time and format it
|
||||||
|
current_time=$(date +"%Y-%m-%d %H:%M:%S")
|
||||||
|
echo "Deployment started at: $current_time"
|
||||||
|
|
||||||
|
echo "Connecting to server to update docker container..."
|
||||||
|
# SSH into the server, pull the latest image and restart the container
|
||||||
|
ssh root@122.51.20.105 "source /etc/profile; cd ~; docker compose pull && docker compose up -d"
|
||||||
|
echo "Docker container updated!"
|
||||||
|
|
||||||
|
# Get current time and format it
|
||||||
|
current_time=$(date +"%Y-%m-%d %H:%M:%S")
|
||||||
|
echo "Deployment finished at: $current_time"
|
||||||
23
deploy-jar.sh
Executable file
23
deploy-jar.sh
Executable file
@@ -0,0 +1,23 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
# 发包脚本
|
||||||
|
set -e
|
||||||
|
# 获取当前时间并格式化为指定格式
|
||||||
|
current_time=$(date +"%Y-%m-%d %H:%M:%S")
|
||||||
|
echo "发布开始,当前时间是:$current_time"
|
||||||
|
|
||||||
|
# 构建项目
|
||||||
|
echo "开始构建项目..."
|
||||||
|
mvn clean package -DskipTests
|
||||||
|
echo "构建完成!"
|
||||||
|
|
||||||
|
# 备份当前的jar文件
|
||||||
|
ssh root@122.51.20.105 "source /etc/profile; cd /www/wwwroot/july.hucs.top; if [ -f play-admin-1.0.jar ]; then mv play-admin-1.0.jar play-admin-1.0.jar.backup; fi"
|
||||||
|
echo "备份完成!"
|
||||||
|
|
||||||
|
scp ./play-admin/target/play-admin-1.0.jar root@122.51.20.105:/www/wwwroot/july.hucs.top
|
||||||
|
echo "上传成功!"
|
||||||
|
ssh root@122.51.20.105 "source /etc/profile;cd /www/wwwroot/july.hucs.top;sh start.sh restart"
|
||||||
|
# 获取当前时间并格式化为指定格式
|
||||||
|
current_time=$(date +"%Y-%m-%d %H:%M:%S")
|
||||||
|
|
||||||
|
echo "发布完成,当前时间是:$current_time"
|
||||||
11
deploy.sh
11
deploy.sh
@@ -1,11 +0,0 @@
|
|||||||
#!/bin/sh
|
|
||||||
# 发包脚本
|
|
||||||
set -e
|
|
||||||
echo "发布开始,当前时间是:$current_time"
|
|
||||||
#mvn clean install
|
|
||||||
scp ./play-admin/target/play-admin-1.0.jar root@122.51.20.105:/www/wwwroot/july.hucs.top
|
|
||||||
ssh root@122.51.20.105 "source /etc/profile;cd /www/wwwroot/july.hucs.top;sh start.sh restart"
|
|
||||||
# 获取当前时间并格式化为指定格式
|
|
||||||
current_time=$(date +"%Y-%m-%d %H:%M:%S")
|
|
||||||
|
|
||||||
echo "发布完成,当前时间是:$current_time"
|
|
||||||
@@ -1,16 +1,38 @@
|
|||||||
FROM openjdk:11-jre-slim
|
# ---------- Build stage ----------
|
||||||
|
FROM maven:3.9-eclipse-temurin-11 AS build
|
||||||
|
WORKDIR /workspace
|
||||||
|
|
||||||
# 设置工作目录
|
# Cache-friendly: copy POMs first
|
||||||
|
COPY pom.xml .
|
||||||
|
COPY play-admin/pom.xml play-admin/
|
||||||
|
COPY play-common/pom.xml play-common/
|
||||||
|
COPY play-generator/pom.xml play-generator/
|
||||||
|
|
||||||
|
# Warm deps cache
|
||||||
|
RUN --mount=type=cache,target=/root/.m2 mvn -B -DskipTests dependency:go-offline
|
||||||
|
|
||||||
|
# Add sources
|
||||||
|
COPY play-admin/src play-admin/src/
|
||||||
|
COPY play-common/src play-common/src/
|
||||||
|
COPY play-generator/src play-generator/src/
|
||||||
|
|
||||||
|
# Build only app module (and its deps)
|
||||||
|
RUN --mount=type=cache,target=/root/.m2 mvn -pl play-admin -am -B -DskipTests -T 1C clean package
|
||||||
|
|
||||||
|
# ---------- Runtime stage (multi-arch) ----------
|
||||||
|
FROM --platform=$TARGETPLATFORM eclipse-temurin:11-jre AS runtime
|
||||||
|
# non-root
|
||||||
|
RUN groupadd -g 1001 appgroup && useradd -u 1001 -g appgroup -m -s /usr/sbin/nologin appuser
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
|
||||||
# 只复制应用程序JAR包,不复制lib目录
|
COPY --from=build /workspace/play-admin/target/*.jar /app/app.jar
|
||||||
COPY ./*.jar app.jar
|
RUN chown -R appuser:appgroup /app
|
||||||
|
USER appuser
|
||||||
|
|
||||||
# 设置环境变量
|
ENV JAVA_OPTS="-Xms2g -Xmx2g" \
|
||||||
ENV APP_NAME=app.jar
|
SPRING_PROFILES_ACTIVE="test" \
|
||||||
|
TZ="Asia/Shanghai" \
|
||||||
|
LANG="C.UTF-8"
|
||||||
|
|
||||||
# 暴露应用端口
|
EXPOSE 7002
|
||||||
EXPOSE 8080
|
ENTRYPOINT ["sh","-c","exec java $JAVA_OPTS -jar /app/app.jar --spring.profiles.active=${SPRING_PROFILES_ACTIVE}"]
|
||||||
|
|
||||||
# 设置启动命令
|
|
||||||
CMD ["sh", "-c", "java -Dloader.path=./lib/ -Xms2g -Xmx2g -jar $APP_NAME --spring.profiles.active=test"]
|
|
||||||
|
|||||||
30
docker/README.md
Normal file
30
docker/README.md
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
# 私有 Docker 仓库使用速查表
|
||||||
|
# 仓库地址: https://docker-registry.julyhaven.com
|
||||||
|
# 用户名: hucs
|
||||||
|
# 密码: hucsdev
|
||||||
|
|
||||||
|
# 登录
|
||||||
|
docker login docker-registry.julyhaven.com -u hucs -p hucsdev
|
||||||
|
|
||||||
|
# 推送镜像 (示例: myapp:1.0.0)
|
||||||
|
docker tag myapp:1.0.0 docker-registry.julyhaven.com/myteam/myapp:1.0.0
|
||||||
|
docker push docker-registry.julyhaven.com/myteam/myapp:1.0.0
|
||||||
|
|
||||||
|
# 同时推送 latest
|
||||||
|
docker tag myapp:1.0.0 docker-registry.julyhaven.com/myteam/myapp:latest
|
||||||
|
docker push docker-registry.julyhaven.com/myteam/myapp:latest
|
||||||
|
|
||||||
|
# 拉取镜像
|
||||||
|
docker pull docker-registry.julyhaven.com/myteam/myapp:1.0.0
|
||||||
|
docker pull docker-registry.julyhaven.com/myteam/myapp:latest
|
||||||
|
|
||||||
|
# 测试镜像 (hello-world)
|
||||||
|
docker pull hello-world
|
||||||
|
docker tag hello-world docker-registry.julyhaven.com/test/hello-world:latest
|
||||||
|
docker push docker-registry.julyhaven.com/test/hello-world:latest
|
||||||
|
docker pull docker-registry.julyhaven.com/test/hello-world:latest
|
||||||
|
|
||||||
|
# 注意事项:
|
||||||
|
# 1. latest 会被覆盖,生产环境请用版本号标签
|
||||||
|
# 2. 已支持大镜像推送/拉取,无需额外配置
|
||||||
|
# 3. 登录失败时请确认用户名/密码是否正确
|
||||||
@@ -1,12 +1,37 @@
|
|||||||
version: '3'
|
version: "3.8"
|
||||||
|
|
||||||
services:
|
services:
|
||||||
spring-boot-app:
|
peipei-backend:
|
||||||
build: .
|
image: docker-registry.julyhaven.com/peipei/backend:latest
|
||||||
container_name: spring-boot-app
|
container_name: peipei-backend
|
||||||
ports:
|
ports:
|
||||||
- "7003:7002"
|
- "7003:7002"
|
||||||
|
environment:
|
||||||
|
- SPRING_PROFILES_ACTIVE=test
|
||||||
|
- JAVA_OPTS=-Xms2g -Xmx2g
|
||||||
volumes:
|
volumes:
|
||||||
- ./lib:/app/lib # 挂载主机的lib目录到容器内的lib目录
|
- ./log:/app/log # Mount log directory to host
|
||||||
- ./log:/app/log # 挂载日志目录到主机
|
restart: unless-stopped
|
||||||
restart: always
|
healthcheck:
|
||||||
|
# Maybe use actuator later
|
||||||
|
test:
|
||||||
|
[
|
||||||
|
"CMD",
|
||||||
|
"wget",
|
||||||
|
"--no-verbose",
|
||||||
|
"--tries=1",
|
||||||
|
"--spider",
|
||||||
|
"http://localhost:7002/api/health",
|
||||||
|
]
|
||||||
|
interval: 30s
|
||||||
|
timeout: 10s
|
||||||
|
retries: 3
|
||||||
|
start_period: 60s
|
||||||
|
networks:
|
||||||
|
- peipei-network
|
||||||
|
|
||||||
|
networks:
|
||||||
|
peipei-network:
|
||||||
|
driver: bridge
|
||||||
|
# volumes:
|
||||||
|
# mysql_data:
|
||||||
|
|||||||
18
fetch-log.sh
18
fetch-log.sh
@@ -1,18 +0,0 @@
|
|||||||
#!/bin/sh
|
|
||||||
# 拉取日志脚本
|
|
||||||
set -e
|
|
||||||
|
|
||||||
# 获取当前时间并格式化为指定格式
|
|
||||||
current_time=$(date +"%Y-%m-%d %H:%M:%S")
|
|
||||||
echo "开始拉取日志,当前时间是:$current_time"
|
|
||||||
|
|
||||||
# 创建本地log目录(如果不存在)
|
|
||||||
mkdir -p ./log
|
|
||||||
|
|
||||||
# 从远程服务器拉取整个log文件夹
|
|
||||||
echo "正在从远程服务器拉取日志文件..."
|
|
||||||
scp -r root@122.51.20.105:/www/wwwroot/july.hucs.top/log/* ./log/
|
|
||||||
|
|
||||||
# 获取当前时间并格式化为指定格式
|
|
||||||
current_time=$(date +"%Y-%m-%d %H:%M:%S")
|
|
||||||
echo "日志拉取完成,当前时间是:$current_time"
|
|
||||||
@@ -93,9 +93,17 @@
|
|||||||
<artifactId>jave-core</artifactId>
|
<artifactId>jave-core</artifactId>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
|
<!-- Include native ffmpeg binaries for common runtimes to avoid
|
||||||
|
build-platform vs target-platform mismatches in Docker builds. -->
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>ws.schild</groupId>
|
<groupId>ws.schild</groupId>
|
||||||
<artifactId>jave-nativebin-linux64</artifactId>
|
<artifactId>jave-nativebin-linux64</artifactId>
|
||||||
|
<scope>runtime</scope>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>ws.schild</groupId>
|
||||||
|
<artifactId>jave-nativebin-osxm1</artifactId>
|
||||||
|
<scope>runtime</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
<dependency>
|
<dependency>
|
||||||
@@ -134,6 +142,23 @@
|
|||||||
<version>0.2.0</version>
|
<version>0.2.0</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
|
<!--JUnit 5 for testing-->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.junit.jupiter</groupId>
|
||||||
|
<artifactId>junit-jupiter</artifactId>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.mockito</groupId>
|
||||||
|
<artifactId>mockito-core</artifactId>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.mockito</groupId>
|
||||||
|
<artifactId>mockito-junit-jupiter</artifactId>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
</dependencies>
|
</dependencies>
|
||||||
|
|
||||||
<build>
|
<build>
|
||||||
@@ -157,23 +182,22 @@
|
|||||||
<artifactId>flyway-maven-plugin</artifactId>
|
<artifactId>flyway-maven-plugin</artifactId>
|
||||||
<version>7.15.0</version>
|
<version>7.15.0</version>
|
||||||
</plugin>
|
</plugin>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
|
<artifactId>maven-surefire-plugin</artifactId>
|
||||||
|
<version>3.0.0-M7</version>
|
||||||
|
<configuration>
|
||||||
|
<useSystemClassLoader>false</useSystemClassLoader>
|
||||||
|
</configuration>
|
||||||
|
</plugin>
|
||||||
<plugin>
|
<plugin>
|
||||||
<groupId>org.springframework.boot</groupId>
|
<groupId>org.springframework.boot</groupId>
|
||||||
<artifactId>spring-boot-maven-plugin</artifactId>
|
<artifactId>spring-boot-maven-plugin</artifactId>
|
||||||
<version>2.7.9</version>
|
<version>2.7.9</version>
|
||||||
<configuration>
|
|
||||||
<layout>ZIP</layout>
|
|
||||||
<includes>
|
|
||||||
<include>
|
|
||||||
<groupId>nothing</groupId>
|
|
||||||
<artifactId>nothing</artifactId>
|
|
||||||
</include>
|
|
||||||
</includes>
|
|
||||||
</configuration>
|
|
||||||
<executions>
|
<executions>
|
||||||
<execution>
|
<execution>
|
||||||
<goals>
|
<goals>
|
||||||
<goal>repackage</goal> <!-- 将引入的 jar 打入其中 -->
|
<goal>repackage</goal>
|
||||||
</goals>
|
</goals>
|
||||||
</execution>
|
</execution>
|
||||||
</executions>
|
</executions>
|
||||||
|
|||||||
@@ -0,0 +1,64 @@
|
|||||||
|
package com.starry.admin.common.config;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Collections;
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
import org.springframework.web.cors.CorsConfiguration;
|
||||||
|
import org.springframework.web.cors.CorsConfigurationSource;
|
||||||
|
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
|
||||||
|
import org.springframework.web.filter.CorsFilter;
|
||||||
|
import org.springframework.web.servlet.config.annotation.CorsRegistry;
|
||||||
|
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 跨域配置
|
||||||
|
*
|
||||||
|
* @author admin
|
||||||
|
*/
|
||||||
|
@Configuration
|
||||||
|
public class CorsConfig implements WebMvcConfigurer {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void addCorsMappings(CorsRegistry registry) {
|
||||||
|
registry.addMapping("/**")
|
||||||
|
.allowedOriginPatterns("*")
|
||||||
|
.allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS")
|
||||||
|
.allowedHeaders("*")
|
||||||
|
.allowCredentials(true)
|
||||||
|
.exposedHeaders("*")
|
||||||
|
.maxAge(3600);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public CorsConfigurationSource corsConfigurationSource() {
|
||||||
|
CorsConfiguration configuration = new CorsConfiguration();
|
||||||
|
|
||||||
|
// 允许所有域名进行跨域调用
|
||||||
|
configuration.setAllowedOriginPatterns(Collections.singletonList("*"));
|
||||||
|
|
||||||
|
// 允许所有请求头
|
||||||
|
configuration.setAllowedHeaders(Collections.singletonList("*"));
|
||||||
|
|
||||||
|
// 允许所有HTTP方法
|
||||||
|
configuration.setAllowedMethods(Arrays.asList("GET", "POST", "PUT", "DELETE", "OPTIONS", "PATCH", "HEAD"));
|
||||||
|
|
||||||
|
// 允许发送Cookie
|
||||||
|
configuration.setAllowCredentials(true);
|
||||||
|
|
||||||
|
// 暴露哪些头部信息(因为跨域访问默认不能获取全部头部信息)
|
||||||
|
configuration.setExposedHeaders(Arrays.asList("*"));
|
||||||
|
|
||||||
|
// 预检请求的缓存时间(秒)
|
||||||
|
configuration.setMaxAge(3600L);
|
||||||
|
|
||||||
|
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
|
||||||
|
source.registerCorsConfiguration("/**", configuration);
|
||||||
|
return source;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public CorsFilter corsFilter() {
|
||||||
|
return new CorsFilter(corsConfigurationSource());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,207 @@
|
|||||||
|
package com.starry.admin.common.filter;
|
||||||
|
|
||||||
|
import com.starry.admin.modules.clerk.module.entity.PlayClerkUserInfoEntity;
|
||||||
|
import com.starry.admin.modules.clerk.service.impl.PlayClerkUserInfoServiceImpl;
|
||||||
|
import com.starry.admin.modules.custom.module.entity.PlayCustomUserInfoEntity;
|
||||||
|
import com.starry.admin.modules.custom.service.impl.PlayCustomUserInfoServiceImpl;
|
||||||
|
import com.starry.admin.modules.weichat.service.WxTokenService;
|
||||||
|
import com.starry.common.context.CustomSecurityContextHolder;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.UUID;
|
||||||
|
import javax.annotation.Resource;
|
||||||
|
import javax.servlet.*;
|
||||||
|
import javax.servlet.http.HttpServletRequest;
|
||||||
|
import javax.servlet.http.HttpServletResponse;
|
||||||
|
import org.slf4j.MDC;
|
||||||
|
import org.springframework.context.annotation.Lazy;
|
||||||
|
import org.springframework.core.Ordered;
|
||||||
|
import org.springframework.core.annotation.Order;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 请求关联ID过滤器,为每个HTTP请求生成唯一的跟踪ID
|
||||||
|
* 用于日志关联和请求链路追踪
|
||||||
|
*
|
||||||
|
* @author Claude
|
||||||
|
*/
|
||||||
|
@Component
|
||||||
|
@Order(Ordered.HIGHEST_PRECEDENCE)
|
||||||
|
public class CorrelationFilter implements Filter {
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
@Lazy
|
||||||
|
private WxTokenService wxTokenService;
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
@Lazy
|
||||||
|
private PlayClerkUserInfoServiceImpl clerkUserInfoService;
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
@Lazy
|
||||||
|
private PlayCustomUserInfoServiceImpl customUserInfoService;
|
||||||
|
|
||||||
|
public static final String CORRELATION_ID_HEADER = "X-Correlation-ID";
|
||||||
|
public static final String CORRELATION_ID_MDC_KEY = "correlationId";
|
||||||
|
public static final String USER_ID_MDC_KEY = "userId";
|
||||||
|
public static final String TENANT_ID_MDC_KEY = "tenantId";
|
||||||
|
public static final String TENANT_KEY_MDC_KEY = "tenantKey";
|
||||||
|
public static final String REQUEST_URI_MDC_KEY = "requestUri";
|
||||||
|
public static final String REQUEST_METHOD_MDC_KEY = "requestMethod";
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
|
||||||
|
throws IOException, ServletException {
|
||||||
|
|
||||||
|
if (!(request instanceof HttpServletRequest)) {
|
||||||
|
chain.doFilter(request, response);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
HttpServletRequest httpRequest = (HttpServletRequest) request;
|
||||||
|
HttpServletResponse httpResponse = (HttpServletResponse) response;
|
||||||
|
|
||||||
|
try {
|
||||||
|
// 生成或获取关联ID
|
||||||
|
String correlationId = getOrGenerateCorrelationId(httpRequest);
|
||||||
|
|
||||||
|
// 设置MDC上下文
|
||||||
|
MDC.put(CORRELATION_ID_MDC_KEY, correlationId);
|
||||||
|
MDC.put(REQUEST_URI_MDC_KEY, httpRequest.getRequestURI());
|
||||||
|
MDC.put(REQUEST_METHOD_MDC_KEY, httpRequest.getMethod());
|
||||||
|
|
||||||
|
// 提取并设置租户信息
|
||||||
|
String tenantKey = httpRequest.getHeader("tenantkey");
|
||||||
|
if (tenantKey != null && !tenantKey.trim().isEmpty()) {
|
||||||
|
MDC.put(TENANT_KEY_MDC_KEY, tenantKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 尝试从安全上下文获取租户ID
|
||||||
|
try {
|
||||||
|
String tenantId = CustomSecurityContextHolder.getTenantId();
|
||||||
|
if (tenantId != null && !tenantId.trim().isEmpty() && !"9999".equals(tenantId)) {
|
||||||
|
MDC.put(TENANT_ID_MDC_KEY, tenantId);
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
// 忽略获取租户ID的异常
|
||||||
|
}
|
||||||
|
|
||||||
|
// 尝试获取用户ID(可能来自JWT或其他认证信息)
|
||||||
|
String userId = extractUserId(httpRequest);
|
||||||
|
if (userId != null) {
|
||||||
|
MDC.put(USER_ID_MDC_KEY, userId);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 将关联ID添加到响应头
|
||||||
|
httpResponse.setHeader(CORRELATION_ID_HEADER, correlationId);
|
||||||
|
|
||||||
|
// 继续过滤器链
|
||||||
|
chain.doFilter(request, response);
|
||||||
|
|
||||||
|
} finally {
|
||||||
|
// 清理MDC上下文
|
||||||
|
MDC.clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取或生成关联ID
|
||||||
|
* 优先从请求头获取,如果没有则生成新的
|
||||||
|
*/
|
||||||
|
private String getOrGenerateCorrelationId(HttpServletRequest request) {
|
||||||
|
String correlationId = request.getHeader(CORRELATION_ID_HEADER);
|
||||||
|
if (correlationId == null || correlationId.trim().isEmpty()) {
|
||||||
|
correlationId = "REQ-" + UUID.randomUUID().toString().substring(0, 8);
|
||||||
|
}
|
||||||
|
return correlationId;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 尝试从请求中提取用户ID
|
||||||
|
* 根据项目的认证机制:支持微信端(clerk/custom token)和管理端(Authorization header)
|
||||||
|
*/
|
||||||
|
private String extractUserId(HttpServletRequest request) {
|
||||||
|
try {
|
||||||
|
// 1. 微信端 - Clerk用户认证
|
||||||
|
String clerkToken = request.getHeader("clerkusertoken");
|
||||||
|
if (clerkToken != null && !clerkToken.trim().isEmpty()) {
|
||||||
|
return extractUserIdFromWxToken(clerkToken, "clerk");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. 微信端 - Custom用户认证
|
||||||
|
String customToken = request.getHeader("customusertoken");
|
||||||
|
if (customToken != null && !customToken.trim().isEmpty()) {
|
||||||
|
return extractUserIdFromWxToken(customToken, "custom");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. 管理端 - JWT Bearer token认证
|
||||||
|
String authHeader = request.getHeader("Authorization");
|
||||||
|
if (authHeader != null && authHeader.startsWith("Bearer ")) {
|
||||||
|
return extractUserIdFromJwtToken(authHeader);
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
|
// 如果解析失败,不影响主流程,只是记录日志时没有用户ID
|
||||||
|
// 可以选择记录debug日志,但不抛异常
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 从微信token中提取真实用户ID
|
||||||
|
*/
|
||||||
|
private String extractUserIdFromWxToken(String token, String userType) {
|
||||||
|
try {
|
||||||
|
// 使用WxTokenService解析JWT token获取真实用户ID
|
||||||
|
String userId = wxTokenService.getWxUserIdByToken(token);
|
||||||
|
|
||||||
|
if (userId != null) {
|
||||||
|
// 根据用户类型获取更多用户信息(可选)
|
||||||
|
if ("clerk".equals(userType)) {
|
||||||
|
PlayClerkUserInfoEntity clerkUser = clerkUserInfoService.selectById(userId);
|
||||||
|
if (clerkUser != null) {
|
||||||
|
// 返回格式: clerk_userId 或者可以包含昵称等信息
|
||||||
|
return "clerk_" + userId;
|
||||||
|
}
|
||||||
|
} else if ("custom".equals(userType)) {
|
||||||
|
PlayCustomUserInfoEntity customUser = customUserInfoService.selectById(userId);
|
||||||
|
if (customUser != null) {
|
||||||
|
return "custom_" + userId;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果查询用户详情失败,至少返回基础用户ID
|
||||||
|
return userType + "_" + userId;
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
|
// Token解析失败,可能是过期或无效token,不影响主流程
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 从管理端JWT token中提取用户ID
|
||||||
|
*/
|
||||||
|
private String extractUserIdFromJwtToken(String authHeader) {
|
||||||
|
try {
|
||||||
|
// 管理端的JWT解析比较复杂,这里先返回标识
|
||||||
|
// 实际应该通过JwtToken组件或SecurityUtils获取当前登录用户信息
|
||||||
|
return "admin_user";
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void init(FilterConfig filterConfig) throws ServletException {
|
||||||
|
// 初始化逻辑
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void destroy() {
|
||||||
|
// 清理逻辑
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -79,9 +79,9 @@ public class MyMetaObjectHandler implements MetaObjectHandler {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (Exception ignored) {
|
} catch (Exception ignored) {
|
||||||
if (SecurityUtils.isLogin()) {
|
// if (SecurityUtils.isLogin()) {
|
||||||
return SecurityUtils.getUserId();
|
// return SecurityUtils.getUserId();
|
||||||
}
|
// }
|
||||||
}
|
}
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,9 +24,6 @@ import org.springframework.security.core.userdetails.UsernameNotFoundException;
|
|||||||
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
|
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
|
||||||
import org.springframework.security.crypto.password.PasswordEncoder;
|
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||||
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
|
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
|
||||||
import org.springframework.web.cors.CorsConfiguration;
|
|
||||||
import org.springframework.web.cors.CorsConfigurationSource;
|
|
||||||
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author admin
|
* @author admin
|
||||||
@@ -62,7 +59,7 @@ public class SpringSecurityConfig extends WebSecurityConfigurerAdapter {
|
|||||||
.antMatchers("/health", "/health/**").permitAll()
|
.antMatchers("/health", "/health/**").permitAll()
|
||||||
// 跨域请求会先进行一次options请求
|
// 跨域请求会先进行一次options请求
|
||||||
.antMatchers(HttpMethod.OPTIONS).permitAll().anyRequest()// 除上面外的所有请求全部需要鉴权认证
|
.antMatchers(HttpMethod.OPTIONS).permitAll().anyRequest()// 除上面外的所有请求全部需要鉴权认证
|
||||||
.authenticated().and().cors().configurationSource(this.corsConfigurationSource());
|
.authenticated().and().cors();
|
||||||
// 禁用缓存
|
// 禁用缓存
|
||||||
httpSecurity.headers().cacheControl();
|
httpSecurity.headers().cacheControl();
|
||||||
// 添加Logout filter
|
// 添加Logout filter
|
||||||
@@ -74,18 +71,6 @@ public class SpringSecurityConfig extends WebSecurityConfigurerAdapter {
|
|||||||
.authenticationEntryPoint(customAuthenticationEntryPoint);
|
.authenticationEntryPoint(customAuthenticationEntryPoint);
|
||||||
}
|
}
|
||||||
|
|
||||||
private CorsConfigurationSource corsConfigurationSource() {
|
|
||||||
CorsConfiguration corsConfiguration = new CorsConfiguration();
|
|
||||||
corsConfiguration.setAllowCredentials(true);
|
|
||||||
corsConfiguration.addAllowedHeader("*"); // 这个得加上,一些复杂的请求方式会带有header,不加上跨域会失效。
|
|
||||||
corsConfiguration.addAllowedMethod("*");
|
|
||||||
corsConfiguration.addExposedHeader("*");
|
|
||||||
corsConfiguration.addAllowedOriginPattern("*");
|
|
||||||
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
|
|
||||||
source.registerCorsConfiguration("/**", corsConfiguration);
|
|
||||||
return source;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
|
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
|
||||||
auth.userDetailsService(userDetailsService()).passwordEncoder(passwordEncoder());
|
auth.userDetailsService(userDetailsService()).passwordEncoder(passwordEncoder());
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
package com.starry.admin.common.task;
|
package com.starry.admin.common.task;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
|
||||||
|
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
|
||||||
import com.starry.admin.modules.clerk.module.entity.PlayClerkUserInfoEntity;
|
import com.starry.admin.modules.clerk.module.entity.PlayClerkUserInfoEntity;
|
||||||
import com.starry.admin.modules.clerk.module.entity.PlayClerkWagesDetailsInfoEntity;
|
import com.starry.admin.modules.clerk.module.entity.PlayClerkWagesDetailsInfoEntity;
|
||||||
import com.starry.admin.modules.clerk.module.entity.PlayClerkWagesInfoEntity;
|
import com.starry.admin.modules.clerk.module.entity.PlayClerkWagesInfoEntity;
|
||||||
@@ -18,6 +20,8 @@ import java.time.LocalDateTime;
|
|||||||
import java.time.temporal.ChronoUnit;
|
import java.time.temporal.ChronoUnit;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import javax.annotation.Resource;
|
import javax.annotation.Resource;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.scheduling.annotation.Scheduled;
|
||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -26,11 +30,10 @@ import org.springframework.stereotype.Component;
|
|||||||
* @author admin
|
* @author admin
|
||||||
* @since 2024/5/28 下午2:42
|
* @since 2024/5/28 下午2:42
|
||||||
**/
|
**/
|
||||||
|
@Slf4j
|
||||||
@Component
|
@Component
|
||||||
public class ClerkWagesSettlementTask {
|
public class ClerkWagesSettlementTask {
|
||||||
|
|
||||||
@Resource
|
|
||||||
ISysTenantService sysTenantService;
|
|
||||||
|
|
||||||
@Resource
|
@Resource
|
||||||
private IPlayClerkUserInfoService clerkUserInfoService;
|
private IPlayClerkUserInfoService clerkUserInfoService;
|
||||||
@@ -47,31 +50,33 @@ public class ClerkWagesSettlementTask {
|
|||||||
/**
|
/**
|
||||||
* 每分钟查询未结算订单 如果订单完成时间超过24小时,可进行结算,生成工资
|
* 每分钟查询未结算订单 如果订单完成时间超过24小时,可进行结算,生成工资
|
||||||
*/
|
*/
|
||||||
// @Scheduled(cron = "0 0/1 * * * ?")
|
@Scheduled(cron = "0 0/10 * * * ?")
|
||||||
public void dailyRanking() {
|
public void dailyRanking() {
|
||||||
// 1、查询所有的租户信息
|
// 1、查询所有的租户信息
|
||||||
List<SysTenantEntity> tenantEntities = sysTenantService.listAll();
|
|
||||||
for (SysTenantEntity tenantEntity : tenantEntities) {
|
|
||||||
SecurityUtils.setTenantId(tenantEntity.getTenantId());
|
|
||||||
List<PlayClerkUserInfoEntity> clerkUserReturnVos = clerkUserInfoService.listAll();
|
List<PlayClerkUserInfoEntity> clerkUserReturnVos = clerkUserInfoService.listAll();
|
||||||
// 生成每个人的工资信息
|
// 生成每个人的工资信息
|
||||||
for (PlayClerkUserInfoEntity clerkUserInfo : clerkUserReturnVos) {
|
for (PlayClerkUserInfoEntity clerkUserInfo : clerkUserReturnVos) {
|
||||||
|
try {
|
||||||
updateClerkWagesInfo(clerkUserInfo.getId());
|
updateClerkWagesInfo(clerkUserInfo.getId());
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("订单结算异常:" + e.getMessage());
|
||||||
|
log.error(e.getMessage(),e);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 活动24小时前完成的订单,对订单进行结算
|
* 活动24小时前完成的订单,对订单进行结算
|
||||||
*
|
*
|
||||||
* @param clerkId
|
* @param clerkId 店员ID
|
||||||
* 店员ID
|
|
||||||
*/
|
*/
|
||||||
public void updateClerkWagesInfo(String clerkId) {
|
public void updateClerkWagesInfo(String clerkId) {
|
||||||
// 获得24小时前完成,并且未结算的订单
|
// 获得24小时前完成,并且未结算的订单
|
||||||
LocalDateTime lastTime = LocalDateTime.now().minusDays(1);
|
LocalDateTime lastTime = LocalDateTime.now().minusDays(1);
|
||||||
// 查询当前店员上次结算工资到现在的订单
|
// 查询当前店员上次结算工资到现在的订单
|
||||||
List<PlayOrderInfoEntity> orderInfoEntities = playOrderInfoService.listByEndTime(clerkId, lastTime);
|
List<PlayOrderInfoEntity> orderInfoEntities = playOrderInfoService.listByEndTime(clerkId, lastTime);
|
||||||
|
if(CollectionUtils.isEmpty(orderInfoEntities)) return;
|
||||||
PlayClerkWagesInfoEntity entity = playClerkWagesInfoService.getLastSettlement(clerkId);
|
PlayClerkWagesInfoEntity entity = playClerkWagesInfoService.getLastSettlement(clerkId);
|
||||||
// 更新订单信息
|
// 更新订单信息
|
||||||
updateClerkWagesInfo(clerkId, entity == null ? IdUtils.getUuid() : entity.getId(), orderInfoEntities);
|
updateClerkWagesInfo(clerkId, entity == null ? IdUtils.getUuid() : entity.getId(), orderInfoEntities);
|
||||||
@@ -80,8 +85,7 @@ public class ClerkWagesSettlementTask {
|
|||||||
/**
|
/**
|
||||||
* 更新最新一次工资统计信息 更新订单记录
|
* 更新最新一次工资统计信息 更新订单记录
|
||||||
*
|
*
|
||||||
* @param orderInfoEntities
|
* @param orderInfoEntities 订单列表
|
||||||
* 订单列表
|
|
||||||
*/
|
*/
|
||||||
public void updateClerkWagesInfo(String clerkId, String wagesId, List<PlayOrderInfoEntity> orderInfoEntities) {
|
public void updateClerkWagesInfo(String clerkId, String wagesId, List<PlayOrderInfoEntity> orderInfoEntities) {
|
||||||
// 修改订单状态并且新增订单结算详情
|
// 修改订单状态并且新增订单结算详情
|
||||||
@@ -89,7 +93,10 @@ public class ClerkWagesSettlementTask {
|
|||||||
// 修改订单状态
|
// 修改订单状态
|
||||||
orderInfo.setOrderSettlementState("1");
|
orderInfo.setOrderSettlementState("1");
|
||||||
orderInfo.setOrderSettlementTime(LocalDateTime.now());
|
orderInfo.setOrderSettlementTime(LocalDateTime.now());
|
||||||
playOrderInfoService.update(orderInfo);
|
playOrderInfoService.update(Wrappers.lambdaUpdate(PlayOrderInfoEntity.class)
|
||||||
|
.eq(PlayOrderInfoEntity::getId, orderInfo.getId())
|
||||||
|
.set(PlayOrderInfoEntity::getOrderSettlementState, "1")
|
||||||
|
.set(PlayOrderInfoEntity::getOrderSettlementTime, LocalDateTime.now()));
|
||||||
|
|
||||||
PlayClerkWagesDetailsInfoEntity wagesDetailsInfo = playClerkWagesDetailsInfoService
|
PlayClerkWagesDetailsInfoEntity wagesDetailsInfo = playClerkWagesDetailsInfoService
|
||||||
.selectByOrderId(orderInfo.getId());
|
.selectByOrderId(orderInfo.getId());
|
||||||
@@ -103,6 +110,7 @@ public class ClerkWagesSettlementTask {
|
|||||||
wagesDetailsInfo.setOrderId(orderInfo.getId());
|
wagesDetailsInfo.setOrderId(orderInfo.getId());
|
||||||
wagesDetailsInfo.setOrderNo(orderInfo.getOrderNo());
|
wagesDetailsInfo.setOrderNo(orderInfo.getOrderNo());
|
||||||
wagesDetailsInfo.setFinalAmount(orderInfo.getFinalAmount());
|
wagesDetailsInfo.setFinalAmount(orderInfo.getFinalAmount());
|
||||||
|
wagesDetailsInfo.setTenantId(orderInfo.getTenantId());
|
||||||
wagesDetailsInfo.setEstimatedRevenue(orderInfo.getEstimatedRevenue());
|
wagesDetailsInfo.setEstimatedRevenue(orderInfo.getEstimatedRevenue());
|
||||||
wagesDetailsInfo.setEndOrderTime(orderInfo.getOrderEndTime());
|
wagesDetailsInfo.setEndOrderTime(orderInfo.getOrderEndTime());
|
||||||
playClerkWagesDetailsInfoService.saveOrUpdate(wagesDetailsInfo);
|
playClerkWagesDetailsInfoService.saveOrUpdate(wagesDetailsInfo);
|
||||||
@@ -138,6 +146,7 @@ public class ClerkWagesSettlementTask {
|
|||||||
wagesInfo.setFinalAmount(finalAmount);
|
wagesInfo.setFinalAmount(finalAmount);
|
||||||
wagesInfo.setOrderContinueNumber(orderContinueNumber);
|
wagesInfo.setOrderContinueNumber(orderContinueNumber);
|
||||||
wagesInfo.setOrderContinueMoney(orderContinueMoney);
|
wagesInfo.setOrderContinueMoney(orderContinueMoney);
|
||||||
|
wagesInfo.setTenantId(orderInfoEntities.get(0).getTenantId());
|
||||||
wagesInfo.setOrdersExpiredNumber(ordersExpiredNumber);
|
wagesInfo.setOrdersExpiredNumber(ordersExpiredNumber);
|
||||||
wagesInfo.setSettlementDate(LocalDate.now());
|
wagesInfo.setSettlementDate(LocalDate.now());
|
||||||
wagesInfo.setEstimatedRevenue(estimatedRevenue);
|
wagesInfo.setEstimatedRevenue(estimatedRevenue);
|
||||||
|
|||||||
@@ -72,12 +72,9 @@ public class OrderRankingSettlementTask {
|
|||||||
/**
|
/**
|
||||||
* 生产排行信息 1、查询所有的租户信息 2、查询每个租户的用户,以及他们在对应时间段的订单 3、根据订单生产排行信息
|
* 生产排行信息 1、查询所有的租户信息 2、查询每个租户的用户,以及他们在对应时间段的订单 3、根据订单生产排行信息
|
||||||
*
|
*
|
||||||
* @param startTime
|
* @param startTime 接单开始时间
|
||||||
* 接单开始时间
|
* @param endTime 接单截至时间
|
||||||
* @param endTime
|
* @param weeklyRanking weeklyRanking 日排名还是周排名(0:每日排名;1:每周排名)
|
||||||
* 接单截至时间
|
|
||||||
* @param weeklyRanking
|
|
||||||
* weeklyRanking 日排名还是周排名(0:每日排名;1:每周排名)
|
|
||||||
* @author admin
|
* @author admin
|
||||||
* @since 2024/6/12 15:21
|
* @since 2024/6/12 15:21
|
||||||
**/
|
**/
|
||||||
@@ -95,8 +92,9 @@ public class OrderRankingSettlementTask {
|
|||||||
// 生成每个人的订单排行信息
|
// 生成每个人的订单排行信息
|
||||||
for (PlayClerkUserInfoEntity clerkUserInfo : clerkUserReturnVos) {
|
for (PlayClerkUserInfoEntity clerkUserInfo : clerkUserReturnVos) {
|
||||||
// 生成订单排行数据
|
// 生成订单排行数据
|
||||||
clerkRankingInfoEntities.add(
|
PlayClerkRankingInfoEntity clerkRanking = getClerkRanking(clerkUserInfo.getId(), startTime, endTime, weeklyRanking, newSerialNumber);
|
||||||
getClerkRanking(clerkUserInfo.getId(), startTime, endTime, weeklyRanking, newSerialNumber));
|
clerkRanking.setTenantId(tenantEntity.getTenantId());
|
||||||
|
clerkRankingInfoEntities.add(clerkRanking);
|
||||||
}
|
}
|
||||||
// 更新排行名次
|
// 更新排行名次
|
||||||
clerkRankingInfoEntities.sort((p1, p2) -> p2.getOrderNumber() - p1.getOrderNumber());
|
clerkRankingInfoEntities.sort((p1, p2) -> p2.getOrderNumber() - p1.getOrderNumber());
|
||||||
@@ -104,8 +102,7 @@ public class OrderRankingSettlementTask {
|
|||||||
PlayClerkRankingInfoEntity item = clerkRankingInfoEntities.get(i);
|
PlayClerkRankingInfoEntity item = clerkRankingInfoEntities.get(i);
|
||||||
item.setRankingIndex(i + 1);
|
item.setRankingIndex(i + 1);
|
||||||
if (i > 0) {
|
if (i > 0) {
|
||||||
item.setPreviousMoney(
|
item.setPreviousMoney(clerkRankingInfoEntities.get(i - 1).getOrderMoney().subtract(item.getOrderMoney()));
|
||||||
clerkRankingInfoEntities.get(i - 1).getOrderMoney().subtract(item.getOrderMoney()));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// 排名数据生成后,将以往排名状态标记为历史排名
|
// 排名数据生成后,将以往排名状态标记为历史排名
|
||||||
@@ -118,21 +115,15 @@ public class OrderRankingSettlementTask {
|
|||||||
/**
|
/**
|
||||||
* 生产当前排行信息
|
* 生产当前排行信息
|
||||||
*
|
*
|
||||||
* @param clerkId
|
* @param clerkId 店员ID
|
||||||
* 店员ID
|
* @param startTime 排行开始统计日期 yyyy-mm-dd 00:00:00
|
||||||
* @param startTime
|
* @param endTime 排行结束统计日期 yyyy-mm-dd 23:59:59
|
||||||
* 排行开始统计日期 yyyy-mm-dd 00:00:00
|
* @param weeklyRanking 日排名还是周排名(0:每日排名;1:每周排名)
|
||||||
* @param endTime
|
* @param serialNumber 查询当前租户、当前排行、的最后一次统计编号
|
||||||
* 排行结束统计日期 yyyy-mm-dd 23:59:59
|
|
||||||
* @param weeklyRanking
|
|
||||||
* 日排名还是周排名(0:每日排名;1:每周排名)
|
|
||||||
* @param serialNumber
|
|
||||||
* 查询当前租户、当前排行、的最后一次统计编号
|
|
||||||
* @author admin
|
* @author admin
|
||||||
* @since 2024/6/7 11:43
|
* @since 2024/6/7 11:43
|
||||||
**/
|
**/
|
||||||
public PlayClerkRankingInfoEntity getClerkRanking(String clerkId, String startTime, String endTime,
|
public PlayClerkRankingInfoEntity getClerkRanking(String clerkId, String startTime, String endTime, String weeklyRanking, Integer serialNumber) {
|
||||||
String weeklyRanking, Integer serialNumber) {
|
|
||||||
List<PlayOrderInfoEntity> orderInfoEntities = orderInfoService.listByTime(clerkId, startTime, endTime, null);
|
List<PlayOrderInfoEntity> orderInfoEntities = orderInfoService.listByTime(clerkId, startTime, endTime, null);
|
||||||
int orderNumber = orderInfoEntities.size();
|
int orderNumber = orderInfoEntities.size();
|
||||||
BigDecimal orderMoney = BigDecimal.ZERO;
|
BigDecimal orderMoney = BigDecimal.ZERO;
|
||||||
@@ -151,13 +142,8 @@ public class OrderRankingSettlementTask {
|
|||||||
ordersExpiredNumber++;
|
ordersExpiredNumber++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
BigDecimal orderContinueProportion = orderNumber == 0
|
BigDecimal orderContinueProportion = orderNumber == 0 ? BigDecimal.ZERO : new BigDecimal(ordersExpiredNumber).divide(new BigDecimal(orderNumber), 4, RoundingMode.HALF_UP).add(new BigDecimal(100));
|
||||||
? BigDecimal.ZERO
|
BigDecimal averageUnitPrice = customIds.isEmpty() ? BigDecimal.ZERO : orderMoney.divide(new BigDecimal(customIds.size()), 4, RoundingMode.HALF_UP);
|
||||||
: new BigDecimal(ordersExpiredNumber).divide(new BigDecimal(orderNumber), 4, RoundingMode.HALF_UP)
|
|
||||||
.add(new BigDecimal(100));
|
|
||||||
BigDecimal averageUnitPrice = customIds.isEmpty()
|
|
||||||
? BigDecimal.ZERO
|
|
||||||
: orderMoney.divide(new BigDecimal(customIds.size()), 4, RoundingMode.HALF_UP);
|
|
||||||
PlayClerkRankingInfoEntity rankingInfo = new PlayClerkRankingInfoEntity();
|
PlayClerkRankingInfoEntity rankingInfo = new PlayClerkRankingInfoEntity();
|
||||||
rankingInfo.setId(IdUtils.getUuid());
|
rankingInfo.setId(IdUtils.getUuid());
|
||||||
rankingInfo.setClerkId(clerkId);
|
rankingInfo.setClerkId(clerkId);
|
||||||
|
|||||||
@@ -0,0 +1,65 @@
|
|||||||
|
package com.starry.admin.modules.clerk.module.enums;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonCreator;
|
||||||
|
import com.fasterxml.jackson.annotation.JsonValue;
|
||||||
|
import io.swagger.annotations.ApiModel;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 店员资料类型枚举
|
||||||
|
*
|
||||||
|
* @author admin
|
||||||
|
* @since 2024-05-19
|
||||||
|
*/
|
||||||
|
@ApiModel(value = "店员资料类型", description = "店员资料审核的数据类型枚举")
|
||||||
|
public enum ClerkDataType {
|
||||||
|
|
||||||
|
NICKNAME("0", "昵称"),
|
||||||
|
AVATAR("1", "头像"),
|
||||||
|
PHOTO_ALBUM("2", "相册"),
|
||||||
|
RECORDING("3", "录音");
|
||||||
|
|
||||||
|
private final String code;
|
||||||
|
private final String description;
|
||||||
|
|
||||||
|
ClerkDataType(String code, String description) {
|
||||||
|
this.code = code;
|
||||||
|
this.description = description;
|
||||||
|
}
|
||||||
|
|
||||||
|
@JsonValue
|
||||||
|
public String getCode() {
|
||||||
|
return code;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getDescription() {
|
||||||
|
return description;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据代码获取枚举
|
||||||
|
* @param code 代码
|
||||||
|
* @return 枚举值
|
||||||
|
*/
|
||||||
|
@JsonCreator
|
||||||
|
public static ClerkDataType fromCode(String code) {
|
||||||
|
if (code == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
for (ClerkDataType type : ClerkDataType.values()) {
|
||||||
|
if (type.code.equals(code)) {
|
||||||
|
return type;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
throw new IllegalArgumentException("Unknown code: " + code);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据代码获取描述
|
||||||
|
* @param code 代码
|
||||||
|
* @return 描述
|
||||||
|
*/
|
||||||
|
public static String getDescriptionByCode(String code) {
|
||||||
|
ClerkDataType type = fromCode(code);
|
||||||
|
return type != null ? type.description : null;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,5 +1,7 @@
|
|||||||
package com.starry.admin.modules.clerk.module.vo;
|
package com.starry.admin.modules.clerk.module.vo;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||||
|
import com.starry.admin.modules.clerk.module.enums.ClerkDataType;
|
||||||
import io.swagger.annotations.ApiModel;
|
import io.swagger.annotations.ApiModel;
|
||||||
import io.swagger.annotations.ApiModelProperty;
|
import io.swagger.annotations.ApiModelProperty;
|
||||||
import java.time.LocalDateTime;
|
import java.time.LocalDateTime;
|
||||||
@@ -46,6 +48,12 @@ public class PlayClerkDataReviewReturnVo {
|
|||||||
@ApiModelProperty(value = "资料类型", example = "1", notes = "0:昵称;1:头像;2:相册;3:录音")
|
@ApiModelProperty(value = "资料类型", example = "1", notes = "0:昵称;1:头像;2:相册;3:录音")
|
||||||
private String dataType;
|
private String dataType;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 资料类型枚举(新增字段,用于类型安全)
|
||||||
|
*/
|
||||||
|
@ApiModelProperty(value = "资料类型枚举", example = "AVATAR", notes = "NICKNAME:昵称, AVATAR:头像, PHOTO_ALBUM:相册, RECORDING:录音")
|
||||||
|
private ClerkDataType dataTypeEnum;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 资料内容
|
* 资料内容
|
||||||
*/
|
*/
|
||||||
@@ -88,4 +96,31 @@ public class PlayClerkDataReviewReturnVo {
|
|||||||
@ApiModelProperty(value = "备注", example = "资料清晰可见")
|
@ApiModelProperty(value = "备注", example = "资料清晰可见")
|
||||||
private String remark;
|
private String remark;
|
||||||
|
|
||||||
|
// 自定义setter方法保持两个字段的同步
|
||||||
|
public void setDataType(String dataType) {
|
||||||
|
this.dataType = dataType;
|
||||||
|
try {
|
||||||
|
this.dataTypeEnum = ClerkDataType.fromCode(dataType);
|
||||||
|
} catch (Exception e) {
|
||||||
|
this.dataTypeEnum = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setDataTypeEnum(ClerkDataType dataTypeEnum) {
|
||||||
|
this.dataTypeEnum = dataTypeEnum;
|
||||||
|
this.dataType = dataTypeEnum != null ? dataTypeEnum.getCode() : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ClerkDataType getDataTypeEnum() {
|
||||||
|
// 如果枚举字段为null但字符串字段有值,尝试从字符串转换
|
||||||
|
if (dataTypeEnum == null && dataType != null) {
|
||||||
|
try {
|
||||||
|
dataTypeEnum = ClerkDataType.fromCode(dataType);
|
||||||
|
} catch (Exception e) {
|
||||||
|
// 转换失败时保持null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return dataTypeEnum;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -42,7 +42,7 @@ public class PlayClerkWagesInfoServiceImpl extends ServiceImpl<PlayClerkWagesInf
|
|||||||
public PlayClerkWagesInfoEntity selectCurrentPeriodWagesByClerkId(String clerkId) {
|
public PlayClerkWagesInfoEntity selectCurrentPeriodWagesByClerkId(String clerkId) {
|
||||||
LambdaQueryWrapper<PlayClerkWagesInfoEntity> lambdaQueryWrapper = new LambdaQueryWrapper<>();
|
LambdaQueryWrapper<PlayClerkWagesInfoEntity> lambdaQueryWrapper = new LambdaQueryWrapper<>();
|
||||||
lambdaQueryWrapper.eq(PlayClerkWagesInfoEntity::getClerkId, clerkId);
|
lambdaQueryWrapper.eq(PlayClerkWagesInfoEntity::getClerkId, clerkId);
|
||||||
lambdaQueryWrapper.eq(PlayClerkWagesInfoEntity::getHistoricalStatistics, "0");
|
// lambdaQueryWrapper.eq(PlayClerkWagesInfoEntity::getHistoricalStatistics, "0");
|
||||||
return this.baseMapper.selectOne(lambdaQueryWrapper);
|
return this.baseMapper.selectOne(lambdaQueryWrapper);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -50,7 +50,7 @@ public class PlayClerkWagesInfoServiceImpl extends ServiceImpl<PlayClerkWagesInf
|
|||||||
public List<PlayClerkWagesInfoEntity> selectHistoricalWagesByClerkId(String clerkId) {
|
public List<PlayClerkWagesInfoEntity> selectHistoricalWagesByClerkId(String clerkId) {
|
||||||
LambdaQueryWrapper<PlayClerkWagesInfoEntity> lambdaQueryWrapper = new LambdaQueryWrapper<>();
|
LambdaQueryWrapper<PlayClerkWagesInfoEntity> lambdaQueryWrapper = new LambdaQueryWrapper<>();
|
||||||
lambdaQueryWrapper.eq(PlayClerkWagesInfoEntity::getClerkId, clerkId);
|
lambdaQueryWrapper.eq(PlayClerkWagesInfoEntity::getClerkId, clerkId);
|
||||||
lambdaQueryWrapper.eq(PlayClerkWagesInfoEntity::getHistoricalStatistics, "1");
|
// lambdaQueryWrapper.eq(PlayClerkWagesInfoEntity::getHistoricalStatistics, "1");
|
||||||
return this.baseMapper.selectList(lambdaQueryWrapper);
|
return this.baseMapper.selectList(lambdaQueryWrapper);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -58,7 +58,7 @@ public class PlayClerkWagesInfoServiceImpl extends ServiceImpl<PlayClerkWagesInf
|
|||||||
public PlayClerkWagesInfoEntity getLastSettlement(String clerkId) {
|
public PlayClerkWagesInfoEntity getLastSettlement(String clerkId) {
|
||||||
LambdaQueryWrapper<PlayClerkWagesInfoEntity> lambdaQueryWrapper = new LambdaQueryWrapper<>();
|
LambdaQueryWrapper<PlayClerkWagesInfoEntity> lambdaQueryWrapper = new LambdaQueryWrapper<>();
|
||||||
lambdaQueryWrapper.eq(PlayClerkWagesInfoEntity::getClerkId, clerkId);
|
lambdaQueryWrapper.eq(PlayClerkWagesInfoEntity::getClerkId, clerkId);
|
||||||
lambdaQueryWrapper.eq(PlayClerkWagesInfoEntity::getHistoricalStatistics, "0");
|
// lambdaQueryWrapper.eq(PlayClerkWagesInfoEntity::getHistoricalStatistics, "0");
|
||||||
lambdaQueryWrapper.orderByDesc(PlayClerkWagesInfoEntity::getEndCountDate);
|
lambdaQueryWrapper.orderByDesc(PlayClerkWagesInfoEntity::getEndCountDate);
|
||||||
return this.baseMapper.selectOne(lambdaQueryWrapper);
|
return this.baseMapper.selectOne(lambdaQueryWrapper);
|
||||||
}
|
}
|
||||||
@@ -67,11 +67,11 @@ public class PlayClerkWagesInfoServiceImpl extends ServiceImpl<PlayClerkWagesInf
|
|||||||
public LocalDate getLastSettlementTime(String clerkId) {
|
public LocalDate getLastSettlementTime(String clerkId) {
|
||||||
LambdaQueryWrapper<PlayClerkWagesInfoEntity> lambdaQueryWrapper = new LambdaQueryWrapper<>();
|
LambdaQueryWrapper<PlayClerkWagesInfoEntity> lambdaQueryWrapper = new LambdaQueryWrapper<>();
|
||||||
lambdaQueryWrapper.eq(PlayClerkWagesInfoEntity::getClerkId, clerkId);
|
lambdaQueryWrapper.eq(PlayClerkWagesInfoEntity::getClerkId, clerkId);
|
||||||
lambdaQueryWrapper.eq(PlayClerkWagesInfoEntity::getHistoricalStatistics, "1");
|
// lambdaQueryWrapper.eq(PlayClerkWagesInfoEntity::getHistoricalStatistics, "1");
|
||||||
lambdaQueryWrapper.orderByDesc(PlayClerkWagesInfoEntity::getEndCountDate);
|
lambdaQueryWrapper.orderByDesc(PlayClerkWagesInfoEntity::getEndCountDate);
|
||||||
PlayClerkWagesInfoEntity entity = this.baseMapper.selectOne(lambdaQueryWrapper);
|
PlayClerkWagesInfoEntity entity = this.baseMapper.selectOne(lambdaQueryWrapper);
|
||||||
if (entity == null) {
|
if (entity == null) {
|
||||||
return LocalDate.of(2000, 1, 1);
|
return LocalDate.of(2025, 1, 1);
|
||||||
}
|
}
|
||||||
return entity.getEndCountDate();
|
return entity.getEndCountDate();
|
||||||
}
|
}
|
||||||
@@ -100,7 +100,7 @@ public class PlayClerkWagesInfoServiceImpl extends ServiceImpl<PlayClerkWagesInf
|
|||||||
lambdaWrapper.eq(PlayClerkWagesInfoEntity::getSettlementDate,
|
lambdaWrapper.eq(PlayClerkWagesInfoEntity::getSettlementDate,
|
||||||
LocalDate.parse(vo.getSettlementDate(), DateTimeFormatter.ofPattern("yyyy-MM-dd")));
|
LocalDate.parse(vo.getSettlementDate(), DateTimeFormatter.ofPattern("yyyy-MM-dd")));
|
||||||
}
|
}
|
||||||
lambdaWrapper.eq(PlayClerkWagesInfoEntity::getHistoricalStatistics, "1");
|
// lambdaWrapper.eq(PlayClerkWagesInfoEntity::getHistoricalStatistics, "1");
|
||||||
// 加入组员的筛选
|
// 加入组员的筛选
|
||||||
List<String> clerkIdList = playClerkGroupInfoService.getValidClerkIdList(SecurityUtils.getLoginUser(), null);
|
List<String> clerkIdList = playClerkGroupInfoService.getValidClerkIdList(SecurityUtils.getLoginUser(), null);
|
||||||
lambdaWrapper.in(PlayClerkUserInfoEntity::getId, clerkIdList);
|
lambdaWrapper.in(PlayClerkUserInfoEntity::getId, clerkIdList);
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
package com.starry.admin.modules.order.controller;
|
package com.starry.admin.modules.order.controller;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
|
||||||
import com.starry.admin.common.exception.CustomException;
|
import com.starry.admin.common.exception.CustomException;
|
||||||
import com.starry.admin.modules.clerk.module.entity.PlayClerkUserInfoEntity;
|
import com.starry.admin.modules.clerk.module.entity.PlayClerkUserInfoEntity;
|
||||||
import com.starry.admin.modules.clerk.service.IPlayClerkUserInfoService;
|
import com.starry.admin.modules.clerk.service.IPlayClerkUserInfoService;
|
||||||
@@ -24,6 +25,7 @@ import io.swagger.annotations.ApiParam;
|
|||||||
import io.swagger.annotations.ApiResponse;
|
import io.swagger.annotations.ApiResponse;
|
||||||
import io.swagger.annotations.ApiResponses;
|
import io.swagger.annotations.ApiResponses;
|
||||||
import java.math.BigDecimal;
|
import java.math.BigDecimal;
|
||||||
|
import java.util.List;
|
||||||
import javax.annotation.Resource;
|
import javax.annotation.Resource;
|
||||||
import org.springframework.validation.annotation.Validated;
|
import org.springframework.validation.annotation.Validated;
|
||||||
import org.springframework.web.bind.annotation.*;
|
import org.springframework.web.bind.annotation.*;
|
||||||
@@ -54,6 +56,8 @@ public class PlayOrderInfoController {
|
|||||||
private IPlayOrderRefundInfoService playOrderRefundInfoService;
|
private IPlayOrderRefundInfoService playOrderRefundInfoService;
|
||||||
@Resource
|
@Resource
|
||||||
private IPlayCustomUserInfoService customUserInfoService;
|
private IPlayCustomUserInfoService customUserInfoService;
|
||||||
|
@Resource
|
||||||
|
private IPlayClerkUserInfoService clerkUserInfoService;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 分页查询订单列表
|
* 分页查询订单列表
|
||||||
@@ -66,6 +70,17 @@ public class PlayOrderInfoController {
|
|||||||
return R.ok(orderInfoService.selectOrderInfoPage(vo));
|
return R.ok(orderInfoService.selectOrderInfoPage(vo));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ApiOperation(value = "发送订单微信通知", notes = "发送订单微信通知")
|
||||||
|
@ApiResponses({
|
||||||
|
@ApiResponse(code = 200, message = "操作成功", response = Boolean.class, responseContainer = "Page")})
|
||||||
|
@PostMapping("/sendNotice")
|
||||||
|
public R sendNotice(String orderId) {
|
||||||
|
PlayOrderInfoEntity orderInfo = orderInfoService.selectOrderInfoById(orderId);
|
||||||
|
List<PlayClerkUserInfoEntity> clerkList = clerkUserInfoService.list(Wrappers.lambdaQuery(PlayClerkUserInfoEntity.class).isNotNull(PlayClerkUserInfoEntity::getOpenid).eq(PlayClerkUserInfoEntity::getClerkState, "1").eq(PlayClerkUserInfoEntity::getSex, orderInfo.getSex()));
|
||||||
|
wxCustomMpService.sendCreateOrderMessageBatch(clerkList, orderInfo.getOrderNo(), orderInfo.getOrderMoney().toString(), orderInfo.getCommodityName(), orderId);
|
||||||
|
return R.ok();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 订单退款
|
* 订单退款
|
||||||
*/
|
*/
|
||||||
@@ -125,7 +140,7 @@ public class PlayOrderInfoController {
|
|||||||
PlayCommodityInfoVo commodityInfo = playCommodityInfoService.queryCommodityInfo(orderInfo.getCommodityId(),
|
PlayCommodityInfoVo commodityInfo = playCommodityInfoService.queryCommodityInfo(orderInfo.getCommodityId(),
|
||||||
clerkUserInfo.getLevelId());
|
clerkUserInfo.getLevelId());
|
||||||
wxCustomMpService.sendCreateOrderMessage(clerkUserInfo.getTenantId(), clerkUserInfo.getOpenid(),
|
wxCustomMpService.sendCreateOrderMessage(clerkUserInfo.getTenantId(), clerkUserInfo.getOpenid(),
|
||||||
orderInfo.getOrderNo(), orderInfo.getOrderMoney().toString(), commodityInfo.getCommodityName(),vo.getOrderId() );
|
orderInfo.getOrderNo(), orderInfo.getOrderMoney().toString(), commodityInfo.getCommodityName(), vo.getOrderId());
|
||||||
return R.ok("操作成功");
|
return R.ok("操作成功");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,17 +1,24 @@
|
|||||||
package com.starry.admin.modules.order.job;
|
package com.starry.admin.modules.order.job;
|
||||||
|
|
||||||
|
import cn.hutool.core.date.DateUtil;
|
||||||
import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
|
import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
|
||||||
import com.baomidou.mybatisplus.core.toolkit.StringUtils;
|
import com.baomidou.mybatisplus.core.toolkit.StringUtils;
|
||||||
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
|
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
|
||||||
import com.starry.admin.common.exception.CustomException;
|
import com.starry.admin.common.exception.CustomException;
|
||||||
|
import com.starry.admin.modules.clerk.module.entity.PlayClerkUserInfoEntity;
|
||||||
|
import com.starry.admin.modules.clerk.service.IPlayClerkUserInfoService;
|
||||||
import com.starry.admin.modules.order.mapper.PlayOrderInfoMapper;
|
import com.starry.admin.modules.order.mapper.PlayOrderInfoMapper;
|
||||||
import com.starry.admin.modules.order.module.entity.PlayOrderInfoEntity;
|
import com.starry.admin.modules.order.module.entity.PlayOrderInfoEntity;
|
||||||
import com.starry.admin.modules.weichat.service.WxCustomMpService;
|
import com.starry.admin.modules.weichat.service.WxCustomMpService;
|
||||||
|
import com.starry.common.redis.RedisCache;
|
||||||
import java.time.LocalDateTime;
|
import java.time.LocalDateTime;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
|
import java.util.Date;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import javax.annotation.Resource;
|
import javax.annotation.Resource;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.data.redis.core.RedisTemplate;
|
||||||
|
import org.springframework.data.redis.core.StringRedisTemplate;
|
||||||
import org.springframework.scheduling.annotation.Scheduled;
|
import org.springframework.scheduling.annotation.Scheduled;
|
||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
@@ -23,6 +30,10 @@ public class OrderJob {
|
|||||||
private PlayOrderInfoMapper orderInfoMapper;
|
private PlayOrderInfoMapper orderInfoMapper;
|
||||||
@Resource
|
@Resource
|
||||||
private WxCustomMpService wxCustomMpService;
|
private WxCustomMpService wxCustomMpService;
|
||||||
|
@Resource
|
||||||
|
private IPlayClerkUserInfoService clerkUserInfoService;
|
||||||
|
@Resource
|
||||||
|
public RedisTemplate<String, Object> redisTemplate;
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -64,6 +75,36 @@ public class OrderJob {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Scheduled(fixedRate = 20000, initialDelay = 20000)
|
||||||
|
public void orderNoticeJob() {
|
||||||
|
// 查询出所有应该待接单的订单,超过10分钟还没接单
|
||||||
|
List<PlayOrderInfoEntity> orderInfoEntityList = orderInfoMapper.selectList(Wrappers.lambdaQuery(PlayOrderInfoEntity.class)
|
||||||
|
.eq(PlayOrderInfoEntity::getOrderStatus, "0")
|
||||||
|
.eq(PlayOrderInfoEntity::getPlaceType, "1")
|
||||||
|
.lt(PlayOrderInfoEntity::getCreatedTime, DateUtil.offsetMinute(new Date(), -10)
|
||||||
|
));
|
||||||
|
if (CollectionUtils.isEmpty(orderInfoEntityList)) return;
|
||||||
|
|
||||||
|
orderInfoEntityList.forEach(orderInfo -> {
|
||||||
|
try {
|
||||||
|
// 半小时只能发一次,使用redis做频次限制
|
||||||
|
if (Boolean.TRUE.equals(redisTemplate.hasKey("order_notice_" + orderInfo.getId()))) {
|
||||||
|
log.error("订单已催促,执行跳过:{}", orderInfo.getOrderNo());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
redisTemplate.opsForValue().set("order_notice_" + orderInfo.getId(), "1", 30, java.util.concurrent.TimeUnit.MINUTES);
|
||||||
|
List<PlayClerkUserInfoEntity> clerkList = clerkUserInfoService.list(Wrappers.lambdaQuery(PlayClerkUserInfoEntity.class).isNotNull(PlayClerkUserInfoEntity::getOpenid).eq(PlayClerkUserInfoEntity::getClerkState, "1")
|
||||||
|
.eq(PlayClerkUserInfoEntity::getSex, orderInfo.getSex()).eq(PlayClerkUserInfoEntity::getTenantId, orderInfo.getTenantId()));
|
||||||
|
wxCustomMpService.sendCreateOrderMessageBatch(clerkList, orderInfo.getOrderNo(), orderInfo.getOrderMoney().toString(), orderInfo.getCommodityName(), orderInfo.getId());
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error(e.getMessage(), e);
|
||||||
|
log.error("订单催促失败:{}", orderInfo.getOrderNo());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
private Integer getServiceDuration(String serviceDuration) {
|
private Integer getServiceDuration(String serviceDuration) {
|
||||||
if (StringUtils.isEmpty(serviceDuration)) {
|
if (StringUtils.isEmpty(serviceDuration)) {
|
||||||
throw new CustomException("服务时长不能为空");
|
throw new CustomException("服务时长不能为空");
|
||||||
|
|||||||
@@ -1,44 +1,194 @@
|
|||||||
package com.starry.admin.modules.order.module.constant;
|
package com.starry.admin.modules.order.module.constant;
|
||||||
|
|
||||||
|
import lombok.Getter;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* 订单相关枚举和常量
|
||||||
|
*
|
||||||
* @author admin
|
* @author admin
|
||||||
* @since 2024/5/8 15:41
|
* @since 2024/5/8 15:41
|
||||||
**/
|
*/
|
||||||
public class OrderConstant {
|
public class OrderConstant {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 订单状态-待接单
|
* 订单状态枚举
|
||||||
*
|
*/
|
||||||
* @since 2024/5/8 15:42
|
@Getter
|
||||||
**/
|
public enum OrderStatus {
|
||||||
|
PENDING("0", "已下单(待接单)"),
|
||||||
|
ACCEPTED("1", "已接单(待开始)"),
|
||||||
|
IN_PROGRESS("2", "已开始(服务中)"),
|
||||||
|
COMPLETED("3", "已完成"),
|
||||||
|
CANCELLED("4", "已取消");
|
||||||
|
|
||||||
|
private final String code;
|
||||||
|
private final String description;
|
||||||
|
|
||||||
|
OrderStatus(String code, String description) {
|
||||||
|
this.code = code;
|
||||||
|
this.description = description;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static OrderStatus fromCode(String code) {
|
||||||
|
for (OrderStatus status : values()) {
|
||||||
|
if (status.code.equals(code)) {
|
||||||
|
return status;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
throw new IllegalArgumentException("Unknown order status code: " + code);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 订单类型枚举
|
||||||
|
*/
|
||||||
|
@Getter
|
||||||
|
public enum OrderType {
|
||||||
|
REFUND("-1", "退款订单"),
|
||||||
|
RECHARGE("0", "充值订单"),
|
||||||
|
WITHDRAWAL("1", "提现订单"),
|
||||||
|
NORMAL("2", "普通订单");
|
||||||
|
|
||||||
|
private final String code;
|
||||||
|
private final String description;
|
||||||
|
|
||||||
|
OrderType(String code, String description) {
|
||||||
|
this.code = code;
|
||||||
|
this.description = description;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static OrderType fromCode(String code) {
|
||||||
|
for (OrderType type : values()) {
|
||||||
|
if (type.code.equals(code)) {
|
||||||
|
return type;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
throw new IllegalArgumentException("Unknown order type code: " + code);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 下单类型枚举
|
||||||
|
*/
|
||||||
|
@Getter
|
||||||
|
public enum PlaceType {
|
||||||
|
OTHER("-1", "其他类型"),
|
||||||
|
SPECIFIED("0", "指定单"),
|
||||||
|
RANDOM("1", "随机单"),
|
||||||
|
REWARD("2", "打赏单");
|
||||||
|
|
||||||
|
private final String code;
|
||||||
|
private final String description;
|
||||||
|
|
||||||
|
PlaceType(String code, String description) {
|
||||||
|
this.code = code;
|
||||||
|
this.description = description;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static PlaceType fromCode(String code) {
|
||||||
|
for (PlaceType type : values()) {
|
||||||
|
if (type.code.equals(code)) {
|
||||||
|
return type;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
throw new IllegalArgumentException("Unknown place type code: " + code);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 性别枚举
|
||||||
|
*/
|
||||||
|
@Getter
|
||||||
|
public enum Gender {
|
||||||
|
UNKNOWN("0", "未知"),
|
||||||
|
MALE("1", "男"),
|
||||||
|
FEMALE("2", "女");
|
||||||
|
|
||||||
|
private final String code;
|
||||||
|
private final String description;
|
||||||
|
|
||||||
|
Gender(String code, String description) {
|
||||||
|
this.code = code;
|
||||||
|
this.description = description;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Gender fromCode(String code) {
|
||||||
|
for (Gender gender : values()) {
|
||||||
|
if (gender.code.equals(code)) {
|
||||||
|
return gender;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
throw new IllegalArgumentException("Unknown gender code: " + code);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 打赏类型枚举
|
||||||
|
*/
|
||||||
|
@Getter
|
||||||
|
public enum RewardType {
|
||||||
|
NOT_APPLICABLE("", "非打赏订单"),
|
||||||
|
BALANCE("0", "余额打赏"),
|
||||||
|
GIFT("1", "礼物打赏");
|
||||||
|
|
||||||
|
private final String code;
|
||||||
|
private final String description;
|
||||||
|
|
||||||
|
RewardType(String code, String description) {
|
||||||
|
this.code = code;
|
||||||
|
this.description = description;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static RewardType fromCode(String code) {
|
||||||
|
// Handle null, empty string, or whitespace as NOT_APPLICABLE
|
||||||
|
if (code == null || code.trim().isEmpty()) {
|
||||||
|
return NOT_APPLICABLE;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (RewardType type : values()) {
|
||||||
|
if (code.equals(type.code)) {
|
||||||
|
return type;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
throw new IllegalArgumentException("Unknown reward type code: " + code);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 商品类型枚举
|
||||||
|
*/
|
||||||
|
@Getter
|
||||||
|
public enum CommodityType {
|
||||||
|
GIFT("0", "礼物"),
|
||||||
|
SERVICE("1", "服务");
|
||||||
|
|
||||||
|
private final String code;
|
||||||
|
private final String description;
|
||||||
|
|
||||||
|
CommodityType(String code, String description) {
|
||||||
|
this.code = code;
|
||||||
|
this.description = description;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static CommodityType fromCode(String code) {
|
||||||
|
for (CommodityType type : values()) {
|
||||||
|
if (type.code.equals(code)) {
|
||||||
|
return type;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
throw new IllegalArgumentException("Unknown commodity type code: " + code);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Legacy constants for backward compatibility - consider deprecating
|
||||||
|
@Deprecated
|
||||||
public final static String ORDER_STATUS_0 = "0";
|
public final static String ORDER_STATUS_0 = "0";
|
||||||
|
@Deprecated
|
||||||
/**
|
|
||||||
* 订单状态-待开始
|
|
||||||
*
|
|
||||||
* @since 2024/5/8 15:42
|
|
||||||
**/
|
|
||||||
public final static String ORDER_STATUS_1 = "1";
|
public final static String ORDER_STATUS_1 = "1";
|
||||||
|
@Deprecated
|
||||||
/**
|
|
||||||
* 订单状态-服务中
|
|
||||||
*
|
|
||||||
* @since 2024/5/8 15:42
|
|
||||||
**/
|
|
||||||
public final static String ORDER_STATUS_2 = "2";
|
public final static String ORDER_STATUS_2 = "2";
|
||||||
|
@Deprecated
|
||||||
/**
|
|
||||||
* 订单状态-已完成
|
|
||||||
*
|
|
||||||
* @since 2024/5/8 15:42
|
|
||||||
**/
|
|
||||||
public final static String ORDER_STATUS_3 = "3";
|
public final static String ORDER_STATUS_3 = "3";
|
||||||
|
@Deprecated
|
||||||
/**
|
|
||||||
* 订单状态-已取消
|
|
||||||
*
|
|
||||||
* @since 2024/5/8 15:42
|
|
||||||
**/
|
|
||||||
public final static String ORDER_STATUS_4 = "4";
|
public final static String ORDER_STATUS_4 = "4";
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,45 @@
|
|||||||
|
package com.starry.admin.modules.order.module.dto;
|
||||||
|
|
||||||
|
import com.starry.admin.modules.order.module.constant.OrderConstant;
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import lombok.Builder;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 商品信息值对象
|
||||||
|
*
|
||||||
|
* @author admin
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@Builder
|
||||||
|
public class CommodityInfo {
|
||||||
|
/**
|
||||||
|
* 商品ID
|
||||||
|
*/
|
||||||
|
private String commodityId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 商品类型
|
||||||
|
*/
|
||||||
|
private OrderConstant.CommodityType commodityType;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 商品单价
|
||||||
|
*/
|
||||||
|
private BigDecimal commodityPrice;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 商品属性-服务时长
|
||||||
|
*/
|
||||||
|
private String serviceDuration;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 商品名称
|
||||||
|
*/
|
||||||
|
private String commodityName;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 商品数量
|
||||||
|
*/
|
||||||
|
private String commodityNumber;
|
||||||
|
}
|
||||||
@@ -0,0 +1,128 @@
|
|||||||
|
package com.starry.admin.modules.order.module.dto;
|
||||||
|
|
||||||
|
import com.starry.admin.modules.order.module.constant.OrderConstant;
|
||||||
|
import com.starry.admin.modules.order.module.constant.OrderConstant.RewardType;
|
||||||
|
import javax.validation.Valid;
|
||||||
|
import javax.validation.constraints.NotBlank;
|
||||||
|
import javax.validation.constraints.NotNull;
|
||||||
|
import lombok.Builder;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 订单创建请求对象 - 使用Builder模式替换20+参数的方法
|
||||||
|
*
|
||||||
|
* @author admin
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@Builder
|
||||||
|
public class OrderCreationRequest {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 订单ID
|
||||||
|
*/
|
||||||
|
@NotBlank(message = "订单ID不能为空")
|
||||||
|
private String orderId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 订单编号
|
||||||
|
*/
|
||||||
|
@NotBlank(message = "订单编号不能为空")
|
||||||
|
private String orderNo;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 订单状态
|
||||||
|
*/
|
||||||
|
@NotNull(message = "订单状态不能为空")
|
||||||
|
private OrderConstant.OrderStatus orderStatus;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 订单类型
|
||||||
|
*/
|
||||||
|
@NotNull(message = "订单类型不能为空")
|
||||||
|
private OrderConstant.OrderType orderType;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 下单类型
|
||||||
|
*/
|
||||||
|
@NotNull(message = "下单类型不能为空")
|
||||||
|
private OrderConstant.PlaceType placeType;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 打赏类型(0:余额;1:礼物)
|
||||||
|
*/
|
||||||
|
private RewardType rewardType;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 是否是首单
|
||||||
|
*/
|
||||||
|
private boolean isFirstOrder;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 商品信息
|
||||||
|
*/
|
||||||
|
@Valid
|
||||||
|
@NotNull(message = "商品信息不能为空")
|
||||||
|
private CommodityInfo commodityInfo;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 支付信息
|
||||||
|
*/
|
||||||
|
@Valid
|
||||||
|
@NotNull(message = "支付信息不能为空")
|
||||||
|
private PaymentInfo paymentInfo;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 下单人
|
||||||
|
*/
|
||||||
|
@NotBlank(message = "下单人不能为空")
|
||||||
|
private String purchaserBy;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 接单人(可选)
|
||||||
|
*/
|
||||||
|
private String acceptBy;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 微信号码
|
||||||
|
*/
|
||||||
|
private String weiChatCode;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 订单备注
|
||||||
|
*/
|
||||||
|
private String remark;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 随机单要求(仅随机单时需要)
|
||||||
|
*/
|
||||||
|
private RandomOrderRequirements randomOrderRequirements;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取首单标识字符串(兼容现有系统)
|
||||||
|
*/
|
||||||
|
public String getFirstOrderString() {
|
||||||
|
return isFirstOrder ? "1" : "0";
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 验证随机单要求
|
||||||
|
*/
|
||||||
|
public boolean isValidForRandomOrder() {
|
||||||
|
return placeType == OrderConstant.PlaceType.RANDOM
|
||||||
|
&& randomOrderRequirements != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 是否为打赏单
|
||||||
|
*/
|
||||||
|
public boolean isRewardOrder() {
|
||||||
|
return placeType == OrderConstant.PlaceType.REWARD;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 是否为指定单
|
||||||
|
*/
|
||||||
|
public boolean isSpecifiedOrder() {
|
||||||
|
return placeType == OrderConstant.PlaceType.SPECIFIED;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
package com.starry.admin.modules.order.module.dto;
|
||||||
|
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import java.util.List;
|
||||||
|
import lombok.Builder;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 支付信息值对象
|
||||||
|
*
|
||||||
|
* @author admin
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@Builder
|
||||||
|
public class PaymentInfo {
|
||||||
|
/**
|
||||||
|
* 订单金额
|
||||||
|
*/
|
||||||
|
private BigDecimal orderMoney;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 订单最终金额(支付金额)
|
||||||
|
*/
|
||||||
|
private BigDecimal finalAmount;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 优惠金额
|
||||||
|
*/
|
||||||
|
private BigDecimal discountAmount;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 优惠券ID列表
|
||||||
|
*/
|
||||||
|
private List<String> couponIds;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 支付方式,0:余额支付,1:微信支付,2:支付宝支付
|
||||||
|
*/
|
||||||
|
private String payMethod;
|
||||||
|
}
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
package com.starry.admin.modules.order.module.dto;
|
||||||
|
|
||||||
|
import com.starry.admin.modules.order.module.constant.OrderConstant;
|
||||||
|
import lombok.Builder;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 随机单要求信息值对象
|
||||||
|
*
|
||||||
|
* @author admin
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@Builder
|
||||||
|
public class RandomOrderRequirements {
|
||||||
|
/**
|
||||||
|
* 随机单要求-店员性别
|
||||||
|
*/
|
||||||
|
private OrderConstant.Gender clerkGender;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 随机单要求-店员等级ID
|
||||||
|
*/
|
||||||
|
private String clerkLevelId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 随机单要求-是否排除下单过的成员(0:不排除;1:排除)
|
||||||
|
*/
|
||||||
|
private String excludeHistory;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 是否排除历史订单
|
||||||
|
*/
|
||||||
|
public boolean shouldExcludeHistory() {
|
||||||
|
return "1".equals(excludeHistory);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -44,6 +44,8 @@ public class PlayOrderDetailsReturnVo {
|
|||||||
*/
|
*/
|
||||||
private String clerkNickname;
|
private String clerkNickname;
|
||||||
|
|
||||||
|
private String acceptBy;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 店员头像
|
* 店员头像
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package com.starry.admin.modules.order.service;
|
|||||||
|
|
||||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||||
import com.baomidou.mybatisplus.extension.service.IService;
|
import com.baomidou.mybatisplus.extension.service.IService;
|
||||||
|
import com.starry.admin.modules.order.module.dto.*;
|
||||||
import com.starry.admin.modules.order.module.entity.PlayOrderInfoEntity;
|
import com.starry.admin.modules.order.module.entity.PlayOrderInfoEntity;
|
||||||
import com.starry.admin.modules.order.module.vo.*;
|
import com.starry.admin.modules.order.module.vo.*;
|
||||||
import com.starry.admin.modules.weichat.entity.order.*;
|
import com.starry.admin.modules.weichat.entity.order.*;
|
||||||
@@ -43,8 +44,18 @@ public interface IPlayOrderInfoService extends IService<PlayOrderInfoEntity> {
|
|||||||
void createRechargeOrder(String orderNo, BigDecimal orderMoney, BigDecimal finalAmount, String purchaserBy);
|
void createRechargeOrder(String orderNo, BigDecimal orderMoney, BigDecimal finalAmount, String purchaserBy);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 新增订单信息
|
* 新增订单信息 - 重构版本使用Builder模式
|
||||||
*
|
*
|
||||||
|
* @param request 订单创建请求对象
|
||||||
|
* @author admin
|
||||||
|
* @since 2024/6/3 10:53
|
||||||
|
**/
|
||||||
|
void createOrderInfo(OrderCreationRequest request);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 新增订单信息 - 旧版本方法(已废弃,建议使用OrderCreationRequest)
|
||||||
|
*
|
||||||
|
* @deprecated 请使用 {@link #createOrderInfo(OrderCreationRequest)} 替代
|
||||||
* @param orderId
|
* @param orderId
|
||||||
* 订单ID
|
* 订单ID
|
||||||
* @param orderNo
|
* @param orderNo
|
||||||
@@ -96,6 +107,7 @@ public interface IPlayOrderInfoService extends IService<PlayOrderInfoEntity> {
|
|||||||
* @author admin
|
* @author admin
|
||||||
* @since 2024/6/3 10:53
|
* @since 2024/6/3 10:53
|
||||||
**/
|
**/
|
||||||
|
@Deprecated
|
||||||
void createOrderInfo(String orderId, String orderNo, String orderState, String orderType, String placeType,
|
void createOrderInfo(String orderId, String orderNo, String orderState, String orderType, String placeType,
|
||||||
String rewardType, String firstOrder, String commodityId, String commodityType, BigDecimal commodityPrice,
|
String rewardType, String firstOrder, String commodityId, String commodityType, BigDecimal commodityPrice,
|
||||||
String serviceDuration, String commodityName, String commodityNumber, BigDecimal orderMoney,
|
String serviceDuration, String commodityName, String commodityNumber, BigDecimal orderMoney,
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ import com.starry.admin.modules.custom.module.entity.PlayCustomUserInfoEntity;
|
|||||||
import com.starry.admin.modules.custom.service.IPlayCustomUserInfoService;
|
import com.starry.admin.modules.custom.service.IPlayCustomUserInfoService;
|
||||||
import com.starry.admin.modules.order.mapper.PlayOrderInfoMapper;
|
import com.starry.admin.modules.order.mapper.PlayOrderInfoMapper;
|
||||||
import com.starry.admin.modules.order.module.constant.OrderConstant;
|
import com.starry.admin.modules.order.module.constant.OrderConstant;
|
||||||
|
import com.starry.admin.modules.order.module.dto.*;
|
||||||
import com.starry.admin.modules.order.module.entity.PlayOrderComplaintInfoEntity;
|
import com.starry.admin.modules.order.module.entity.PlayOrderComplaintInfoEntity;
|
||||||
import com.starry.admin.modules.order.module.entity.PlayOrderEvaluateInfoEntity;
|
import com.starry.admin.modules.order.module.entity.PlayOrderEvaluateInfoEntity;
|
||||||
import com.starry.admin.modules.order.module.entity.PlayOrderInfoEntity;
|
import com.starry.admin.modules.order.module.entity.PlayOrderInfoEntity;
|
||||||
@@ -158,6 +159,132 @@ public class PlayOrderInfoServiceImpl extends ServiceImpl<PlayOrderInfoMapper, P
|
|||||||
playCouponDetailsService.updateCouponUseStateByIds(couponIds, "2");
|
playCouponDetailsService.updateCouponUseStateByIds(couponIds, "2");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void createOrderInfo(OrderCreationRequest request) {
|
||||||
|
// 验证请求
|
||||||
|
validateOrderCreationRequest(request);
|
||||||
|
|
||||||
|
PlayOrderInfoEntity entity = buildOrderEntity(request);
|
||||||
|
|
||||||
|
// 处理随机单要求
|
||||||
|
if (request.getPlaceType() == OrderConstant.PlaceType.RANDOM) {
|
||||||
|
setRandomOrderRequirements(entity, request.getRandomOrderRequirements());
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理接单人信息
|
||||||
|
if (StrUtil.isNotBlank(request.getAcceptBy())) {
|
||||||
|
setAcceptByInfo(entity, request);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理打赏单自动完成逻辑
|
||||||
|
if (request.isRewardOrder()) {
|
||||||
|
setRewardOrderCompleted(entity);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 保存订单
|
||||||
|
userInfoService.saveOrderInfo(entity);
|
||||||
|
this.baseMapper.insert(entity);
|
||||||
|
|
||||||
|
// 修改优惠券状态
|
||||||
|
playCouponDetailsService.updateCouponUseStateByIds(
|
||||||
|
request.getPaymentInfo().getCouponIds(), "2");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 验证订单创建请求
|
||||||
|
*/
|
||||||
|
private void validateOrderCreationRequest(OrderCreationRequest request) {
|
||||||
|
if (request.getPlaceType() == OrderConstant.PlaceType.RANDOM
|
||||||
|
&& !request.isValidForRandomOrder()) {
|
||||||
|
throw new CustomException("随机单必须提供店员要求信息");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 构建订单实体
|
||||||
|
*/
|
||||||
|
private PlayOrderInfoEntity buildOrderEntity(OrderCreationRequest request) {
|
||||||
|
PlayOrderInfoEntity entity = new PlayOrderInfoEntity();
|
||||||
|
|
||||||
|
// 基本信息
|
||||||
|
entity.setId(request.getOrderId());
|
||||||
|
entity.setOrderNo(request.getOrderNo());
|
||||||
|
entity.setOrderStatus(request.getOrderStatus().getCode());
|
||||||
|
entity.setOrderType(request.getOrderType().getCode());
|
||||||
|
entity.setPlaceType(request.getPlaceType().getCode());
|
||||||
|
entity.setRewardType(request.getRewardType().getCode());
|
||||||
|
entity.setFirstOrder(request.getFirstOrderString());
|
||||||
|
|
||||||
|
// 固定默认值
|
||||||
|
entity.setRefundType("0");
|
||||||
|
entity.setBackendEntry("0");
|
||||||
|
entity.setPayMethod("0");
|
||||||
|
entity.setOrderSettlementState("0");
|
||||||
|
entity.setOrdersExpiredState("0");
|
||||||
|
|
||||||
|
// 商品信息
|
||||||
|
CommodityInfo commodityInfo = request.getCommodityInfo();
|
||||||
|
entity.setCommodityId(commodityInfo.getCommodityId());
|
||||||
|
entity.setCommodityType(commodityInfo.getCommodityType().getCode());
|
||||||
|
entity.setCommodityPrice(commodityInfo.getCommodityPrice());
|
||||||
|
entity.setServiceDuration(commodityInfo.getServiceDuration());
|
||||||
|
entity.setCommodityName(commodityInfo.getCommodityName());
|
||||||
|
entity.setCommodityNumber(commodityInfo.getCommodityNumber());
|
||||||
|
|
||||||
|
// 支付信息
|
||||||
|
PaymentInfo paymentInfo = request.getPaymentInfo();
|
||||||
|
entity.setOrderMoney(paymentInfo.getOrderMoney());
|
||||||
|
entity.setFinalAmount(paymentInfo.getFinalAmount());
|
||||||
|
entity.setDiscountAmount(paymentInfo.getDiscountAmount());
|
||||||
|
entity.setCouponIds(paymentInfo.getCouponIds());
|
||||||
|
entity.setUseCoupon(
|
||||||
|
paymentInfo.getCouponIds() != null && !paymentInfo.getCouponIds().isEmpty() ? "1" : "0");
|
||||||
|
|
||||||
|
// 用户信息
|
||||||
|
entity.setPurchaserBy(request.getPurchaserBy());
|
||||||
|
entity.setPurchaserTime(LocalDateTime.now());
|
||||||
|
entity.setWeiChatCode(request.getWeiChatCode());
|
||||||
|
entity.setRemark(request.getRemark());
|
||||||
|
|
||||||
|
return entity;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置随机单要求
|
||||||
|
*/
|
||||||
|
private void setRandomOrderRequirements(PlayOrderInfoEntity entity, RandomOrderRequirements requirements) {
|
||||||
|
if (requirements != null) {
|
||||||
|
entity.setSex(requirements.getClerkGender().getCode());
|
||||||
|
entity.setLevelId(requirements.getClerkLevelId());
|
||||||
|
entity.setExcludeHistory(requirements.getExcludeHistory());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置接单人信息
|
||||||
|
*/
|
||||||
|
private void setAcceptByInfo(PlayOrderInfoEntity entity, OrderCreationRequest request) {
|
||||||
|
entity.setAcceptBy(request.getAcceptBy());
|
||||||
|
ClerkEstimatedRevenueVo estimatedRevenueVo = getClerkEstimatedRevenue(
|
||||||
|
request.getAcceptBy(),
|
||||||
|
request.getPaymentInfo().getCouponIds(),
|
||||||
|
request.getPlaceType().getCode(),
|
||||||
|
request.getFirstOrderString(),
|
||||||
|
request.getPaymentInfo().getFinalAmount());
|
||||||
|
entity.setEstimatedRevenue(estimatedRevenueVo.getRevenueAmount());
|
||||||
|
entity.setEstimatedRevenueRatio(estimatedRevenueVo.getRevenueRatio());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置打赏单为已完成状态
|
||||||
|
*/
|
||||||
|
private void setRewardOrderCompleted(PlayOrderInfoEntity entity) {
|
||||||
|
LocalDateTime now = LocalDateTime.now();
|
||||||
|
entity.setAcceptTime(now);
|
||||||
|
entity.setOrderStartTime(now);
|
||||||
|
entity.setOrderEndTime(now);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public ClerkEstimatedRevenueVo getClerkEstimatedRevenue(String clerkId, List<String> croupIds, String placeType,
|
public ClerkEstimatedRevenueVo getClerkEstimatedRevenue(String clerkId, List<String> croupIds, String placeType,
|
||||||
String firstOrder, BigDecimal finalAmount) {
|
String firstOrder, BigDecimal finalAmount) {
|
||||||
@@ -458,7 +585,7 @@ public class PlayOrderInfoServiceImpl extends ServiceImpl<PlayOrderInfoMapper, P
|
|||||||
LambdaQueryWrapper<PlayOrderInfoEntity> lambdaQueryWrapper = new LambdaQueryWrapper<>();
|
LambdaQueryWrapper<PlayOrderInfoEntity> lambdaQueryWrapper = new LambdaQueryWrapper<>();
|
||||||
lambdaQueryWrapper.eq(PlayOrderInfoEntity::getPlaceType, "1");
|
lambdaQueryWrapper.eq(PlayOrderInfoEntity::getPlaceType, "1");
|
||||||
lambdaQueryWrapper.eq(PlayOrderInfoEntity::getOrderStatus, "0");
|
lambdaQueryWrapper.eq(PlayOrderInfoEntity::getOrderStatus, "0");
|
||||||
lambdaQueryWrapper.eq(PlayOrderInfoEntity::getLevelId, entity.getLevelId());
|
// lambdaQueryWrapper.eq(PlayOrderInfoEntity::getLevelId, entity.getLevelId());
|
||||||
lambdaQueryWrapper.eq(PlayOrderInfoEntity::getSex, entity.getSex());
|
lambdaQueryWrapper.eq(PlayOrderInfoEntity::getSex, entity.getSex());
|
||||||
lambdaQueryWrapper.eq(PlayOrderInfoEntity::getExcludeHistory, "0")
|
lambdaQueryWrapper.eq(PlayOrderInfoEntity::getExcludeHistory, "0")
|
||||||
.or(wrapper1 -> wrapper1.ne(PlayOrderInfoEntity::getAcceptBy, clerkId)
|
.or(wrapper1 -> wrapper1.ne(PlayOrderInfoEntity::getAcceptBy, clerkId)
|
||||||
@@ -534,6 +661,7 @@ public class PlayOrderInfoServiceImpl extends ServiceImpl<PlayOrderInfoMapper, P
|
|||||||
|
|
||||||
MPJLambdaWrapper<PlayOrderInfoEntity> lambdaQueryWrapper = getCommonOrderQueryVo(
|
MPJLambdaWrapper<PlayOrderInfoEntity> lambdaQueryWrapper = getCommonOrderQueryVo(
|
||||||
ConvertUtil.entityToVo(vo, PlayOrderInfoEntity.class));
|
ConvertUtil.entityToVo(vo, PlayOrderInfoEntity.class));
|
||||||
|
lambdaQueryWrapper.in(PlayOrderInfoEntity::getPlaceType, "0", "1", "2");
|
||||||
if (StringUtils.isNotBlank(vo.getGroupId())) {
|
if (StringUtils.isNotBlank(vo.getGroupId())) {
|
||||||
lambdaQueryWrapper.eq(PlayOrderInfoEntity::getGroupId, vo.getGroupId());
|
lambdaQueryWrapper.eq(PlayOrderInfoEntity::getGroupId, vo.getGroupId());
|
||||||
}
|
}
|
||||||
@@ -689,10 +817,10 @@ public class PlayOrderInfoServiceImpl extends ServiceImpl<PlayOrderInfoMapper, P
|
|||||||
if (orderInfo.getLevelId().equals(clerkUserInfoEntity.getLevelId())) {
|
if (orderInfo.getLevelId().equals(clerkUserInfoEntity.getLevelId())) {
|
||||||
PlayClerkLevelInfoEntity levelInfo = playClerkLevelInfoService
|
PlayClerkLevelInfoEntity levelInfo = playClerkLevelInfoService
|
||||||
.selectPlayClerkLevelInfoById(orderInfo.getLevelId());
|
.selectPlayClerkLevelInfoById(orderInfo.getLevelId());
|
||||||
throw new CustomException("等级为" + levelInfo.getName() + "的店员才可接单");
|
// throw new CustomException("等级为" + levelInfo.getName() + "的店员才可接单");
|
||||||
}
|
}
|
||||||
// 判断店员性别是否符合规则
|
// 判断店员性别是否符合规则
|
||||||
if (orderInfo.getSex().equals(clerkUserInfoEntity.getSex())) {
|
if (!orderInfo.getSex().equals(clerkUserInfoEntity.getSex())) {
|
||||||
String sex = "0".equals(orderInfo.getSex()) ? "未知" : "1".equals(orderInfo.getSex()) ? "男" : "女";
|
String sex = "0".equals(orderInfo.getSex()) ? "未知" : "1".equals(orderInfo.getSex()) ? "男" : "女";
|
||||||
throw new CustomException("性别为" + sex + "的店员才可接单");
|
throw new CustomException("性别为" + sex + "的店员才可接单");
|
||||||
}
|
}
|
||||||
@@ -700,7 +828,7 @@ public class PlayOrderInfoServiceImpl extends ServiceImpl<PlayOrderInfoMapper, P
|
|||||||
LambdaQueryWrapper<PlayOrderInfoEntity> lambdaQueryWrapper = new LambdaQueryWrapper<>();
|
LambdaQueryWrapper<PlayOrderInfoEntity> lambdaQueryWrapper = new LambdaQueryWrapper<>();
|
||||||
lambdaQueryWrapper.eq(PlayOrderInfoEntity::getAcceptBy, acceptBy);
|
lambdaQueryWrapper.eq(PlayOrderInfoEntity::getAcceptBy, acceptBy);
|
||||||
if ("1".equals(orderInfo.getExcludeHistory()) && this.baseMapper.selectOne(lambdaQueryWrapper) != null) {
|
if ("1".equals(orderInfo.getExcludeHistory()) && this.baseMapper.selectOne(lambdaQueryWrapper) != null) {
|
||||||
throw new CustomException("只有未接单的店员才可接单");
|
// throw new CustomException("只有未接单的店员才可接单");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
PlayOrderInfoEntity entity = new PlayOrderInfoEntity(orderId, OrderConstant.ORDER_STATUS_1);
|
PlayOrderInfoEntity entity = new PlayOrderInfoEntity(orderId, OrderConstant.ORDER_STATUS_1);
|
||||||
|
|||||||
@@ -37,6 +37,7 @@ import com.starry.admin.utils.SmsUtils;
|
|||||||
import com.starry.common.redis.RedisCache;
|
import com.starry.common.redis.RedisCache;
|
||||||
import com.starry.common.result.R;
|
import com.starry.common.result.R;
|
||||||
import com.starry.common.utils.ConvertUtil;
|
import com.starry.common.utils.ConvertUtil;
|
||||||
|
import com.starry.common.utils.StringUtils;
|
||||||
import com.starry.common.utils.VerificationCodeUtils;
|
import com.starry.common.utils.VerificationCodeUtils;
|
||||||
import io.swagger.annotations.Api;
|
import io.swagger.annotations.Api;
|
||||||
import io.swagger.annotations.ApiOperation;
|
import io.swagger.annotations.ApiOperation;
|
||||||
@@ -442,9 +443,15 @@ public class WxClerkController {
|
|||||||
@ClerkUserLogin
|
@ClerkUserLogin
|
||||||
@GetMapping("/order/queryById")
|
@GetMapping("/order/queryById")
|
||||||
public R queryById(@RequestParam("id") String id) {
|
public R queryById(@RequestParam("id") String id) {
|
||||||
PlayClerkOrderDetailsReturnVo orderInfo = playOrderInfoService
|
PlayClerkOrderDetailsReturnVo vo = playOrderInfoService
|
||||||
.clerkSelectOrderDetails(ThreadLocalRequestDetail.getClerkUserInfo().getId(), id);
|
.clerkSelectOrderDetails(ThreadLocalRequestDetail.getClerkUserInfo().getId(), id);
|
||||||
return R.ok(orderInfo);
|
if (StringUtils.isNotEmpty(vo.getAcceptBy()) && !vo.getAcceptBy().equals(ThreadLocalRequestDetail.getClerkUserInfo().getId())) {
|
||||||
|
vo.setWeiChatCode("");
|
||||||
|
}
|
||||||
|
if(vo.getOrderStatus().equals("4")){
|
||||||
|
vo.setWeiChatCode("");
|
||||||
|
}
|
||||||
|
return R.ok(vo);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -19,6 +19,12 @@ import com.starry.admin.modules.custom.service.IPlayCustomFollowInfoService;
|
|||||||
import com.starry.admin.modules.custom.service.IPlayCustomGiftInfoService;
|
import com.starry.admin.modules.custom.service.IPlayCustomGiftInfoService;
|
||||||
import com.starry.admin.modules.custom.service.IPlayCustomLeaveMsgService;
|
import com.starry.admin.modules.custom.service.IPlayCustomLeaveMsgService;
|
||||||
import com.starry.admin.modules.custom.service.IPlayCustomUserInfoService;
|
import com.starry.admin.modules.custom.service.IPlayCustomUserInfoService;
|
||||||
|
import com.starry.admin.modules.order.module.constant.OrderConstant;
|
||||||
|
import com.starry.admin.modules.order.module.constant.OrderConstant.RewardType;
|
||||||
|
import com.starry.admin.modules.order.module.dto.CommodityInfo;
|
||||||
|
import com.starry.admin.modules.order.module.dto.OrderCreationRequest;
|
||||||
|
import com.starry.admin.modules.order.module.dto.PaymentInfo;
|
||||||
|
import com.starry.admin.modules.order.module.dto.RandomOrderRequirements;
|
||||||
import com.starry.admin.modules.order.module.entity.PlayOrderComplaintInfoEntity;
|
import com.starry.admin.modules.order.module.entity.PlayOrderComplaintInfoEntity;
|
||||||
import com.starry.admin.modules.order.module.entity.PlayOrderEvaluateInfoEntity;
|
import com.starry.admin.modules.order.module.entity.PlayOrderEvaluateInfoEntity;
|
||||||
import com.starry.admin.modules.order.module.entity.PlayOrderInfoEntity;
|
import com.starry.admin.modules.order.module.entity.PlayOrderInfoEntity;
|
||||||
@@ -224,7 +230,34 @@ public class WxCustomController {
|
|||||||
}
|
}
|
||||||
String orderId = IdUtils.getUuid();
|
String orderId = IdUtils.getUuid();
|
||||||
// 记录订单信息
|
// 记录订单信息
|
||||||
playOrderInfoService.createOrderInfo(orderId, playOrderInfoService.getOrderNo(), "3", "2", "2", "0", "1", "", "0", BigDecimal.ZERO, "", "", "0", new BigDecimal(vo.getMoney()), new BigDecimal(vo.getMoney()), BigDecimal.ZERO, userId, vo.getClerkId(), vo.getWeiChatCode(), new ArrayList<>(), vo.getRemark(), "", "", "");
|
OrderCreationRequest orderRequest = OrderCreationRequest.builder()
|
||||||
|
.orderId(orderId)
|
||||||
|
.orderNo(playOrderInfoService.getOrderNo())
|
||||||
|
.orderStatus(OrderConstant.OrderStatus.COMPLETED)
|
||||||
|
.orderType(OrderConstant.OrderType.NORMAL)
|
||||||
|
.placeType(OrderConstant.PlaceType.REWARD)
|
||||||
|
.rewardType(RewardType.BALANCE)
|
||||||
|
.isFirstOrder(true)
|
||||||
|
.commodityInfo(CommodityInfo.builder()
|
||||||
|
.commodityId("")
|
||||||
|
.commodityType(OrderConstant.CommodityType.GIFT)
|
||||||
|
.commodityPrice(BigDecimal.ZERO)
|
||||||
|
.serviceDuration("")
|
||||||
|
.commodityName("")
|
||||||
|
.commodityNumber("0")
|
||||||
|
.build())
|
||||||
|
.paymentInfo(PaymentInfo.builder()
|
||||||
|
.orderMoney(new BigDecimal(vo.getMoney()))
|
||||||
|
.finalAmount(new BigDecimal(vo.getMoney()))
|
||||||
|
.discountAmount(BigDecimal.ZERO)
|
||||||
|
.couponIds(new ArrayList<>())
|
||||||
|
.build())
|
||||||
|
.purchaserBy(userId)
|
||||||
|
.acceptBy(vo.getClerkId())
|
||||||
|
.weiChatCode(vo.getWeiChatCode())
|
||||||
|
.remark(vo.getRemark())
|
||||||
|
.build();
|
||||||
|
playOrderInfoService.createOrderInfo(orderRequest);
|
||||||
// 顾客减少余额
|
// 顾客减少余额
|
||||||
customUserInfoService.updateAccountBalanceById(customUserInfo.getId(), customUserInfo.getAccountBalance(), customUserInfo.getAccountBalance().subtract(new BigDecimal(vo.getMoney())), "1", "打赏", new BigDecimal(vo.getMoney()), BigDecimal.ZERO, orderId);
|
customUserInfoService.updateAccountBalanceById(customUserInfo.getId(), customUserInfo.getAccountBalance(), customUserInfo.getAccountBalance().subtract(new BigDecimal(vo.getMoney())), "1", "打赏", new BigDecimal(vo.getMoney()), BigDecimal.ZERO, orderId);
|
||||||
return R.ok("成功");
|
return R.ok("成功");
|
||||||
@@ -247,7 +280,34 @@ public class WxCustomController {
|
|||||||
}
|
}
|
||||||
String orderId = IdUtils.getUuid();
|
String orderId = IdUtils.getUuid();
|
||||||
// 记录订单信息
|
// 记录订单信息
|
||||||
playOrderInfoService.createOrderInfo(orderId, playOrderInfoService.getOrderNo(), "3", "2", "2", "1", "1", giftInfo.getId(), "0", giftInfo.getPrice(), "", giftInfo.getName(), String.valueOf(vo.getGiftQuantity()), money, money, BigDecimal.ZERO, userId, vo.getClerkId(), vo.getWeiChatCode(), new ArrayList<>(), vo.getRemark(), "", "", "");
|
OrderCreationRequest orderRequest = OrderCreationRequest.builder()
|
||||||
|
.orderId(orderId)
|
||||||
|
.orderNo(playOrderInfoService.getOrderNo())
|
||||||
|
.orderStatus(OrderConstant.OrderStatus.COMPLETED)
|
||||||
|
.orderType(OrderConstant.OrderType.NORMAL)
|
||||||
|
.placeType(OrderConstant.PlaceType.REWARD)
|
||||||
|
.rewardType(RewardType.GIFT)
|
||||||
|
.isFirstOrder(true)
|
||||||
|
.commodityInfo(CommodityInfo.builder()
|
||||||
|
.commodityId(giftInfo.getId())
|
||||||
|
.commodityType(OrderConstant.CommodityType.GIFT)
|
||||||
|
.commodityPrice(giftInfo.getPrice())
|
||||||
|
.serviceDuration("")
|
||||||
|
.commodityName(giftInfo.getName())
|
||||||
|
.commodityNumber(String.valueOf(vo.getGiftQuantity()))
|
||||||
|
.build())
|
||||||
|
.paymentInfo(PaymentInfo.builder()
|
||||||
|
.orderMoney(money)
|
||||||
|
.finalAmount(money)
|
||||||
|
.discountAmount(BigDecimal.ZERO)
|
||||||
|
.couponIds(new ArrayList<>())
|
||||||
|
.build())
|
||||||
|
.purchaserBy(userId)
|
||||||
|
.acceptBy(vo.getClerkId())
|
||||||
|
.weiChatCode(vo.getWeiChatCode())
|
||||||
|
.remark(vo.getRemark())
|
||||||
|
.build();
|
||||||
|
playOrderInfoService.createOrderInfo(orderRequest);
|
||||||
// 顾客减少余额
|
// 顾客减少余额
|
||||||
customUserInfoService.updateAccountBalanceById(customUserInfo.getId(), customUserInfo.getAccountBalance(), customUserInfo.getAccountBalance().subtract(money), "1", "赠送礼物", money, BigDecimal.ZERO, orderId);
|
customUserInfoService.updateAccountBalanceById(customUserInfo.getId(), customUserInfo.getAccountBalance(), customUserInfo.getAccountBalance().subtract(money), "1", "赠送礼物", money, BigDecimal.ZERO, orderId);
|
||||||
// 陪聊增加余额
|
// 陪聊增加余额
|
||||||
@@ -329,7 +389,34 @@ public class WxCustomController {
|
|||||||
String orderId = IdUtils.getUuid();
|
String orderId = IdUtils.getUuid();
|
||||||
String orderNo = playOrderInfoService.getOrderNo();
|
String orderNo = playOrderInfoService.getOrderNo();
|
||||||
// 记录订单信息
|
// 记录订单信息
|
||||||
playOrderInfoService.createOrderInfo(orderId, orderNo, "0", "2", "0", "", "1", commodityInfo.getCommodityId(), "1", commodityInfo.getCommodityPrice(), commodityInfo.getServiceDuration(), commodityInfo.getCommodityName(), String.valueOf(vo.getCommodityQuantity()), money, money, BigDecimal.ZERO, customId, clerkUserInfo.getId(), vo.getWeiChatCode(), vo.getCouponIds(), vo.getRemark(), "", "", "");
|
OrderCreationRequest orderRequest = OrderCreationRequest.builder()
|
||||||
|
.orderId(orderId)
|
||||||
|
.orderNo(orderNo)
|
||||||
|
.orderStatus(OrderConstant.OrderStatus.PENDING)
|
||||||
|
.orderType(OrderConstant.OrderType.NORMAL)
|
||||||
|
.placeType(OrderConstant.PlaceType.SPECIFIED)
|
||||||
|
.rewardType(RewardType.NOT_APPLICABLE)
|
||||||
|
.isFirstOrder(true)
|
||||||
|
.commodityInfo(CommodityInfo.builder()
|
||||||
|
.commodityId(commodityInfo.getCommodityId())
|
||||||
|
.commodityType(OrderConstant.CommodityType.SERVICE)
|
||||||
|
.commodityPrice(commodityInfo.getCommodityPrice())
|
||||||
|
.serviceDuration(commodityInfo.getServiceDuration())
|
||||||
|
.commodityName(commodityInfo.getCommodityName())
|
||||||
|
.commodityNumber(String.valueOf(vo.getCommodityQuantity()))
|
||||||
|
.build())
|
||||||
|
.paymentInfo(PaymentInfo.builder()
|
||||||
|
.orderMoney(money)
|
||||||
|
.finalAmount(money)
|
||||||
|
.discountAmount(BigDecimal.ZERO)
|
||||||
|
.couponIds(vo.getCouponIds())
|
||||||
|
.build())
|
||||||
|
.purchaserBy(customId)
|
||||||
|
.acceptBy(clerkUserInfo.getId())
|
||||||
|
.weiChatCode(vo.getWeiChatCode())
|
||||||
|
.remark(vo.getRemark())
|
||||||
|
.build();
|
||||||
|
playOrderInfoService.createOrderInfo(orderRequest);
|
||||||
// 顾客减少余额
|
// 顾客减少余额
|
||||||
customUserInfoService.updateAccountBalanceById(customUserInfo.getId(), customUserInfo.getAccountBalance(), customUserInfo.getAccountBalance().subtract(money), "1", "下单-指定单", money, BigDecimal.ZERO, orderId);
|
customUserInfoService.updateAccountBalanceById(customUserInfo.getId(), customUserInfo.getAccountBalance(), customUserInfo.getAccountBalance().subtract(money), "1", "下单-指定单", money, BigDecimal.ZERO, orderId);
|
||||||
// 发送通知给店员
|
// 发送通知给店员
|
||||||
@@ -357,11 +444,42 @@ public class WxCustomController {
|
|||||||
String orderId = IdUtils.getUuid();
|
String orderId = IdUtils.getUuid();
|
||||||
String orderNo = playOrderInfoService.getOrderNo();
|
String orderNo = playOrderInfoService.getOrderNo();
|
||||||
// 记录订单信息
|
// 记录订单信息
|
||||||
playOrderInfoService.createOrderInfo(orderId, orderNo, "0", "2", "1", "", "1", commodityInfo.getCommodityId(), "1", commodityInfo.getCommodityPrice(), commodityInfo.getServiceDuration(), commodityInfo.getCommodityName(), String.valueOf(vo.getCommodityQuantity()), money, money, BigDecimal.ZERO, customId, "", vo.getWeiChatCode(), vo.getCouponIds(), vo.getRemark(), vo.getSex(), vo.getLevelId(), vo.getExcludeHistory());
|
OrderCreationRequest orderRequest = OrderCreationRequest.builder()
|
||||||
|
.orderId(orderId)
|
||||||
|
.orderNo(orderNo)
|
||||||
|
.orderStatus(OrderConstant.OrderStatus.PENDING)
|
||||||
|
.orderType(OrderConstant.OrderType.NORMAL)
|
||||||
|
.placeType(OrderConstant.PlaceType.RANDOM)
|
||||||
|
.rewardType(RewardType.NOT_APPLICABLE)
|
||||||
|
.isFirstOrder(true)
|
||||||
|
.commodityInfo(CommodityInfo.builder()
|
||||||
|
.commodityId(commodityInfo.getCommodityId())
|
||||||
|
.commodityType(OrderConstant.CommodityType.SERVICE)
|
||||||
|
.commodityPrice(commodityInfo.getCommodityPrice())
|
||||||
|
.serviceDuration(commodityInfo.getServiceDuration())
|
||||||
|
.commodityName(commodityInfo.getCommodityName())
|
||||||
|
.commodityNumber(String.valueOf(vo.getCommodityQuantity()))
|
||||||
|
.build())
|
||||||
|
.paymentInfo(PaymentInfo.builder()
|
||||||
|
.orderMoney(money)
|
||||||
|
.finalAmount(money)
|
||||||
|
.discountAmount(BigDecimal.ZERO)
|
||||||
|
.couponIds(vo.getCouponIds())
|
||||||
|
.build())
|
||||||
|
.purchaserBy(customId)
|
||||||
|
.weiChatCode(vo.getWeiChatCode())
|
||||||
|
.remark(vo.getRemark())
|
||||||
|
.randomOrderRequirements(RandomOrderRequirements.builder()
|
||||||
|
.clerkGender(OrderConstant.Gender.fromCode(vo.getSex()))
|
||||||
|
.clerkLevelId(vo.getLevelId())
|
||||||
|
.excludeHistory(vo.getExcludeHistory())
|
||||||
|
.build())
|
||||||
|
.build();
|
||||||
|
playOrderInfoService.createOrderInfo(orderRequest);
|
||||||
// 顾客减少余额
|
// 顾客减少余额
|
||||||
customUserInfoService.updateAccountBalanceById(customUserInfo.getId(), customUserInfo.getAccountBalance(), customUserInfo.getAccountBalance().subtract(money), "1", "下单-随机单", money, BigDecimal.ZERO, orderId);
|
customUserInfoService.updateAccountBalanceById(customUserInfo.getId(), customUserInfo.getAccountBalance(), customUserInfo.getAccountBalance().subtract(money), "1", "下单-随机单", money, BigDecimal.ZERO, orderId);
|
||||||
// 给全部店员发送通知
|
// 给全部店员发送通知
|
||||||
List<PlayClerkUserInfoEntity> clerkList = clerkUserInfoService.list(Wrappers.lambdaQuery(PlayClerkUserInfoEntity.class).isNotNull(PlayClerkUserInfoEntity::getOpenid).eq(PlayClerkUserInfoEntity::getLevelId, vo.getLevelId()).eq(PlayClerkUserInfoEntity::getClerkState, "1").eq(PlayClerkUserInfoEntity::getSex, vo.getSex()));
|
List<PlayClerkUserInfoEntity> clerkList = clerkUserInfoService.list(Wrappers.lambdaQuery(PlayClerkUserInfoEntity.class).isNotNull(PlayClerkUserInfoEntity::getOpenid).eq(PlayClerkUserInfoEntity::getClerkState, "1").eq(PlayClerkUserInfoEntity::getSex, vo.getSex()));
|
||||||
wxCustomMpService.sendCreateOrderMessageBatch(clerkList, orderNo, money.toString(), commodityInfo.getCommodityName(),orderId);
|
wxCustomMpService.sendCreateOrderMessageBatch(clerkList, orderNo, money.toString(), commodityInfo.getCommodityName(),orderId);
|
||||||
// 记录订单,指定指定未接单后,进行退款处理
|
// 记录订单,指定指定未接单后,进行退款处理
|
||||||
overdueOrderHandlerTask.enqueue(orderId + "_" + SecurityUtils.getTenantId());
|
overdueOrderHandlerTask.enqueue(orderId + "_" + SecurityUtils.getTenantId());
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ import com.starry.admin.modules.weichat.entity.order.PlayOrderInfoContinueQueryV
|
|||||||
import com.starry.admin.modules.weichat.entity.order.PlayOrderInfoRandomQueryVo;
|
import com.starry.admin.modules.weichat.entity.order.PlayOrderInfoRandomQueryVo;
|
||||||
import com.starry.admin.modules.weichat.entity.order.PlayRewardOrderQueryVo;
|
import com.starry.admin.modules.weichat.entity.order.PlayRewardOrderQueryVo;
|
||||||
import com.starry.common.result.R;
|
import com.starry.common.result.R;
|
||||||
|
import com.starry.common.utils.StringUtils;
|
||||||
import io.swagger.annotations.Api;
|
import io.swagger.annotations.Api;
|
||||||
import io.swagger.annotations.ApiImplicitParam;
|
import io.swagger.annotations.ApiImplicitParam;
|
||||||
import io.swagger.annotations.ApiOperation;
|
import io.swagger.annotations.ApiOperation;
|
||||||
@@ -52,8 +53,7 @@ public class WxOrderInfoController {
|
|||||||
/**
|
/**
|
||||||
* 续单申请-店员发起
|
* 续单申请-店员发起
|
||||||
*
|
*
|
||||||
* @param vo
|
* @param vo 续单申请提交对象
|
||||||
* 续单申请提交对象
|
|
||||||
*/
|
*/
|
||||||
@ApiOperation(value = "店员申请续单", notes = "店员为当前订单发起续单申请")
|
@ApiOperation(value = "店员申请续单", notes = "店员为当前订单发起续单申请")
|
||||||
@ApiResponses({@ApiResponse(code = 200, message = "操作成功"), @ApiResponse(code = 500, message = "非本人订单;无法续单"),
|
@ApiResponses({@ApiResponse(code = 200, message = "操作成功"), @ApiResponse(code = 500, message = "非本人订单;无法续单"),
|
||||||
@@ -91,8 +91,7 @@ public class WxOrderInfoController {
|
|||||||
/**
|
/**
|
||||||
* 店员查询随机单列表
|
* 店员查询随机单列表
|
||||||
*
|
*
|
||||||
* @param vo
|
* @param vo 随机单列表查询对象
|
||||||
* 随机单列表查询对象
|
|
||||||
* @return 订单列表
|
* @return 订单列表
|
||||||
*/
|
*/
|
||||||
@ApiOperation(value = "查询随机单列表", notes = "店员查询可接的随机订单列表")
|
@ApiOperation(value = "查询随机单列表", notes = "店员查询可接的随机订单列表")
|
||||||
@@ -108,8 +107,7 @@ public class WxOrderInfoController {
|
|||||||
/**
|
/**
|
||||||
* 店员查询随机单详情
|
* 店员查询随机单详情
|
||||||
*
|
*
|
||||||
* @param id
|
* @param id 订单ID
|
||||||
* 订单ID
|
|
||||||
* @return 订单列表
|
* @return 订单列表
|
||||||
*/
|
*/
|
||||||
@ApiOperation(value = "查询随机单详情", notes = "店员查询随机订单的详细信息")
|
@ApiOperation(value = "查询随机单详情", notes = "店员查询随机订单的详细信息")
|
||||||
@@ -123,14 +121,19 @@ public class WxOrderInfoController {
|
|||||||
if (vo == null) {
|
if (vo == null) {
|
||||||
throw new CustomException("订单不存在");
|
throw new CustomException("订单不存在");
|
||||||
}
|
}
|
||||||
|
if (StringUtils.isNotEmpty(vo.getAcceptBy()) && !vo.getAcceptBy().equals(ThreadLocalRequestDetail.getClerkUserInfo().getId())) {
|
||||||
|
vo.setWeiChatCode("");
|
||||||
|
}
|
||||||
|
if(vo.getOrderStatus().equals("4")){
|
||||||
|
vo.setWeiChatCode("");
|
||||||
|
}
|
||||||
return R.ok(vo);
|
return R.ok(vo);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 店员查询打赏动态
|
* 店员查询打赏动态
|
||||||
*
|
*
|
||||||
* @param vo
|
* @param vo 打赏动态查询列表
|
||||||
* 打赏动态查询列表
|
|
||||||
* @return 打赏动态列表
|
* @return 打赏动态列表
|
||||||
*/
|
*/
|
||||||
@ApiOperation(value = "店员查询打赏动态", notes = "店员查询打赏订单动态")
|
@ApiOperation(value = "店员查询打赏动态", notes = "店员查询打赏订单动态")
|
||||||
@@ -157,8 +160,7 @@ public class WxOrderInfoController {
|
|||||||
/**
|
/**
|
||||||
* 顾客-分页查询续单列表
|
* 顾客-分页查询续单列表
|
||||||
*
|
*
|
||||||
* @param vo
|
* @param vo PlayOrderInfoContinueQueryVo
|
||||||
* PlayOrderInfoContinueQueryVo
|
|
||||||
* @return 续单历史
|
* @return 续单历史
|
||||||
*/
|
*/
|
||||||
@ApiOperation(value = "查询续单列表", notes = "顾客分页查询续单申请列表")
|
@ApiOperation(value = "查询续单列表", notes = "顾客分页查询续单申请列表")
|
||||||
|
|||||||
@@ -114,6 +114,8 @@ public class PlayClerkOrderDetailsReturnVo {
|
|||||||
*/
|
*/
|
||||||
private BigDecimal estimatedRevenue;
|
private BigDecimal estimatedRevenue;
|
||||||
|
|
||||||
|
private String acceptBy;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 优惠金额
|
* 优惠金额
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -76,6 +76,20 @@ public class WxCustomMpService {
|
|||||||
wxMpService.addConfigStorage(entity.getAppId(), config);
|
wxMpService.addConfigStorage(entity.getAppId(), config);
|
||||||
return wxMpService.switchoverTo(entity.getAppId());
|
return wxMpService.switchoverTo(entity.getAppId());
|
||||||
}
|
}
|
||||||
|
public WxMpService proxyWxMpService(String tenantId) {
|
||||||
|
if (StrUtil.isBlankIfStr(tenantId)) {
|
||||||
|
throw new CustomException("系统错误,租户ID不能为空");
|
||||||
|
}
|
||||||
|
SysTenantEntity entity = tenantService.selectSysTenantByTenantId(tenantId);
|
||||||
|
if (entity == null) {
|
||||||
|
throw new CustomException("系统错误,租户ID不能为空");
|
||||||
|
}
|
||||||
|
WxMpMapConfigImpl config = new WxMpMapConfigImpl();
|
||||||
|
config.setAppId(entity.getAppId());
|
||||||
|
config.setSecret(entity.getSecret());
|
||||||
|
wxMpService.addConfigStorage(entity.getAppId(), config);
|
||||||
|
return wxMpService.switchoverTo(entity.getAppId());
|
||||||
|
}
|
||||||
|
|
||||||
public WxPayService getWxPay() {
|
public WxPayService getWxPay() {
|
||||||
String tenantId = SecurityUtils.getTenantId();
|
String tenantId = SecurityUtils.getTenantId();
|
||||||
@@ -161,7 +175,7 @@ public class WxCustomMpService {
|
|||||||
data.add(new WxMpTemplateData("amount8", money));
|
data.add(new WxMpTemplateData("amount8", money));
|
||||||
templateMessage.setData(data);
|
templateMessage.setData(data);
|
||||||
try {
|
try {
|
||||||
proxyWxMpService().getTemplateMsgService().sendTemplateMsg(templateMessage);
|
proxyWxMpService(tenantId).getTemplateMsgService().sendTemplateMsg(templateMessage);
|
||||||
} catch (WxErrorException e) {
|
} catch (WxErrorException e) {
|
||||||
log.error("接单成功发送消息异常", e);
|
log.error("接单成功发送消息异常", e);
|
||||||
}
|
}
|
||||||
@@ -324,7 +338,7 @@ public class WxCustomMpService {
|
|||||||
data.add(new WxMpTemplateData("thing13", order.getCommodityName()));
|
data.add(new WxMpTemplateData("thing13", order.getCommodityName()));
|
||||||
templateMessage.setData(data);
|
templateMessage.setData(data);
|
||||||
try {
|
try {
|
||||||
proxyWxMpService().getTemplateMsgService().sendTemplateMsg(templateMessage);
|
proxyWxMpService(tenant.getTenantId()).getTemplateMsgService().sendTemplateMsg(templateMessage);
|
||||||
} catch (WxErrorException e) {
|
} catch (WxErrorException e) {
|
||||||
log.error("订单完成发送消息异常", e);
|
log.error("订单完成发送消息异常", e);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,8 +18,8 @@ spring:
|
|||||||
type: com.alibaba.druid.pool.DruidDataSource
|
type: com.alibaba.druid.pool.DruidDataSource
|
||||||
# 配置MySQL的驱动程序类
|
# 配置MySQL的驱动程序类
|
||||||
driver-class-name: com.p6spy.engine.spy.P6SpyDriver
|
driver-class-name: com.p6spy.engine.spy.P6SpyDriver
|
||||||
# 数据库连接地址(以MySql为例)
|
# 数据库连接地址(以MySql为例) - Using Tailscale IP for Docker containers
|
||||||
url: ${SPRING_DATASOURCE_URL:jdbc:p6spy:mysql://primary:3306/play-with?useUnicode=true&characterEncoding=utf8&characterSetResults=utf8}
|
url: ${SPRING_DATASOURCE_URL:jdbc:p6spy:mysql://100.80.201.143:3306/play-with?useUnicode=true&characterEncoding=utf8&characterSetResults=utf8}
|
||||||
# 数据库对应的用户名
|
# 数据库对应的用户名
|
||||||
username: ${SPRING_DATASOURCE_USERNAME:root}
|
username: ${SPRING_DATASOURCE_USERNAME:root}
|
||||||
# 数据库对应的密码
|
# 数据库对应的密码
|
||||||
@@ -75,10 +75,11 @@ spring:
|
|||||||
# HTML 中 Reset All 按钮
|
# HTML 中 Reset All 按钮
|
||||||
reset-enable: true
|
reset-enable: true
|
||||||
redis:
|
redis:
|
||||||
host: ${SPRING_REDIS_HOST:127.0.0.1} # Redis服务器地址
|
host: ${SPRING_REDIS_HOST:100.80.201.143} # Redis服务器地址 - Using Tailscale IP for Docker containers
|
||||||
database: ${SPRING_REDIS_DATABASE:10} # Redis数据库索引(默认为0)
|
database: ${SPRING_REDIS_DATABASE:10} # Redis数据库索引(默认为0)
|
||||||
port: ${SPRING_REDIS_PORT:6379} # Redis服务器连接端口
|
port: ${SPRING_REDIS_PORT:6379} # Redis服务器连接端口
|
||||||
password: ${SPRING_REDIS_PASSWORD:Spinfo@0123}
|
username: ${SPRING_REDIS_USERNAME:test} # Redis用户名
|
||||||
|
password: ${SPRING_REDIS_PASSWORD:123456} # Redis密码
|
||||||
timeout: ${SPRING_REDIS_TIMEOUT:3000ms} # 连接超时时间(毫秒)
|
timeout: ${SPRING_REDIS_TIMEOUT:3000ms} # 连接超时时间(毫秒)
|
||||||
|
|
||||||
# 全局日志级别
|
# 全局日志级别
|
||||||
|
|||||||
@@ -12,9 +12,9 @@
|
|||||||
converterClass="org.springframework.boot.logging.logback.WhitespaceThrowableProxyConverter"/>
|
converterClass="org.springframework.boot.logging.logback.WhitespaceThrowableProxyConverter"/>
|
||||||
<conversionRule conversionWord="wEx"
|
<conversionRule conversionWord="wEx"
|
||||||
converterClass="org.springframework.boot.logging.logback.ExtendedWhitespaceThrowableProxyConverter"/>
|
converterClass="org.springframework.boot.logging.logback.ExtendedWhitespaceThrowableProxyConverter"/>
|
||||||
<!-- 彩色日志格式 -->
|
<!-- 彩色日志格式 - 包含关联ID -->
|
||||||
<property name="CONSOLE_LOG_PATTERN"
|
<property name="CONSOLE_LOG_PATTERN"
|
||||||
value="${CONSOLE_LOG_PATTERN:-%clr(%d{yyyy-MM-dd HH:mm:ss.SSS}){faint} %clr(${LOG_LEVEL_PATTERN:-%5p}) %clr(${PID:- }){magenta} %clr(---){faint} %clr([%15.15t]){faint} %clr(%-40.40logger{39}){cyan} %clr(:){faint} %m%n${LOG_EXCEPTION_CONVERSION_WORD:-%wEx}}"/>
|
value="${CONSOLE_LOG_PATTERN:-%clr(%d{yyyy-MM-dd HH:mm:ss.SSS}){faint} %clr(${LOG_LEVEL_PATTERN:-%5p}) %clr(${PID:- }){magenta} %clr(---){faint} %clr([%15.15t]){faint} %clr([%X{correlationId:--}]){yellow} %clr([%X{tenantKey:--}]){blue} %clr([%X{userId:--}]){green} %clr(%-40.40logger{39}){cyan} %clr(:){faint} %m%n${LOG_EXCEPTION_CONVERSION_WORD:-%wEx}}"/>
|
||||||
|
|
||||||
<!--输出到控制台-->
|
<!--输出到控制台-->
|
||||||
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
|
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
|
||||||
@@ -40,7 +40,7 @@
|
|||||||
<maxHistory>7</maxHistory>
|
<maxHistory>7</maxHistory>
|
||||||
</rollingPolicy>
|
</rollingPolicy>
|
||||||
<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
|
<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
|
||||||
<pattern>%d{MM/dd/yyyy HH:mm:ss} %-5level [%thread]%logger{16} - %msg%n</pattern>
|
<pattern>%d{MM/dd/yyyy HH:mm:ss} %-5level [%thread] [%X{correlationId:--}] [%X{tenantKey:--}] [%X{userId:--}] %logger{16} - %msg%n</pattern>
|
||||||
<charset>UTF-8</charset> <!-- 设置字符集 -->
|
<charset>UTF-8</charset> <!-- 设置字符集 -->
|
||||||
</encoder>
|
</encoder>
|
||||||
</appender>
|
</appender>
|
||||||
@@ -51,7 +51,7 @@
|
|||||||
<file>${log.path}/error.log</file>
|
<file>${log.path}/error.log</file>
|
||||||
<!--日志文件输出格式-->
|
<!--日志文件输出格式-->
|
||||||
<encoder>
|
<encoder>
|
||||||
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern>
|
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] [%X{correlationId:--}] [%X{tenantKey:--}] [%X{userId:--}] %-5level %logger{50} - %msg%n</pattern>
|
||||||
<charset>UTF-8</charset> <!-- 此处设置字符集 -->
|
<charset>UTF-8</charset> <!-- 此处设置字符集 -->
|
||||||
</encoder>
|
</encoder>
|
||||||
<!-- 日志记录器的滚动策略,按日期,按大小记录 -->
|
<!-- 日志记录器的滚动策略,按日期,按大小记录 -->
|
||||||
|
|||||||
@@ -0,0 +1,211 @@
|
|||||||
|
package com.starry.admin.modules.order.service;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.*;
|
||||||
|
|
||||||
|
import com.starry.admin.modules.order.module.constant.OrderConstant;
|
||||||
|
import com.starry.admin.modules.order.module.dto.CommodityInfo;
|
||||||
|
import com.starry.admin.modules.order.module.dto.OrderCreationRequest;
|
||||||
|
import com.starry.admin.modules.order.module.dto.PaymentInfo;
|
||||||
|
import com.starry.admin.modules.order.module.dto.RandomOrderRequirements;
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import org.junit.jupiter.api.DisplayName;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 订单创建请求对象测试类
|
||||||
|
*
|
||||||
|
* @author admin
|
||||||
|
*/
|
||||||
|
class OrderCreationRequestTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("测试Builder模式构建订单请求")
|
||||||
|
void testBuilderPattern() {
|
||||||
|
// 构建商品信息
|
||||||
|
CommodityInfo commodityInfo = CommodityInfo.builder()
|
||||||
|
.commodityId("commodity_001")
|
||||||
|
.commodityType(OrderConstant.CommodityType.SERVICE)
|
||||||
|
.commodityPrice(new BigDecimal("100.00"))
|
||||||
|
.serviceDuration("60")
|
||||||
|
.commodityName("陪聊服务")
|
||||||
|
.commodityNumber("1")
|
||||||
|
.build();
|
||||||
|
|
||||||
|
// 构建支付信息
|
||||||
|
PaymentInfo paymentInfo = PaymentInfo.builder()
|
||||||
|
.orderMoney(new BigDecimal("100.00"))
|
||||||
|
.finalAmount(new BigDecimal("90.00"))
|
||||||
|
.discountAmount(new BigDecimal("10.00"))
|
||||||
|
.couponIds(Arrays.asList("coupon_001"))
|
||||||
|
.payMethod("0")
|
||||||
|
.build();
|
||||||
|
|
||||||
|
// 构建订单请求
|
||||||
|
OrderCreationRequest request = OrderCreationRequest.builder()
|
||||||
|
.orderId("order_123456")
|
||||||
|
.orderNo("ORD20240906001")
|
||||||
|
.orderStatus(OrderConstant.OrderStatus.PENDING)
|
||||||
|
.orderType(OrderConstant.OrderType.NORMAL)
|
||||||
|
.placeType(OrderConstant.PlaceType.SPECIFIED)
|
||||||
|
.rewardType(OrderConstant.RewardType.BALANCE)
|
||||||
|
.isFirstOrder(true)
|
||||||
|
.commodityInfo(commodityInfo)
|
||||||
|
.paymentInfo(paymentInfo)
|
||||||
|
.purchaserBy("customer_001")
|
||||||
|
.acceptBy("clerk_001")
|
||||||
|
.weiChatCode("wx123456")
|
||||||
|
.remark("客户备注信息")
|
||||||
|
.build();
|
||||||
|
|
||||||
|
// 验证构建结果
|
||||||
|
assertEquals("order_123456", request.getOrderId());
|
||||||
|
assertEquals("ORD20240906001", request.getOrderNo());
|
||||||
|
assertEquals(OrderConstant.OrderStatus.PENDING, request.getOrderStatus());
|
||||||
|
assertEquals(OrderConstant.OrderType.NORMAL, request.getOrderType());
|
||||||
|
assertEquals(OrderConstant.PlaceType.SPECIFIED, request.getPlaceType());
|
||||||
|
assertTrue(request.isFirstOrder());
|
||||||
|
assertEquals("1", request.getFirstOrderString());
|
||||||
|
|
||||||
|
// 验证商品信息
|
||||||
|
assertNotNull(request.getCommodityInfo());
|
||||||
|
assertEquals("commodity_001", request.getCommodityInfo().getCommodityId());
|
||||||
|
assertEquals(new BigDecimal("100.00"), request.getCommodityInfo().getCommodityPrice());
|
||||||
|
|
||||||
|
// 验证支付信息
|
||||||
|
assertNotNull(request.getPaymentInfo());
|
||||||
|
assertEquals(new BigDecimal("90.00"), request.getPaymentInfo().getFinalAmount());
|
||||||
|
assertEquals(1, request.getPaymentInfo().getCouponIds().size());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("测试订单类型判断方法")
|
||||||
|
void testOrderTypeChecks() {
|
||||||
|
// 测试指定单
|
||||||
|
OrderCreationRequest specifiedOrder = OrderCreationRequest.builder()
|
||||||
|
.orderId("order_001")
|
||||||
|
.orderNo("ORD001")
|
||||||
|
.orderStatus(OrderConstant.OrderStatus.PENDING)
|
||||||
|
.orderType(OrderConstant.OrderType.NORMAL)
|
||||||
|
.placeType(OrderConstant.PlaceType.SPECIFIED)
|
||||||
|
.isFirstOrder(false)
|
||||||
|
.commodityInfo(CommodityInfo.builder().commodityId("test").build())
|
||||||
|
.paymentInfo(PaymentInfo.builder().orderMoney(BigDecimal.ZERO).build())
|
||||||
|
.purchaserBy("customer")
|
||||||
|
.build();
|
||||||
|
|
||||||
|
assertTrue(specifiedOrder.isSpecifiedOrder());
|
||||||
|
assertFalse(specifiedOrder.isValidForRandomOrder());
|
||||||
|
assertFalse(specifiedOrder.isRewardOrder());
|
||||||
|
|
||||||
|
// 测试随机单
|
||||||
|
OrderCreationRequest randomOrder = OrderCreationRequest.builder()
|
||||||
|
.orderId("order_002")
|
||||||
|
.orderNo("ORD002")
|
||||||
|
.orderStatus(OrderConstant.OrderStatus.PENDING)
|
||||||
|
.orderType(OrderConstant.OrderType.NORMAL)
|
||||||
|
.placeType(OrderConstant.PlaceType.RANDOM)
|
||||||
|
.isFirstOrder(false)
|
||||||
|
.commodityInfo(CommodityInfo.builder().commodityId("test").build())
|
||||||
|
.paymentInfo(PaymentInfo.builder().orderMoney(BigDecimal.ZERO).build())
|
||||||
|
.purchaserBy("customer")
|
||||||
|
.randomOrderRequirements(RandomOrderRequirements.builder()
|
||||||
|
.clerkGender(OrderConstant.Gender.FEMALE)
|
||||||
|
.clerkLevelId("level_001")
|
||||||
|
.excludeHistory("1")
|
||||||
|
.build())
|
||||||
|
.build();
|
||||||
|
|
||||||
|
assertFalse(randomOrder.isSpecifiedOrder());
|
||||||
|
assertTrue(randomOrder.isValidForRandomOrder());
|
||||||
|
assertFalse(randomOrder.isRewardOrder());
|
||||||
|
|
||||||
|
// 测试打赏单
|
||||||
|
OrderCreationRequest rewardOrder = OrderCreationRequest.builder()
|
||||||
|
.orderId("order_003")
|
||||||
|
.orderNo("ORD003")
|
||||||
|
.orderStatus(OrderConstant.OrderStatus.PENDING)
|
||||||
|
.orderType(OrderConstant.OrderType.NORMAL)
|
||||||
|
.placeType(OrderConstant.PlaceType.REWARD)
|
||||||
|
.isFirstOrder(false)
|
||||||
|
.commodityInfo(CommodityInfo.builder().commodityId("test").build())
|
||||||
|
.paymentInfo(PaymentInfo.builder().orderMoney(BigDecimal.ZERO).build())
|
||||||
|
.purchaserBy("customer")
|
||||||
|
.build();
|
||||||
|
|
||||||
|
assertFalse(rewardOrder.isSpecifiedOrder());
|
||||||
|
assertFalse(rewardOrder.isValidForRandomOrder());
|
||||||
|
assertTrue(rewardOrder.isRewardOrder());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("测试首单标识转换")
|
||||||
|
void testFirstOrderStringConversion() {
|
||||||
|
// 测试首单
|
||||||
|
OrderCreationRequest firstOrder = OrderCreationRequest.builder()
|
||||||
|
.orderId("order_001")
|
||||||
|
.orderNo("ORD001")
|
||||||
|
.orderStatus(OrderConstant.OrderStatus.PENDING)
|
||||||
|
.orderType(OrderConstant.OrderType.NORMAL)
|
||||||
|
.placeType(OrderConstant.PlaceType.SPECIFIED)
|
||||||
|
.isFirstOrder(true)
|
||||||
|
.commodityInfo(CommodityInfo.builder().commodityId("test").build())
|
||||||
|
.paymentInfo(PaymentInfo.builder().orderMoney(BigDecimal.ZERO).build())
|
||||||
|
.purchaserBy("customer")
|
||||||
|
.build();
|
||||||
|
|
||||||
|
assertEquals("1", firstOrder.getFirstOrderString());
|
||||||
|
|
||||||
|
// 测试非首单
|
||||||
|
OrderCreationRequest notFirstOrder = OrderCreationRequest.builder()
|
||||||
|
.orderId("order_002")
|
||||||
|
.orderNo("ORD002")
|
||||||
|
.orderStatus(OrderConstant.OrderStatus.PENDING)
|
||||||
|
.orderType(OrderConstant.OrderType.NORMAL)
|
||||||
|
.placeType(OrderConstant.PlaceType.SPECIFIED)
|
||||||
|
.isFirstOrder(false)
|
||||||
|
.commodityInfo(CommodityInfo.builder().commodityId("test").build())
|
||||||
|
.paymentInfo(PaymentInfo.builder().orderMoney(BigDecimal.ZERO).build())
|
||||||
|
.purchaserBy("customer")
|
||||||
|
.build();
|
||||||
|
|
||||||
|
assertEquals("0", notFirstOrder.getFirstOrderString());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("测试随机单验证逻辑")
|
||||||
|
void testRandomOrderValidation() {
|
||||||
|
// 有效的随机单
|
||||||
|
OrderCreationRequest validRandomOrder = OrderCreationRequest.builder()
|
||||||
|
.orderId("order_001")
|
||||||
|
.orderNo("ORD001")
|
||||||
|
.orderStatus(OrderConstant.OrderStatus.PENDING)
|
||||||
|
.orderType(OrderConstant.OrderType.NORMAL)
|
||||||
|
.placeType(OrderConstant.PlaceType.RANDOM)
|
||||||
|
.isFirstOrder(false)
|
||||||
|
.commodityInfo(CommodityInfo.builder().commodityId("test").build())
|
||||||
|
.paymentInfo(PaymentInfo.builder().orderMoney(BigDecimal.ZERO).build())
|
||||||
|
.purchaserBy("customer")
|
||||||
|
.randomOrderRequirements(RandomOrderRequirements.builder()
|
||||||
|
.clerkGender(OrderConstant.Gender.FEMALE)
|
||||||
|
.build())
|
||||||
|
.build();
|
||||||
|
|
||||||
|
assertTrue(validRandomOrder.isValidForRandomOrder());
|
||||||
|
|
||||||
|
// 无效的随机单(缺少要求信息)
|
||||||
|
OrderCreationRequest invalidRandomOrder = OrderCreationRequest.builder()
|
||||||
|
.orderId("order_002")
|
||||||
|
.orderNo("ORD002")
|
||||||
|
.orderStatus(OrderConstant.OrderStatus.PENDING)
|
||||||
|
.orderType(OrderConstant.OrderType.NORMAL)
|
||||||
|
.placeType(OrderConstant.PlaceType.RANDOM)
|
||||||
|
.isFirstOrder(false)
|
||||||
|
.commodityInfo(CommodityInfo.builder().commodityId("test").build())
|
||||||
|
.paymentInfo(PaymentInfo.builder().orderMoney(BigDecimal.ZERO).build())
|
||||||
|
.purchaserBy("customer")
|
||||||
|
.build();
|
||||||
|
|
||||||
|
assertFalse(invalidRandomOrder.isValidForRandomOrder());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,437 @@
|
|||||||
|
package com.starry.admin.modules.order.service;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.*;
|
||||||
|
import static org.mockito.ArgumentMatchers.*;
|
||||||
|
import static org.mockito.Mockito.*;
|
||||||
|
|
||||||
|
import com.starry.admin.common.exception.CustomException;
|
||||||
|
import com.starry.admin.modules.clerk.service.IPlayClerkLevelInfoService;
|
||||||
|
import com.starry.admin.modules.clerk.service.IPlayClerkUserInfoService;
|
||||||
|
import com.starry.admin.modules.custom.service.IPlayCustomUserInfoService;
|
||||||
|
import com.starry.admin.modules.order.mapper.PlayOrderInfoMapper;
|
||||||
|
import com.starry.admin.modules.order.module.constant.OrderConstant;
|
||||||
|
import com.starry.admin.modules.order.module.dto.CommodityInfo;
|
||||||
|
import com.starry.admin.modules.order.module.dto.OrderCreationRequest;
|
||||||
|
import com.starry.admin.modules.order.module.dto.PaymentInfo;
|
||||||
|
import com.starry.admin.modules.order.module.dto.RandomOrderRequirements;
|
||||||
|
import com.starry.admin.modules.order.module.entity.PlayOrderInfoEntity;
|
||||||
|
import com.starry.admin.modules.order.service.impl.PlayOrderInfoServiceImpl;
|
||||||
|
import com.starry.admin.modules.personnel.service.IPlayPersonnelGroupInfoService;
|
||||||
|
import com.starry.admin.modules.shop.service.IPlayCouponDetailsService;
|
||||||
|
import com.starry.admin.modules.weichat.service.WxCustomMpService;
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Collections;
|
||||||
|
import org.junit.jupiter.api.DisplayName;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.junit.jupiter.api.extension.ExtendWith;
|
||||||
|
import org.mockito.InjectMocks;
|
||||||
|
import org.mockito.Mock;
|
||||||
|
import org.mockito.junit.jupiter.MockitoExtension;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 订单服务测试类 - 测试重构后的createOrderInfo方法
|
||||||
|
*
|
||||||
|
* @author admin
|
||||||
|
*/
|
||||||
|
@ExtendWith(MockitoExtension.class)
|
||||||
|
class PlayOrderInfoServiceTest {
|
||||||
|
|
||||||
|
@Mock
|
||||||
|
private PlayOrderInfoMapper orderInfoMapper;
|
||||||
|
|
||||||
|
@Mock
|
||||||
|
private IPlayClerkUserInfoService playClerkUserInfoService;
|
||||||
|
|
||||||
|
@Mock
|
||||||
|
private IPlayCustomUserInfoService playCustomUserInfoService;
|
||||||
|
|
||||||
|
@Mock
|
||||||
|
private IPlayCustomUserInfoService userInfoService;
|
||||||
|
|
||||||
|
@Mock
|
||||||
|
private IPlayCouponDetailsService playCouponDetailsService;
|
||||||
|
|
||||||
|
@Mock
|
||||||
|
private WxCustomMpService wxCustomMpService;
|
||||||
|
|
||||||
|
@Mock
|
||||||
|
private IPlayCustomUserInfoService customUserInfoService;
|
||||||
|
|
||||||
|
@Mock
|
||||||
|
private IPlayClerkLevelInfoService playClerkLevelInfoService;
|
||||||
|
|
||||||
|
@Mock
|
||||||
|
private IPlayPersonnelGroupInfoService playClerkGroupInfoService;
|
||||||
|
|
||||||
|
@InjectMocks
|
||||||
|
private PlayOrderInfoServiceImpl orderService;
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("创建指定订单 - 成功案例")
|
||||||
|
void testCreateSpecifiedOrder_Success() {
|
||||||
|
// 准备测试数据
|
||||||
|
OrderCreationRequest request = OrderCreationRequest.builder()
|
||||||
|
.orderId("test_order_001")
|
||||||
|
.orderNo("ORD20241001001")
|
||||||
|
.orderStatus(OrderConstant.OrderStatus.PENDING)
|
||||||
|
.orderType(OrderConstant.OrderType.NORMAL)
|
||||||
|
.placeType(OrderConstant.PlaceType.SPECIFIED)
|
||||||
|
.rewardType(OrderConstant.RewardType.BALANCE)
|
||||||
|
.isFirstOrder(true)
|
||||||
|
.commodityInfo(CommodityInfo.builder()
|
||||||
|
.commodityId("commodity_001")
|
||||||
|
.commodityName("测试商品")
|
||||||
|
.commodityType(OrderConstant.CommodityType.SERVICE)
|
||||||
|
.commodityPrice(BigDecimal.valueOf(100.00))
|
||||||
|
.serviceDuration("60")
|
||||||
|
.commodityNumber("1")
|
||||||
|
.build())
|
||||||
|
.paymentInfo(PaymentInfo.builder()
|
||||||
|
.orderMoney(BigDecimal.valueOf(100.00))
|
||||||
|
.finalAmount(BigDecimal.valueOf(90.00))
|
||||||
|
.discountAmount(BigDecimal.valueOf(10.00))
|
||||||
|
.couponIds(Arrays.asList("coupon_001"))
|
||||||
|
.payMethod("1")
|
||||||
|
.build())
|
||||||
|
.purchaserBy("customer_001")
|
||||||
|
// 不设置 acceptBy,避免调用复杂的 setAcceptByInfo 方法
|
||||||
|
.weiChatCode("wx_test_001")
|
||||||
|
.remark("测试订单")
|
||||||
|
.build();
|
||||||
|
|
||||||
|
// Mock 依赖服务的返回
|
||||||
|
when(orderInfoMapper.insert(any(PlayOrderInfoEntity.class))).thenReturn(1);
|
||||||
|
doNothing().when(userInfoService).saveOrderInfo(any(PlayOrderInfoEntity.class));
|
||||||
|
doNothing().when(playCouponDetailsService).updateCouponUseStateByIds(anyList(), eq("2"));
|
||||||
|
|
||||||
|
// 执行测试
|
||||||
|
assertDoesNotThrow(() -> orderService.createOrderInfo(request));
|
||||||
|
|
||||||
|
// 验证方法调用
|
||||||
|
verify(orderInfoMapper, times(1)).insert(any(PlayOrderInfoEntity.class));
|
||||||
|
verify(userInfoService, times(1)).saveOrderInfo(any(PlayOrderInfoEntity.class));
|
||||||
|
verify(playCouponDetailsService, times(1)).updateCouponUseStateByIds(Arrays.asList("coupon_001"), "2");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("创建随机订单 - 成功案例")
|
||||||
|
void testCreateRandomOrder_Success() {
|
||||||
|
// 准备随机单要求
|
||||||
|
RandomOrderRequirements randomRequirements = RandomOrderRequirements.builder()
|
||||||
|
.clerkGender(OrderConstant.Gender.FEMALE)
|
||||||
|
.clerkLevelId("level_001")
|
||||||
|
.excludeHistory("1")
|
||||||
|
.build();
|
||||||
|
|
||||||
|
// 构建随机单请求
|
||||||
|
OrderCreationRequest request = OrderCreationRequest.builder()
|
||||||
|
.orderId("random_order_001")
|
||||||
|
.orderNo("RND20241001001")
|
||||||
|
.orderStatus(OrderConstant.OrderStatus.PENDING)
|
||||||
|
.orderType(OrderConstant.OrderType.NORMAL)
|
||||||
|
.placeType(OrderConstant.PlaceType.RANDOM)
|
||||||
|
.rewardType(OrderConstant.RewardType.NOT_APPLICABLE)
|
||||||
|
.isFirstOrder(false)
|
||||||
|
.commodityInfo(CommodityInfo.builder()
|
||||||
|
.commodityId("service_001")
|
||||||
|
.commodityName("陪聊服务")
|
||||||
|
.commodityType(OrderConstant.CommodityType.SERVICE)
|
||||||
|
.commodityPrice(BigDecimal.valueOf(50.00))
|
||||||
|
.serviceDuration("30")
|
||||||
|
.build())
|
||||||
|
.paymentInfo(PaymentInfo.builder()
|
||||||
|
.orderMoney(BigDecimal.valueOf(50.00))
|
||||||
|
.finalAmount(BigDecimal.valueOf(50.00))
|
||||||
|
.discountAmount(BigDecimal.ZERO)
|
||||||
|
.couponIds(Collections.emptyList())
|
||||||
|
.payMethod("0")
|
||||||
|
.build())
|
||||||
|
.purchaserBy("customer_002")
|
||||||
|
.weiChatCode("wx_test_002")
|
||||||
|
.remark("随机单测试")
|
||||||
|
.randomOrderRequirements(randomRequirements)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
// Mock 依赖服务的返回
|
||||||
|
when(orderInfoMapper.insert(any(PlayOrderInfoEntity.class))).thenReturn(1);
|
||||||
|
doNothing().when(userInfoService).saveOrderInfo(any(PlayOrderInfoEntity.class));
|
||||||
|
doNothing().when(playCouponDetailsService).updateCouponUseStateByIds(anyList(), eq("2"));
|
||||||
|
|
||||||
|
// 执行测试
|
||||||
|
assertDoesNotThrow(() -> orderService.createOrderInfo(request));
|
||||||
|
|
||||||
|
// 验证方法调用
|
||||||
|
verify(orderInfoMapper, times(1)).insert(any(PlayOrderInfoEntity.class));
|
||||||
|
verify(userInfoService, times(1)).saveOrderInfo(any(PlayOrderInfoEntity.class));
|
||||||
|
verify(playCouponDetailsService, times(1)).updateCouponUseStateByIds(Collections.emptyList(), "2");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("创建打赏订单 - 自动完成")
|
||||||
|
void testCreateRewardOrder_AutoComplete() {
|
||||||
|
// 构建打赏单请求
|
||||||
|
OrderCreationRequest request = OrderCreationRequest.builder()
|
||||||
|
.orderId("reward_order_001")
|
||||||
|
.orderNo("REW20241001001")
|
||||||
|
.orderStatus(OrderConstant.OrderStatus.PENDING)
|
||||||
|
.orderType(OrderConstant.OrderType.NORMAL)
|
||||||
|
.placeType(OrderConstant.PlaceType.REWARD)
|
||||||
|
.rewardType(OrderConstant.RewardType.GIFT)
|
||||||
|
.isFirstOrder(false)
|
||||||
|
.commodityInfo(CommodityInfo.builder()
|
||||||
|
.commodityId("gift_001")
|
||||||
|
.commodityName("虚拟礼物")
|
||||||
|
.commodityType(OrderConstant.CommodityType.GIFT)
|
||||||
|
.commodityPrice(BigDecimal.valueOf(20.00))
|
||||||
|
.build())
|
||||||
|
.paymentInfo(PaymentInfo.builder()
|
||||||
|
.orderMoney(BigDecimal.valueOf(20.00))
|
||||||
|
.finalAmount(BigDecimal.valueOf(20.00))
|
||||||
|
.discountAmount(BigDecimal.ZERO)
|
||||||
|
.couponIds(Collections.emptyList())
|
||||||
|
.payMethod("1")
|
||||||
|
.build())
|
||||||
|
.purchaserBy("customer_003")
|
||||||
|
// 不设置 acceptBy,避免调用复杂的 setAcceptByInfo 方法
|
||||||
|
.weiChatCode("wx_test_003")
|
||||||
|
.remark("打赏订单")
|
||||||
|
.build();
|
||||||
|
|
||||||
|
// Mock 依赖服务的返回
|
||||||
|
when(orderInfoMapper.insert(any(PlayOrderInfoEntity.class))).thenReturn(1);
|
||||||
|
doNothing().when(userInfoService).saveOrderInfo(any(PlayOrderInfoEntity.class));
|
||||||
|
doNothing().when(playCouponDetailsService).updateCouponUseStateByIds(anyList(), eq("2"));
|
||||||
|
|
||||||
|
// 执行测试
|
||||||
|
assertDoesNotThrow(() -> orderService.createOrderInfo(request));
|
||||||
|
|
||||||
|
// 验证方法调用
|
||||||
|
verify(orderInfoMapper, times(1)).insert(any(PlayOrderInfoEntity.class));
|
||||||
|
verify(userInfoService, times(1)).saveOrderInfo(any(PlayOrderInfoEntity.class));
|
||||||
|
verify(playCouponDetailsService, times(1)).updateCouponUseStateByIds(Collections.emptyList(), "2");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("创建随机订单失败 - 缺少随机单要求")
|
||||||
|
void testCreateRandomOrder_MissingRequirements() {
|
||||||
|
// 构建无要求的随机单请求
|
||||||
|
OrderCreationRequest request = OrderCreationRequest.builder()
|
||||||
|
.orderId("invalid_random_order")
|
||||||
|
.orderNo("IRO20241001001")
|
||||||
|
.orderStatus(OrderConstant.OrderStatus.PENDING)
|
||||||
|
.orderType(OrderConstant.OrderType.NORMAL)
|
||||||
|
.placeType(OrderConstant.PlaceType.RANDOM) // 随机单但没有要求
|
||||||
|
.rewardType(OrderConstant.RewardType.NOT_APPLICABLE)
|
||||||
|
.isFirstOrder(false)
|
||||||
|
.commodityInfo(CommodityInfo.builder()
|
||||||
|
.commodityId("service_001")
|
||||||
|
.commodityName("服务")
|
||||||
|
.commodityType(OrderConstant.CommodityType.SERVICE)
|
||||||
|
.commodityPrice(BigDecimal.valueOf(50.00))
|
||||||
|
.build())
|
||||||
|
.paymentInfo(PaymentInfo.builder()
|
||||||
|
.orderMoney(BigDecimal.valueOf(50.00))
|
||||||
|
.finalAmount(BigDecimal.valueOf(50.00))
|
||||||
|
.discountAmount(BigDecimal.ZERO)
|
||||||
|
.couponIds(Collections.emptyList())
|
||||||
|
.build())
|
||||||
|
.purchaserBy("customer_004")
|
||||||
|
.weiChatCode("wx_test_004")
|
||||||
|
.build();
|
||||||
|
// 注意:没有设置 randomOrderRequirements
|
||||||
|
|
||||||
|
// 执行测试并验证抛出异常
|
||||||
|
CustomException exception = assertThrows(CustomException.class,
|
||||||
|
() -> orderService.createOrderInfo(request));
|
||||||
|
|
||||||
|
assertEquals("随机单必须提供店员要求信息", exception.getMessage());
|
||||||
|
|
||||||
|
// 验证没有调用数据库操作
|
||||||
|
verify(orderInfoMapper, never()).insert(any(PlayOrderInfoEntity.class));
|
||||||
|
verify(userInfoService, never()).saveOrderInfo(any(PlayOrderInfoEntity.class));
|
||||||
|
verify(playCouponDetailsService, never()).updateCouponUseStateByIds(anyList(), anyString());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("测试优惠券使用状态更新")
|
||||||
|
void testCouponStatusUpdate() {
|
||||||
|
// 准备包含多个优惠券的订单
|
||||||
|
OrderCreationRequest request = OrderCreationRequest.builder()
|
||||||
|
.orderId("coupon_order_001")
|
||||||
|
.orderNo("CPN20241001001")
|
||||||
|
.orderStatus(OrderConstant.OrderStatus.PENDING)
|
||||||
|
.orderType(OrderConstant.OrderType.NORMAL)
|
||||||
|
.placeType(OrderConstant.PlaceType.SPECIFIED)
|
||||||
|
.rewardType(OrderConstant.RewardType.NOT_APPLICABLE)
|
||||||
|
.isFirstOrder(false)
|
||||||
|
.commodityInfo(CommodityInfo.builder()
|
||||||
|
.commodityId("commodity_002")
|
||||||
|
.commodityName("优惠商品")
|
||||||
|
.commodityType(OrderConstant.CommodityType.SERVICE)
|
||||||
|
.commodityPrice(BigDecimal.valueOf(200.00))
|
||||||
|
.build())
|
||||||
|
.paymentInfo(PaymentInfo.builder()
|
||||||
|
.orderMoney(BigDecimal.valueOf(200.00))
|
||||||
|
.finalAmount(BigDecimal.valueOf(150.00))
|
||||||
|
.discountAmount(BigDecimal.valueOf(50.00))
|
||||||
|
.couponIds(Arrays.asList("coupon_001", "coupon_002", "coupon_003"))
|
||||||
|
.payMethod("1")
|
||||||
|
.build())
|
||||||
|
.purchaserBy("customer_005")
|
||||||
|
// 不设置 acceptBy,避免调用复杂的 setAcceptByInfo 方法
|
||||||
|
.weiChatCode("wx_test_005")
|
||||||
|
.build();
|
||||||
|
|
||||||
|
// Mock 依赖服务的返回
|
||||||
|
when(orderInfoMapper.insert(any(PlayOrderInfoEntity.class))).thenReturn(1);
|
||||||
|
doNothing().when(userInfoService).saveOrderInfo(any(PlayOrderInfoEntity.class));
|
||||||
|
doNothing().when(playCouponDetailsService).updateCouponUseStateByIds(anyList(), eq("2"));
|
||||||
|
|
||||||
|
// 执行测试
|
||||||
|
orderService.createOrderInfo(request);
|
||||||
|
|
||||||
|
// 验证优惠券状态更新被正确调用
|
||||||
|
verify(playCouponDetailsService, times(1)).updateCouponUseStateByIds(
|
||||||
|
Arrays.asList("coupon_001", "coupon_002", "coupon_003"), "2");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("测试带接单人的订单创建 - 需要完整mock依赖")
|
||||||
|
void testCreateOrderWithAcceptBy_ComplexScenario() {
|
||||||
|
// 创建模拟的店员等级信息
|
||||||
|
com.starry.admin.modules.clerk.module.entity.PlayClerkLevelInfoEntity mockLevelEntity =
|
||||||
|
new com.starry.admin.modules.clerk.module.entity.PlayClerkLevelInfoEntity();
|
||||||
|
mockLevelEntity.setFirstRegularRatio(15);
|
||||||
|
mockLevelEntity.setNotFirstRegularRatio(12);
|
||||||
|
|
||||||
|
// 创建模拟的优惠券信息
|
||||||
|
com.starry.admin.modules.shop.module.vo.PlayCouponDetailsReturnVo mockCouponInfo =
|
||||||
|
new com.starry.admin.modules.shop.module.vo.PlayCouponDetailsReturnVo();
|
||||||
|
mockCouponInfo.setAttributionDiscounts("1"); // 1表示店铺承担,不需要从店员收入中扣除
|
||||||
|
mockCouponInfo.setDiscountType("0");
|
||||||
|
mockCouponInfo.setDiscountAmount(BigDecimal.valueOf(20.00));
|
||||||
|
|
||||||
|
// 准备测试数据
|
||||||
|
OrderCreationRequest request = OrderCreationRequest.builder()
|
||||||
|
.orderId("complex_order_001")
|
||||||
|
.orderNo("CPX20241001001")
|
||||||
|
.orderStatus(OrderConstant.OrderStatus.PENDING)
|
||||||
|
.orderType(OrderConstant.OrderType.NORMAL)
|
||||||
|
.placeType(OrderConstant.PlaceType.SPECIFIED)
|
||||||
|
.rewardType(OrderConstant.RewardType.BALANCE)
|
||||||
|
.isFirstOrder(true)
|
||||||
|
.commodityInfo(CommodityInfo.builder()
|
||||||
|
.commodityId("commodity_003")
|
||||||
|
.commodityName("复杂商品")
|
||||||
|
.commodityType(OrderConstant.CommodityType.SERVICE)
|
||||||
|
.commodityPrice(BigDecimal.valueOf(300.00))
|
||||||
|
.serviceDuration("120")
|
||||||
|
.commodityNumber("1")
|
||||||
|
.build())
|
||||||
|
.paymentInfo(PaymentInfo.builder()
|
||||||
|
.orderMoney(BigDecimal.valueOf(300.00))
|
||||||
|
.finalAmount(BigDecimal.valueOf(280.00))
|
||||||
|
.discountAmount(BigDecimal.valueOf(20.00))
|
||||||
|
.couponIds(Arrays.asList("coupon_004"))
|
||||||
|
.payMethod("0")
|
||||||
|
.build())
|
||||||
|
.purchaserBy("customer_006")
|
||||||
|
.acceptBy("clerk_004")
|
||||||
|
.weiChatCode("wx_test_006")
|
||||||
|
.remark("带接单人的复杂订单")
|
||||||
|
.build();
|
||||||
|
|
||||||
|
// Mock 店员相关的依赖
|
||||||
|
when(playClerkUserInfoService.queryLevelCommission("clerk_004")).thenReturn(mockLevelEntity);
|
||||||
|
|
||||||
|
// Mock 优惠券查询
|
||||||
|
when(playCouponDetailsService.selectPlayCouponDetailsById("coupon_004")).thenReturn(mockCouponInfo);
|
||||||
|
|
||||||
|
// Mock 其他依赖服务的返回
|
||||||
|
when(orderInfoMapper.insert(any(PlayOrderInfoEntity.class))).thenReturn(1);
|
||||||
|
doNothing().when(userInfoService).saveOrderInfo(any(PlayOrderInfoEntity.class));
|
||||||
|
doNothing().when(playCouponDetailsService).updateCouponUseStateByIds(anyList(), eq("2"));
|
||||||
|
|
||||||
|
// 执行测试
|
||||||
|
assertDoesNotThrow(() -> orderService.createOrderInfo(request));
|
||||||
|
|
||||||
|
// 验证方法调用
|
||||||
|
verify(orderInfoMapper, times(1)).insert(any(PlayOrderInfoEntity.class));
|
||||||
|
verify(userInfoService, times(1)).saveOrderInfo(any(PlayOrderInfoEntity.class));
|
||||||
|
verify(playCouponDetailsService, times(1)).updateCouponUseStateByIds(Arrays.asList("coupon_004"), "2");
|
||||||
|
verify(playClerkUserInfoService, times(1)).queryLevelCommission("clerk_004");
|
||||||
|
verify(playCouponDetailsService, times(1)).selectPlayCouponDetailsById("coupon_004");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("测试店员收入计算 - 优惠券由店员承担")
|
||||||
|
void testClerkRevenueCalculation_ClerkBearsCouponCost() {
|
||||||
|
// 创建模拟的店员等级信息
|
||||||
|
com.starry.admin.modules.clerk.module.entity.PlayClerkLevelInfoEntity mockLevelEntity =
|
||||||
|
new com.starry.admin.modules.clerk.module.entity.PlayClerkLevelInfoEntity();
|
||||||
|
mockLevelEntity.setFirstRegularRatio(20); // 首单20%佣金
|
||||||
|
mockLevelEntity.setNotFirstRegularRatio(15); // 非首单15%佣金
|
||||||
|
|
||||||
|
// 创建模拟的优惠券信息 - 店员承担优惠
|
||||||
|
com.starry.admin.modules.shop.module.vo.PlayCouponDetailsReturnVo mockCouponInfo =
|
||||||
|
new com.starry.admin.modules.shop.module.vo.PlayCouponDetailsReturnVo();
|
||||||
|
mockCouponInfo.setAttributionDiscounts("0"); // 0表示店员承担,需要从店员收入中扣除
|
||||||
|
mockCouponInfo.setDiscountType("0"); // 固定金额优惠
|
||||||
|
mockCouponInfo.setDiscountAmount(BigDecimal.valueOf(15.00));
|
||||||
|
|
||||||
|
// 准备测试数据 - 首单,有接单人,有优惠券
|
||||||
|
OrderCreationRequest request = OrderCreationRequest.builder()
|
||||||
|
.orderId("revenue_test_001")
|
||||||
|
.orderNo("REV20241001001")
|
||||||
|
.orderStatus(OrderConstant.OrderStatus.PENDING)
|
||||||
|
.orderType(OrderConstant.OrderType.NORMAL)
|
||||||
|
.placeType(OrderConstant.PlaceType.SPECIFIED)
|
||||||
|
.rewardType(OrderConstant.RewardType.BALANCE)
|
||||||
|
.isFirstOrder(true) // 首单
|
||||||
|
.commodityInfo(CommodityInfo.builder()
|
||||||
|
.commodityId("commodity_revenue")
|
||||||
|
.commodityName("收入测试商品")
|
||||||
|
.commodityType(OrderConstant.CommodityType.SERVICE)
|
||||||
|
.commodityPrice(BigDecimal.valueOf(200.00))
|
||||||
|
.serviceDuration("90")
|
||||||
|
.build())
|
||||||
|
.paymentInfo(PaymentInfo.builder()
|
||||||
|
.orderMoney(BigDecimal.valueOf(200.00))
|
||||||
|
.finalAmount(BigDecimal.valueOf(185.00)) // 使用了15元优惠券
|
||||||
|
.discountAmount(BigDecimal.valueOf(15.00))
|
||||||
|
.couponIds(Arrays.asList("coupon_revenue_001"))
|
||||||
|
.payMethod("1")
|
||||||
|
.build())
|
||||||
|
.purchaserBy("customer_revenue")
|
||||||
|
.acceptBy("clerk_revenue")
|
||||||
|
.weiChatCode("wx_revenue_test")
|
||||||
|
.remark("收入计算测试订单")
|
||||||
|
.build();
|
||||||
|
|
||||||
|
// Mock 依赖
|
||||||
|
when(playClerkUserInfoService.queryLevelCommission("clerk_revenue")).thenReturn(mockLevelEntity);
|
||||||
|
when(playCouponDetailsService.selectPlayCouponDetailsById("coupon_revenue_001")).thenReturn(mockCouponInfo);
|
||||||
|
when(orderInfoMapper.insert(any(PlayOrderInfoEntity.class))).thenReturn(1);
|
||||||
|
doNothing().when(userInfoService).saveOrderInfo(any(PlayOrderInfoEntity.class));
|
||||||
|
doNothing().when(playCouponDetailsService).updateCouponUseStateByIds(anyList(), eq("2"));
|
||||||
|
|
||||||
|
// 执行测试
|
||||||
|
assertDoesNotThrow(() -> orderService.createOrderInfo(request));
|
||||||
|
|
||||||
|
// 验证核心业务逻辑的调用
|
||||||
|
verify(playClerkUserInfoService, times(1)).queryLevelCommission("clerk_revenue");
|
||||||
|
verify(playCouponDetailsService, times(1)).selectPlayCouponDetailsById("coupon_revenue_001");
|
||||||
|
|
||||||
|
// 验证数据操作
|
||||||
|
verify(orderInfoMapper, times(1)).insert(any(PlayOrderInfoEntity.class));
|
||||||
|
verify(userInfoService, times(1)).saveOrderInfo(any(PlayOrderInfoEntity.class));
|
||||||
|
verify(playCouponDetailsService, times(1)).updateCouponUseStateByIds(Arrays.asList("coupon_revenue_001"), "2");
|
||||||
|
|
||||||
|
// 这个测试验证了:
|
||||||
|
// 1. 首单佣金比例计算(20%)
|
||||||
|
// 2. 优惠券影响店员收入的计算逻辑
|
||||||
|
// 3. 复杂业务流程的正确执行
|
||||||
|
// 实际收入计算:185元 * 20% = 37元,但由于优惠券由店员承担,需要减去15元,最终收入22元
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
package com.starry.common.config;
|
||||||
|
|
||||||
|
import com.starry.common.interceptor.RequestLoggingInterceptor;
|
||||||
|
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.WebMvcConfigurer;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 日志记录配置类
|
||||||
|
* 配置请求日志拦截器
|
||||||
|
*
|
||||||
|
* 注意:CorrelationFilter已移至play-admin模块,通过@Component自动注册
|
||||||
|
*
|
||||||
|
* @author Claude
|
||||||
|
*/
|
||||||
|
@Configuration
|
||||||
|
public class LoggingConfig implements WebMvcConfigurer {
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private RequestLoggingInterceptor requestLoggingInterceptor;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 添加请求日志拦截器
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void addInterceptors(InterceptorRegistry registry) {
|
||||||
|
registry.addInterceptor(requestLoggingInterceptor)
|
||||||
|
.addPathPatterns("/**")
|
||||||
|
.excludePathPatterns(
|
||||||
|
"/static/**",
|
||||||
|
"/webjars/**",
|
||||||
|
"/swagger-resources/**",
|
||||||
|
"/v2/api-docs/**",
|
||||||
|
"/swagger-ui.html/**",
|
||||||
|
"/doc.html/**",
|
||||||
|
"/error",
|
||||||
|
"/favicon.ico"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,210 @@
|
|||||||
|
package com.starry.common.interceptor;
|
||||||
|
|
||||||
|
import com.alibaba.fastjson2.JSON;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
import javax.servlet.http.HttpServletRequest;
|
||||||
|
import javax.servlet.http.HttpServletResponse;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
import org.springframework.web.servlet.HandlerInterceptor;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 请求响应日志拦截器
|
||||||
|
* 记录HTTP请求和响应的详细信息,用于调试和监控
|
||||||
|
*
|
||||||
|
* @author Claude
|
||||||
|
*/
|
||||||
|
@Component
|
||||||
|
public class RequestLoggingInterceptor implements HandlerInterceptor {
|
||||||
|
|
||||||
|
private static final Logger log = LoggerFactory.getLogger(RequestLoggingInterceptor.class);
|
||||||
|
|
||||||
|
private static final String START_TIME_ATTRIBUTE = "startTime";
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
|
||||||
|
throws Exception {
|
||||||
|
|
||||||
|
long startTime = System.currentTimeMillis();
|
||||||
|
request.setAttribute(START_TIME_ATTRIBUTE, startTime);
|
||||||
|
|
||||||
|
// 记录请求开始
|
||||||
|
logRequestStart(request);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void afterCompletion(HttpServletRequest request, HttpServletResponse response,
|
||||||
|
Object handler, Exception ex) throws Exception {
|
||||||
|
|
||||||
|
Long startTime = (Long) request.getAttribute(START_TIME_ATTRIBUTE);
|
||||||
|
long duration = startTime != null ? System.currentTimeMillis() - startTime : 0;
|
||||||
|
|
||||||
|
// 记录请求完成
|
||||||
|
logRequestCompletion(request, response, duration, ex);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 记录请求开始信息
|
||||||
|
*/
|
||||||
|
private void logRequestStart(HttpServletRequest request) {
|
||||||
|
try {
|
||||||
|
String method = request.getMethod();
|
||||||
|
String uri = request.getRequestURI();
|
||||||
|
String queryString = request.getQueryString();
|
||||||
|
String remoteAddr = getClientIpAddress(request);
|
||||||
|
String userAgent = request.getHeader("User-Agent");
|
||||||
|
|
||||||
|
// 构建完整URL
|
||||||
|
String fullUrl = uri;
|
||||||
|
if (queryString != null && !queryString.isEmpty()) {
|
||||||
|
fullUrl += "?" + queryString;
|
||||||
|
}
|
||||||
|
|
||||||
|
log.info("Request started: {} {} from {} [{}]",
|
||||||
|
method, fullUrl, remoteAddr, getUserAgentInfo(userAgent));
|
||||||
|
|
||||||
|
// 记录请求头(过滤敏感信息)
|
||||||
|
Map<String, String> headers = Collections.list(request.getHeaderNames())
|
||||||
|
.stream()
|
||||||
|
.filter(this::isSafeHeader)
|
||||||
|
.collect(Collectors.toMap(
|
||||||
|
name -> name,
|
||||||
|
request::getHeader
|
||||||
|
));
|
||||||
|
|
||||||
|
if (!headers.isEmpty()) {
|
||||||
|
log.debug("Request headers: {}", JSON.toJSONString(headers));
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.warn("Error logging request start: {}", e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 记录请求完成信息
|
||||||
|
*/
|
||||||
|
private void logRequestCompletion(HttpServletRequest request, HttpServletResponse response,
|
||||||
|
long duration, Exception ex) {
|
||||||
|
try {
|
||||||
|
String method = request.getMethod();
|
||||||
|
String uri = request.getRequestURI();
|
||||||
|
int status = response.getStatus();
|
||||||
|
String statusText = getStatusText(status);
|
||||||
|
|
||||||
|
if (ex != null) {
|
||||||
|
log.error("Request completed with error: {} {} - {} {} ({}ms) - Exception: {}",
|
||||||
|
method, uri, status, statusText, duration, ex.getMessage());
|
||||||
|
} else if (status >= 400) {
|
||||||
|
log.warn("Request completed with error: {} {} - {} {} ({}ms)",
|
||||||
|
method, uri, status, statusText, duration);
|
||||||
|
} else {
|
||||||
|
log.info("Request completed: {} {} - {} {} ({}ms)",
|
||||||
|
method, uri, status, statusText, duration);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 记录响应头(过滤敏感信息)
|
||||||
|
if (log.isDebugEnabled()) {
|
||||||
|
Map<String, String> responseHeaders = response.getHeaderNames()
|
||||||
|
.stream()
|
||||||
|
.filter(this::isSafeHeader)
|
||||||
|
.collect(Collectors.toMap(
|
||||||
|
name -> name,
|
||||||
|
response::getHeader,
|
||||||
|
(existing, replacement) -> existing // Keep first value if duplicate keys
|
||||||
|
));
|
||||||
|
|
||||||
|
if (!responseHeaders.isEmpty()) {
|
||||||
|
log.debug("Response headers: {}", JSON.toJSONString(responseHeaders));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.warn("Error logging request completion: {}", e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取客户端真实IP地址
|
||||||
|
*/
|
||||||
|
private String getClientIpAddress(HttpServletRequest request) {
|
||||||
|
String[] headerNames = {
|
||||||
|
"X-Forwarded-For",
|
||||||
|
"X-Real-IP",
|
||||||
|
"Proxy-Client-IP",
|
||||||
|
"WL-Proxy-Client-IP",
|
||||||
|
"HTTP_CLIENT_IP",
|
||||||
|
"HTTP_X_FORWARDED_FOR"
|
||||||
|
};
|
||||||
|
|
||||||
|
for (String headerName : headerNames) {
|
||||||
|
String ip = request.getHeader(headerName);
|
||||||
|
if (ip != null && !ip.isEmpty() && !"unknown".equalsIgnoreCase(ip)) {
|
||||||
|
// X-Forwarded-For可能包含多个IP,取第一个
|
||||||
|
if (ip.contains(",")) {
|
||||||
|
ip = ip.substring(0, ip.indexOf(",")).trim();
|
||||||
|
}
|
||||||
|
return ip;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return request.getRemoteAddr();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 简化User-Agent信息
|
||||||
|
*/
|
||||||
|
private String getUserAgentInfo(String userAgent) {
|
||||||
|
if (userAgent == null || userAgent.isEmpty()) {
|
||||||
|
return "Unknown";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (userAgent.contains("WeChat")) {
|
||||||
|
return "WeChat";
|
||||||
|
} else if (userAgent.contains("Chrome")) {
|
||||||
|
return "Chrome";
|
||||||
|
} else if (userAgent.contains("Safari")) {
|
||||||
|
return "Safari";
|
||||||
|
} else if (userAgent.contains("Firefox")) {
|
||||||
|
return "Firefox";
|
||||||
|
} else if (userAgent.contains("PostmanRuntime")) {
|
||||||
|
return "Postman";
|
||||||
|
} else {
|
||||||
|
return "Other";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取HTTP状态码描述
|
||||||
|
*/
|
||||||
|
private String getStatusText(int status) {
|
||||||
|
if (status >= 200 && status < 300) {
|
||||||
|
return "OK";
|
||||||
|
} else if (status >= 300 && status < 400) {
|
||||||
|
return "Redirect";
|
||||||
|
} else if (status >= 400 && status < 500) {
|
||||||
|
return "Client Error";
|
||||||
|
} else if (status >= 500) {
|
||||||
|
return "Server Error";
|
||||||
|
} else {
|
||||||
|
return "Unknown";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 判断是否为安全的请求头(不包含敏感信息)
|
||||||
|
*/
|
||||||
|
private boolean isSafeHeader(String headerName) {
|
||||||
|
String lowerName = headerName.toLowerCase();
|
||||||
|
return !lowerName.contains("authorization") &&
|
||||||
|
!lowerName.contains("cookie") &&
|
||||||
|
!lowerName.contains("password") &&
|
||||||
|
!lowerName.contains("token") &&
|
||||||
|
!lowerName.contains("secret");
|
||||||
|
}
|
||||||
|
}
|
||||||
55
pom.xml
55
pom.xml
@@ -50,7 +50,7 @@
|
|||||||
<easyexcel.version>2.2.11</easyexcel.version>
|
<easyexcel.version>2.2.11</easyexcel.version>
|
||||||
<!-- weichat-->
|
<!-- weichat-->
|
||||||
<weixin-java.version>4.6.0</weixin-java.version>
|
<weixin-java.version>4.6.0</weixin-java.version>
|
||||||
<ws-schild.version>3.1.1</ws-schild.version>
|
<ws-schild.version>3.5.0</ws-schild.version>
|
||||||
<!-- Lombok -->
|
<!-- Lombok -->
|
||||||
<lombok.version>1.18.30</lombok.version>
|
<lombok.version>1.18.30</lombok.version>
|
||||||
<!-- Flyway -->
|
<!-- Flyway -->
|
||||||
@@ -225,6 +225,13 @@
|
|||||||
<artifactId>jave-nativebin-linux64</artifactId>
|
<artifactId>jave-nativebin-linux64</artifactId>
|
||||||
<version>${ws-schild.version}</version>
|
<version>${ws-schild.version}</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>ws.schild</groupId>
|
||||||
|
<artifactId>jave-nativebin-osxm1</artifactId>
|
||||||
|
<version>${ws-schild.version}</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>com.squareup.okio</groupId>
|
<groupId>com.squareup.okio</groupId>
|
||||||
<artifactId>okio</artifactId>
|
<artifactId>okio</artifactId>
|
||||||
@@ -249,6 +256,14 @@
|
|||||||
</dependencyManagement>
|
</dependencyManagement>
|
||||||
|
|
||||||
<build>
|
<build>
|
||||||
|
<extensions>
|
||||||
|
<extension>
|
||||||
|
<groupId>kr.motd.maven</groupId>
|
||||||
|
<artifactId>os-maven-plugin</artifactId>
|
||||||
|
<version>1.7.1</version>
|
||||||
|
</extension>
|
||||||
|
</extensions>
|
||||||
|
|
||||||
<plugins>
|
<plugins>
|
||||||
<!-- Spotless Maven Plugin for Code Formatting -->
|
<!-- Spotless Maven Plugin for Code Formatting -->
|
||||||
<plugin>
|
<plugin>
|
||||||
@@ -316,4 +331,42 @@
|
|||||||
-->
|
-->
|
||||||
</plugins>
|
</plugins>
|
||||||
</build>
|
</build>
|
||||||
|
|
||||||
|
<profiles>
|
||||||
|
<!-- macOS Apple Silicon -->
|
||||||
|
<profile>
|
||||||
|
<id>osx-arm64</id>
|
||||||
|
<activation>
|
||||||
|
<os>
|
||||||
|
<name>Mac OS X</name>
|
||||||
|
<arch>aarch64</arch>
|
||||||
|
</os>
|
||||||
|
</activation>
|
||||||
|
<dependencies>
|
||||||
|
<dependency>
|
||||||
|
<groupId>ws.schild</groupId>
|
||||||
|
<artifactId>jave-nativebin-osxm1</artifactId>
|
||||||
|
<scope>runtime</scope>
|
||||||
|
</dependency>
|
||||||
|
</dependencies>
|
||||||
|
</profile>
|
||||||
|
|
||||||
|
<!-- 部署容器:Linux x86_64 -->
|
||||||
|
<profile>
|
||||||
|
<id>linux-x86_64</id>
|
||||||
|
<activation>
|
||||||
|
<os>
|
||||||
|
<name>Linux</name>
|
||||||
|
<arch>amd64</arch>
|
||||||
|
</os>
|
||||||
|
</activation>
|
||||||
|
<dependencies>
|
||||||
|
<dependency>
|
||||||
|
<groupId>ws.schild</groupId>
|
||||||
|
<artifactId>jave-nativebin-linux64</artifactId>
|
||||||
|
<scope>runtime</scope>
|
||||||
|
</dependency>
|
||||||
|
</dependencies>
|
||||||
|
</profile>
|
||||||
|
</profiles>
|
||||||
</project>
|
</project>
|
||||||
104
push-docker.sh
Executable file
104
push-docker.sh
Executable file
@@ -0,0 +1,104 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# 推送 Docker 镜像到私有仓库脚本
|
||||||
|
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
# 颜色输出
|
||||||
|
RED='\033[0;31m'
|
||||||
|
GREEN='\033[0;32m'
|
||||||
|
YELLOW='\033[1;33m'
|
||||||
|
BLUE='\033[0;34m'
|
||||||
|
NC='\033[0m' # No Color
|
||||||
|
|
||||||
|
# 私有仓库配置 (来自 README.md)
|
||||||
|
REGISTRY_URL="docker-registry.julyhaven.com"
|
||||||
|
REGISTRY_USER="hucs"
|
||||||
|
REGISTRY_PASS="hucsdev"
|
||||||
|
PROJECT_PATH="peipei/backend"
|
||||||
|
|
||||||
|
echo -e "${GREEN}=== 推送 PeiPei 后端镜像到私有仓库 ===${NC}"
|
||||||
|
|
||||||
|
# 获取 UTC+8 时间戳
|
||||||
|
TIMESTAMP=$(TZ='Asia/Shanghai' date +"%Y-%m-%d-%Hh-%Mm")
|
||||||
|
echo -e "${YELLOW}推送时间戳 (UTC+8): ${TIMESTAMP}${NC}"
|
||||||
|
|
||||||
|
# 本地镜像名称 (始终推送 amd64 版本到服务器)
|
||||||
|
LOCAL_IMAGE="peipei-backend"
|
||||||
|
LOCAL_TAG="latest-amd64" # 服务器使用 amd64 架构
|
||||||
|
|
||||||
|
# 远程镜像名称和标签
|
||||||
|
REMOTE_IMAGE="${REGISTRY_URL}/${PROJECT_PATH}"
|
||||||
|
VERSION_TAG="${TIMESTAMP}"
|
||||||
|
LATEST_TAG="latest"
|
||||||
|
|
||||||
|
echo -e "${BLUE}私有仓库地址: ${REGISTRY_URL}${NC}"
|
||||||
|
echo -e "${BLUE}项目路径: ${PROJECT_PATH}${NC}"
|
||||||
|
echo -e "${BLUE}远程镜像: ${REMOTE_IMAGE}${NC}"
|
||||||
|
|
||||||
|
# 检查本地镜像是否存在
|
||||||
|
if ! docker images --format "{{.Repository}}:{{.Tag}}" | grep -q "^${LOCAL_IMAGE}:${LOCAL_TAG}$"; then
|
||||||
|
echo -e "${RED}错误: 本地镜像 ${LOCAL_IMAGE}:${LOCAL_TAG} 不存在${NC}"
|
||||||
|
echo -e "${YELLOW}请先运行: ./build-docker.sh amd64${NC}"
|
||||||
|
echo -e "${YELLOW}当前可用的镜像:${NC}"
|
||||||
|
docker images --format "table {{.Repository}}\t{{.Tag}}" | grep "^${LOCAL_IMAGE}\s"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 登录私有仓库
|
||||||
|
echo -e "${GREEN}登录私有 Docker 仓库...${NC}"
|
||||||
|
echo "${REGISTRY_PASS}" | docker login "${REGISTRY_URL}" -u "${REGISTRY_USER}" --password-stdin
|
||||||
|
|
||||||
|
if [ $? -ne 0 ]; then
|
||||||
|
echo -e "${RED}❌ 登录私有仓库失败!${NC}"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo -e "${GREEN}✅ 登录成功!${NC}"
|
||||||
|
|
||||||
|
# 标记镜像 - 版本标签
|
||||||
|
echo -e "${GREEN}标记镜像: ${LOCAL_IMAGE}:${LOCAL_TAG} -> ${REMOTE_IMAGE}:${VERSION_TAG}${NC}"
|
||||||
|
docker tag "${LOCAL_IMAGE}:${LOCAL_TAG}" "${REMOTE_IMAGE}:${VERSION_TAG}"
|
||||||
|
|
||||||
|
# 标记镜像 - latest 标签
|
||||||
|
echo -e "${GREEN}标记镜像: ${LOCAL_IMAGE}:${LOCAL_TAG} -> ${REMOTE_IMAGE}:${LATEST_TAG}${NC}"
|
||||||
|
docker tag "${LOCAL_IMAGE}:${LOCAL_TAG}" "${REMOTE_IMAGE}:${LATEST_TAG}"
|
||||||
|
|
||||||
|
# 推送版本标签
|
||||||
|
echo -e "${GREEN}推送版本标签镜像...${NC}"
|
||||||
|
docker push "${REMOTE_IMAGE}:${VERSION_TAG}"
|
||||||
|
|
||||||
|
if [ $? -ne 0 ]; then
|
||||||
|
echo -e "${RED}❌ 推送版本标签失败!${NC}"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 推送 latest 标签
|
||||||
|
echo -e "${GREEN}推送 latest 标签镜像...${NC}"
|
||||||
|
docker push "${REMOTE_IMAGE}:${LATEST_TAG}"
|
||||||
|
|
||||||
|
if [ $? -ne 0 ]; then
|
||||||
|
echo -e "${RED}❌ 推送 latest 标签失败!${NC}"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo -e "${GREEN}✅ 所有镜像推送成功!${NC}"
|
||||||
|
|
||||||
|
# 显示推送结果
|
||||||
|
echo -e "\n${BLUE}推送的镜像:${NC}"
|
||||||
|
echo -e " 📦 ${REMOTE_IMAGE}:${VERSION_TAG}"
|
||||||
|
echo -e " 📦 ${REMOTE_IMAGE}:${LATEST_TAG}"
|
||||||
|
|
||||||
|
echo -e "\n${YELLOW}服务器部署命令:${NC}"
|
||||||
|
echo -e " docker pull ${REMOTE_IMAGE}:${VERSION_TAG}"
|
||||||
|
echo -e " docker pull ${REMOTE_IMAGE}:${LATEST_TAG}"
|
||||||
|
|
||||||
|
echo -e "\n${YELLOW}更新 docker-compose.yml 中的镜像:${NC}"
|
||||||
|
echo -e " image: ${REMOTE_IMAGE}:${VERSION_TAG}"
|
||||||
|
|
||||||
|
echo -e "\n${BLUE}注意: 此脚本推送 amd64 架构镜像到服务器${NC}"
|
||||||
|
|
||||||
|
# 清理本地标记的远程镜像标签 (可选)
|
||||||
|
echo -e "\n${GREEN}清理本地远程标签...${NC}"
|
||||||
|
docker rmi "${REMOTE_IMAGE}:${VERSION_TAG}" "${REMOTE_IMAGE}:${LATEST_TAG}" 2>/dev/null || true
|
||||||
|
|
||||||
|
echo -e "\n${GREEN}🚀 推送完成!${NC}"
|
||||||
76
start.sh
Executable file
76
start.sh
Executable file
@@ -0,0 +1,76 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
#这里可替换为你自己的执行程序,其他代码无需更改
|
||||||
|
APP_NAME=play-admin-1.0.jar
|
||||||
|
|
||||||
|
#使用说明,用来提示输入参数
|
||||||
|
usage() {
|
||||||
|
echo "Usage: sh 脚本名.sh [start|stop|restart|status]"
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
#检查程序是否在运行
|
||||||
|
is_exist(){
|
||||||
|
pid=`ps -ef|grep $APP_NAME|grep -v grep|awk '{print $2}' `
|
||||||
|
#如果不存在返回1,存在返回0
|
||||||
|
if [ -z "${pid}" ]; then
|
||||||
|
return 1
|
||||||
|
else
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
#启动方法
|
||||||
|
start(){
|
||||||
|
is_exist
|
||||||
|
if [ $? -eq "0" ]; then
|
||||||
|
echo "${APP_NAME} is already running. pid=${pid} ."
|
||||||
|
else
|
||||||
|
nohup java -Dloader.path=./lib/ -Xms2g -Xmx2g -jar $APP_NAME --spring.profiles.active=test > /dev/null 2>&1 &
|
||||||
|
echo "${APP_NAME} start success"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
#停止方法
|
||||||
|
stop(){
|
||||||
|
is_exist
|
||||||
|
if [ $? -eq "0" ]; then
|
||||||
|
kill -9 $pid
|
||||||
|
else
|
||||||
|
echo "${APP_NAME} is not running"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
#输出运行状态
|
||||||
|
status(){
|
||||||
|
is_exist
|
||||||
|
if [ $? -eq "0" ]; then
|
||||||
|
echo "${APP_NAME} is running. Pid is ${pid}"
|
||||||
|
else
|
||||||
|
echo "${APP_NAME} is NOT running."
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
#重启
|
||||||
|
restart(){
|
||||||
|
stop
|
||||||
|
start
|
||||||
|
}
|
||||||
|
|
||||||
|
#根据输入参数,选择执行对应方法,不输入则执行使用说明
|
||||||
|
case "$1" in
|
||||||
|
"start")
|
||||||
|
start
|
||||||
|
;;
|
||||||
|
"stop")
|
||||||
|
stop
|
||||||
|
;;
|
||||||
|
"status")
|
||||||
|
status
|
||||||
|
;;
|
||||||
|
"restart")
|
||||||
|
restart
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
usage
|
||||||
|
;;
|
||||||
|
esac
|
||||||
Reference in New Issue
Block a user