--- File: .gitignore ---
# Compiled class file
*.class

# Log file
*.log

# BlueJ files
*.ctxt

# Mobile Tools for Java (J2ME)
.mtj.tmp/

# Package Files #
*.jar
*.war
*.nar
*.ear
*.zip
*.tar.gz
*.rar

# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
hs_err_pid*
target
.idea
*.iml
nb-configuration.xml
nbactions.xml
.project
.settings
*/.settings
*/.project
.classpath
*/.idea
.idea/
.vscode/
.settings/
target/
ideaproj/
*/bin
.DS_Store
*.ipr
*.iws
bin/
nbproject/
.springBeans
/log/
/logs/
/storage/
/var/log/*
*.factorypath
/storage/

--- File: README.md ---
# PeiPei 后端项目

基于 Spring Boot 的多模块项目，为 PeiPei 应用提供后端服务。

## 项目结构

- **play-admin**: 主要的 Spring Boot 应用模块
- **play-common**: 公共工具类和共享代码
- **play-generator**: 代码生成工具

## 构建要求

- **Java 11** (必须)
- **Maven 3.6+**

## 快速开始

### 1. 安装 Java 11

在 macOS 上使用 Homebrew:
```bash
brew install --cask zulu@11
```

设置 Java 11 为默认版本:

#### 临时设置 (当前会话):
```bash
export JAVA_HOME=$(/usr/libexec/java_home -v 11)
```

#### 永久设置方法:

**方法1: Shell 配置 (推荐)**
添加到 `~/.zshrc` 或 `~/.bash_profile`:
```bash
export JAVA_HOME=$(/usr/libexec/java_home -v 11)
export PATH="$JAVA_HOME/bin:$PATH"
```

**方法2: Maven 专用**
创建 `~/.mavenrc`:
```bash
export JAVA_HOME=$(/usr/libexec/java_home -v 11)
```

**方法3: VS Code 项目配置**
项目已配置 `.vscode/settings.json` 使用 Java 11:
```json
{
    "java.configuration.runtimes": [
        {
            "name": "JavaSE-11",
            "path": "/usr/libexec/java_home -v 11"
        }
    ],
    "java.jdt.ls.java.home": "/usr/libexec/java_home -v 11"
}
```

### 2. 构建项目

```bash
# 清理并构建所有模块
mvn clean install

# 或者仅编译
mvn clean compile
```

### 3. 运行应用

```bash
# 运行主应用
java -jar play-admin/target/play-admin-1.0.jar

# 或使用 Maven
cd play-admin
mvn spring-boot:run
```

## 配置说明

项目在所有模块中统一使用 Java 11:
- 所有模块都配置为 Java 11 源码和目标版本
- Lombok 注解自动处理
- 无需显式配置注解处理器

## 开发说明

- 项目已更新为所有模块统一使用 Java 11
- Lombok 依赖使用 `scope=provided` 启用自动注解处理
- Maven 编译插件继承 Spring Boot 父 POM 配置

### 代码格式化和质量检查

项目集成了 Spotless 和 Checkstyle 插件:

#### Spotless (代码格式化)
- 基于空格的缩进 (4个空格)
- 自动清理尾随空白字符
- 文件末尾添加换行符
- 基本的导入组织

常用命令:
```bash
# 检查代码格式
mvn spotless:check

# 自动格式化代码
mvn spotless:apply
```

#### Checkstyle (代码规范检查)
- 使用 Sun Java 编码规范 (比 Google 规范更宽松)
- 在编译时自动检查代码规范
- 与 Java 11 和 Lombok 兼容

常用命令:
```bash
# 检查代码规范
mvn checkstyle:check

# 生成规范检查报告
mvn checkstyle:checkstyle
```

#### 集成命令
```bash
# 格式化代码并编译
mvn spotless:apply compile

# 完整检查 (格式化 + 规范检查 + 编译)
mvn spotless:apply checkstyle:check compile
```

## 模块介绍

### play-admin
主要的 Spring Boot 应用，包含:
- REST API 接口
- 安全配置
- 数据库集成
- 微信集成

### play-common
共享工具库，包含:
- 公共域对象
- 工具类
- Redis 配置
- 安全工具

### play-generator
代码生成工具，包含:
- MyBatis Plus 代码生成
- 基于模板的代码生成

## 故障排除

### 常见问题

**编译失败: "cannot find symbol" 错误**
- 确保使用 Java 11: `java -version` 应显示 Java 11
- 设置正确的 JAVA_HOME: `export JAVA_HOME=$(/usr/libexec/java_home -v 11)`
- 清理并重新编译: `mvn clean compile`

**Maven 使用错误的 Java 版本**
- 检查 Maven 版本: `mvn -version`
- 创建 `~/.mavenrc` 文件设置 JAVA_HOME
- 或在命令前加环境变量: `JAVA_HOME=$(/usr/libexec/java_home -v 11) mvn clean compile`

**VS Code Java 支持问题**
- 确保安装了 Extension Pack for Java
- 检查 `.vscode/settings.json` 中的 Java 配置
- 重新加载窗口: Cmd+Shift+P → "Developer: Reload Window"

**Spotless 格式化问题**
- 修复格式问题: `mvn spotless:apply`
- 跳过格式检查: `mvn compile -Dspotless.check.skip=true`

### 验证配置
```bash
# 验证 Java 版本
java -version

# 验证 Maven Java 版本  
mvn -version

# 验证编译
mvn clean compile

# 验证完整构建
mvn clean install
```

## 构建状态

✅ 所有模块使用 Java 11 编译成功  
✅ Lombok 注解自动处理  
✅ 模块间配置一致  
✅ Spotless 代码格式化已配置  
✅ Checkstyle 代码规范检查已配置
✅ VS Code Java 配置已设置

--- File: deploy.sh ---
#!/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"

--- File: docker/Dockerfile ---
FROM openjdk:11-jre-slim

# 设置工作目录
WORKDIR /app

# 只复制应用程序JAR包，不复制lib目录
COPY ./*.jar app.jar

# 设置环境变量
ENV APP_NAME=app.jar

# 暴露应用端口
EXPOSE 8080

# 设置启动命令
CMD ["sh", "-c", "java -Dloader.path=./lib/ -Xms2g -Xmx2g -jar $APP_NAME --spring.profiles.active=test"]


--- File: docker/docker-compose.yml ---
version: '3'

services:
  spring-boot-app:
    build: .
    container_name: spring-boot-app
    ports:
      - "7003:7002"
    volumes:
      - ./lib:/app/lib  # 挂载主机的lib目录到容器内的lib目录
      - ./log:/app/log  # 挂载日志目录到主机
    restart: always


--- File: fetch-log.sh ---
#!/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"

--- File: play-admin/pom.xml ---
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xmlns="http://maven.apache.org/POM/4.0.0"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>com.starry</groupId>
        <artifactId>play-with</artifactId>
        <version>1.0</version>
    </parent>

    <artifactId>play-admin</artifactId>

    <properties>
        <java.version>11</java.version>
        <maven.compiler.source>11</maven.compiler.source>
        <maven.compiler.target>11</maven.compiler.target>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>

    <dependencies>
        <!--引入springboot-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <!-- Flyway -->
        <dependency>
            <groupId>org.flywaydb</groupId>
            <artifactId>flyway-core</artifactId>
        </dependency>
        <!--通用模块-->
        <dependency>
            <groupId>com.starry</groupId>
            <artifactId>play-common</artifactId>
            <version>1.0</version>
        </dependency>
        <!--代码生成模块-->
        <dependency>
            <groupId>com.starry</groupId>
            <artifactId>play-generator</artifactId>
        </dependency>


        <!--JWT(Json Web Token)登录支持-->
        <dependency>
            <groupId>io.jsonwebtoken</groupId>
            <artifactId>jjwt</artifactId>
        </dependency>
        <!--mysql-->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.26</version>
        </dependency>
        <dependency>
            <groupId>com.github.yulichang</groupId>
            <artifactId>mybatis-plus-join-boot-starter</artifactId>
        </dependency>
        <!-- easyexcel -->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>easyexcel</artifactId>
        </dependency>
        <!-- SpringBoot AOP 拦截器 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-aop</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>

        <dependency>
            <groupId>com.aliyun.oss</groupId>
            <artifactId>aliyun-sdk-oss</artifactId>
        </dependency>

        <dependency>
            <groupId>com.github.binarywang</groupId>
            <artifactId>wx-java-mp-spring-boot-starter</artifactId>
        </dependency>
        <dependency>
            <groupId>com.github.wxpay</groupId>
            <artifactId>wxpay-sdk</artifactId>
        </dependency>


        <dependency>
            <groupId>ws.schild</groupId>
            <artifactId>jave-core</artifactId>
        </dependency>

        <dependency>
            <groupId>ws.schild</groupId>
            <artifactId>jave-nativebin-linux64</artifactId>
        </dependency>

        <dependency>
            <groupId>com.github.binarywang</groupId>
            <artifactId>weixin-java-pay</artifactId>
            <version>4.5.0</version>
        </dependency>

        <dependency>
            <groupId>com.squareup.okio</groupId>
            <artifactId>okio</artifactId>
        </dependency>

        <dependency>
            <groupId>com.tencentcloudapi</groupId>
            <artifactId>tencentcloud-sdk-java-dnspod</artifactId>
            <version>3.1.322</version>
            <exclusions>
                <exclusion>
                    <artifactId>okio</artifactId>
                    <groupId>com.squareup.okio</groupId>
                </exclusion>
            </exclusions>
        </dependency>

        <!--Lombok-->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <scope>provided</scope>
        </dependency>
        <!-- Add lombok annotation processor -->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok-mapstruct-binding</artifactId>
            <version>0.2.0</version>
        </dependency>

    </dependencies>

    <build>
        <defaultGoal>ruoyi-admin-mrwho</defaultGoal>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <configuration>
                    <annotationProcessorPaths>
                        <path>
                            <groupId>org.projectlombok</groupId>
                            <artifactId>lombok</artifactId>
                            <version>1.18.30</version>
                        </path>
                    </annotationProcessorPaths>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.flywaydb</groupId>
                <artifactId>flyway-maven-plugin</artifactId>
                <version>7.15.0</version> 
            </plugin>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <version>2.7.9</version>
                <configuration>
                    <layout>ZIP</layout>
                    <includes>
                        <include>
                            <groupId>nothing</groupId>
                            <artifactId>nothing</artifactId>
                        </include>
                    </includes>
                </configuration>
                <executions>
                    <execution>
                        <goals>
                            <goal>repackage</goal> <!-- 将引入的 jar 打入其中 -->
                        </goals>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>
</project>

--- File: play-admin/src/main/java/com/starry/admin/Application.java ---
package com.starry.admin;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.transaction.annotation.EnableTransactionManagement;

/**
 * @author admin
 */
@EnableTransactionManagement
@SpringBootApplication
@EnableScheduling
@ComponentScan("com.starry")
public class Application {

    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }

}


--- File: play-admin/src/main/java/com/starry/admin/common/aspect/ClerkUserLogin.java ---
package com.starry.admin.common.aspect;

import java.lang.annotation.*;

/**
 * 陪聊登录注解
 *
 * @author ruoyi
 */
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface ClerkUserLogin {

    boolean manage() default false;
}


--- File: play-admin/src/main/java/com/starry/admin/common/aspect/ClerkUserLoginAspect.java ---
package com.starry.admin.common.aspect;

import com.starry.admin.common.conf.ThreadLocalRequestDetail;
import com.starry.admin.common.exception.ServiceException;
import com.starry.admin.modules.clerk.module.entity.PlayClerkUserInfoEntity;
import com.starry.admin.modules.clerk.service.impl.PlayClerkUserInfoServiceImpl;
import com.starry.admin.modules.weichat.service.WxTokenService;
import com.starry.common.constant.Constants;
import com.starry.common.constant.HttpStatus;
import com.starry.common.utils.StringUtils;
import java.util.Objects;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;

/**
 * 限流处理
 *
 * @author ruoyi
 */
@Slf4j
@Aspect
@Component
public class ClerkUserLoginAspect {

    @Resource
    private PlayClerkUserInfoServiceImpl clerkUserInfoService;

    @Resource
    private WxTokenService tokenService;
    @Resource
    private HttpServletRequest request;

    @Before("@annotation(clerkUserLogin)")
    public void doBefore(JoinPoint point, ClerkUserLogin clerkUserLogin) {
        String userToken = request.getHeader(Constants.CLERK_USER_LOGIN_TOKEN);
        if (StringUtils.isEmpty(userToken)) {
            throw new ServiceException("token为空", HttpStatus.UNAUTHORIZED);
        }
        if (userToken.startsWith(Constants.TOKEN_PREFIX)) {
            userToken = userToken.replace(Constants.TOKEN_PREFIX, "");
        }
        // 解析token
        String userId;
        try {
            userId = tokenService.getWxUserIdByToken(userToken);
        } catch (Exception e) {
            log.error(e.getMessage(), e);
            throw new ServiceException("获取用户信息异常", HttpStatus.UNAUTHORIZED);
        }
        PlayClerkUserInfoEntity entity = clerkUserInfoService.selectById(userId);
        if (Objects.isNull(entity)) {
            throw new ServiceException("未查询到有效用户", HttpStatus.UNAUTHORIZED);
        }
        if (!userToken.equals(entity.getToken())) {
            throw new ServiceException("token异常", HttpStatus.UNAUTHORIZED);
        }
        ThreadLocalRequestDetail.setRequestDetail(entity);
    }
}


--- File: play-admin/src/main/java/com/starry/admin/common/aspect/CustomUserLogin.java ---
package com.starry.admin.common.aspect;

import java.lang.annotation.*;

/**
 * 客户登录注解
 *
 * @author ruoyi
 */
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface CustomUserLogin {

    boolean manage() default false;
}


--- File: play-admin/src/main/java/com/starry/admin/common/aspect/CustomUserLoginAspect.java ---
package com.starry.admin.common.aspect;

import com.starry.admin.common.conf.ThreadLocalRequestDetail;
import com.starry.admin.common.exception.ServiceException;
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.constant.Constants;
import com.starry.common.constant.HttpStatus;
import com.starry.common.utils.StringUtils;
import java.util.Objects;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;

/**
 * 限流处理
 *
 * @author ruoyi
 */
@Slf4j
@Aspect
@Component
public class CustomUserLoginAspect {

    @Resource
    private PlayCustomUserInfoServiceImpl customUserInfoService;

    @Resource
    private WxTokenService tokenService;
    @Resource
    private HttpServletRequest request;

    @Before("@annotation(customUserLogin)")
    public void doBefore(JoinPoint point, CustomUserLogin customUserLogin) {
        String userToken = request.getHeader(Constants.CUSTOM_USER_LOGIN_TOKEN);
        if (StringUtils.isEmpty(userToken)) {
            throw new ServiceException("token为空", HttpStatus.UNAUTHORIZED);
        }
        if (userToken.startsWith(Constants.TOKEN_PREFIX)) {
            userToken = userToken.replace(Constants.TOKEN_PREFIX, "");
        }
        // 解析token
        String userId;
        try {
            userId = tokenService.getWxUserIdByToken(userToken);
        } catch (Exception e) {
            log.error(e.getMessage(), e);
            throw new ServiceException("获取用户信息异常", HttpStatus.UNAUTHORIZED);
        }
        PlayCustomUserInfoEntity entity = customUserInfoService.selectById(userId);
        if (Objects.isNull(entity)) {
            throw new ServiceException("未查询到有效用户", HttpStatus.UNAUTHORIZED);
        }
        if (!userToken.equals(entity.getToken())) {
            throw new ServiceException("token异常", HttpStatus.UNAUTHORIZED);
        }
        ThreadLocalRequestDetail.setRequestDetail(entity);
    }
}


--- File: play-admin/src/main/java/com/starry/admin/common/aspect/DataScopeAspect.java ---
package com.starry.admin.common.aspect;

import cn.hutool.core.convert.Convert;
import cn.hutool.core.util.StrUtil;
import com.starry.admin.common.domain.LoginUser;
import com.starry.admin.modules.system.module.entity.SysRoleEntity;
import com.starry.admin.modules.system.module.entity.SysUserEntity;
import com.starry.admin.utils.SecurityUtils;
import com.starry.common.annotation.DataScope;
import com.starry.common.context.CustomSecurityContextHolder;
import com.starry.common.domain.BaseEntity;
import com.starry.common.utils.StringUtils;
import java.util.ArrayList;
import java.util.List;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;

/**
 * 数据过滤处理
 *
 * @author vctgo
 */
@Aspect
@Component
public class DataScopeAspect {
    /**
     * 全部数据权限
     */
    public static final String DATA_SCOPE_ALL = "1";

    /**
     * 自定数据权限
     */
    public static final String DATA_SCOPE_CUSTOM = "2";

    /**
     * 部门数据权限
     */
    public static final String DATA_SCOPE_DEPT = "3";

    /**
     * 部门及以下数据权限
     */
    public static final String DATA_SCOPE_DEPT_AND_CHILD = "4";

    /**
     * 仅本人数据权限
     */
    public static final String DATA_SCOPE_SELF = "5";

    /**
     * 数据权限过滤关键字
     */
    public static final String DATA_SCOPE = "dataScope";

    /**
     * 数据范围过滤
     *
     * @param joinPoint
     *            切点
     * @param user
     *            用户
     * @param deptAlias
     *            部门别名
     * @param userAlias
     *            用户别名
     * @param permission
     *            权限字符
     */
    public static void dataScopeFilter(JoinPoint joinPoint, SysUserEntity user, String deptAlias, String userAlias,
            String permission) {
        StringBuilder sqlString = new StringBuilder();
        List<String> conditions = new ArrayList<>();
        for (SysRoleEntity role : user.getRoles()) {
            String dataScope = role.getDataScope();
            if (!DATA_SCOPE_CUSTOM.equals(dataScope) && conditions.contains(dataScope)) {
                continue;
            }
            if (StrUtil.isNotBlank(permission) && StringUtils.isNotEmpty(role.getPermissions())
                    && !StringUtils.containsAny(role.getPermissions(), Convert.toStrArray(permission))) {
                continue;
            }
            if (DATA_SCOPE_ALL.equals(dataScope)) {
                sqlString = new StringBuilder();
                break;
            } else if (DATA_SCOPE_CUSTOM.equals(dataScope)) {
                sqlString.append(StringUtils.format(
                        " OR {}.dept_id IN ( SELECT dept_id FROM sys_role_dept WHERE role_id = {} ) ", deptAlias,
                        role.getRoleId()));
            } else if (DATA_SCOPE_DEPT.equals(dataScope)) {
                sqlString.append(StringUtils.format(" OR {}.dept_id = {} ", deptAlias, user.getDeptId()));
            } else if (DATA_SCOPE_DEPT_AND_CHILD.equals(dataScope)) {
                sqlString.append(StringUtils.format(
                        " OR {}.dept_id IN ( SELECT dept_id FROM sys_dept WHERE dept_id = {} or find_in_set( {} , ancestors ) )",
                        deptAlias, user.getDeptId(), user.getDeptId()));
            } else if (DATA_SCOPE_SELF.equals(dataScope)) {
                if (StringUtils.isNotBlank(userAlias)) {
                    sqlString.append(StringUtils.format(" OR {}.user_id = {} ", userAlias, user.getUserId()));
                } else {
                    // 数据权限为仅本人且没有userAlias别名不查询任何数据
                    sqlString.append(StringUtils.format(" OR {}.dept_id = 0 ", deptAlias));
                }
            }
            conditions.add(dataScope);
        }

        if (StringUtils.isNotBlank(sqlString.toString())) {
            Object params = joinPoint.getArgs()[0];
            if (StringUtils.isNotNull(params) && params instanceof BaseEntity) {
                BaseEntity baseEntity = (BaseEntity) params;
                baseEntity.getParams().put(DATA_SCOPE, " AND (" + sqlString.substring(4) + ")");
            }
        }
    }

    @Before("@annotation(controllerDataScope)")
    public void doBefore(JoinPoint point, DataScope controllerDataScope) {
        clearDataScope(point);
        handleDataScope(point, controllerDataScope);
    }

    protected void handleDataScope(final JoinPoint joinPoint, DataScope controllerDataScope) {
        if (controllerDataScope == null) {
            return;
        }
        // 获取当前的用户
        LoginUser loginUser = SecurityUtils.getLoginUser();
        if (StringUtils.isNotNull(loginUser)) {
            SysUserEntity currentUser = loginUser.getUser();
            // 如果是超级管理员，则不过滤数据
            if (StringUtils.isNotNull(currentUser) && SysUserEntity.isAdmin(currentUser)) {
                String permission = StringUtils.defaultIfEmpty(controllerDataScope.permission(),
                        CustomSecurityContextHolder.getPermission());
                dataScopeFilter(joinPoint, currentUser, controllerDataScope.deptAlias(),
                        controllerDataScope.userAlias(), permission);
            }
        }
    }

    /**
     * 拼接权限sql前先清空params.dataScope参数防止注入
     */
    private void clearDataScope(final JoinPoint joinPoint) {
        Object params = joinPoint.getArgs()[0];
        if (StringUtils.isNotNull(params) && params instanceof BaseEntity) {
            BaseEntity baseEntity = (BaseEntity) params;
            baseEntity.getParams().put(DATA_SCOPE, "");
        }
    }

}


--- File: play-admin/src/main/java/com/starry/admin/common/aspect/LogAspect.java ---
package com.starry.admin.common.aspect;

import cn.hutool.extra.servlet.ServletUtil;
import com.alibaba.fastjson2.JSON;
import com.starry.admin.common.domain.LoginUser;
import com.starry.admin.modules.system.module.entity.SysOperationLogEntity;
import com.starry.admin.modules.system.service.ISysOperationLogService;
import com.starry.admin.utils.SecurityUtils;
import com.starry.common.annotation.Log;
import com.starry.common.utils.ServletUtils;
import com.starry.common.utils.StringUtils;
import java.util.Collection;
import java.util.Date;
import java.util.Map;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.http.HttpMethod;
import org.springframework.stereotype.Component;
import org.springframework.validation.BindingResult;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.servlet.HandlerMapping;

/**
 * @author admin
 */
@Aspect
@Component
@Slf4j
public class LogAspect {

    @Resource
    private ISysOperationLogService operLogService;

    /**
     * 处理完请求后执行
     *
     * @param joinPoint
     *            切点
     */
    @AfterReturning(pointcut = "@annotation(controllerLog)", returning = "jsonResult")
    public void doAfterReturn(JoinPoint joinPoint, Log controllerLog, Object jsonResult) {
        handleLog(joinPoint, controllerLog, jsonResult);
    }

    protected void handleLog(final JoinPoint joinPoint, Log controllerLog, Object jsonResult) {
        try {
            // 获取当前的用户
            LoginUser loginUser = SecurityUtils.getLoginUser();

            // 日志记录
            SysOperationLogEntity operLog = new SysOperationLogEntity();
            operLog.setStatus(0);
            // 请求的IP地址
            String iP = ServletUtil.getClientIP(ServletUtils.getRequest());
            if ("0:0:0:0:0:0:0:1".equals(iP)) {
                iP = "127.0.0.1";
            }
            operLog.setOperIp(iP);
            operLog.setOperUrl(ServletUtils.getRequest().getRequestURI());
            if (loginUser != null) {
                operLog.setOperName(loginUser.getUsername());
            }
            if (null != null) {
                operLog.setStatus(1);
                operLog.setErrorMsg(StringUtils.substring(((Exception) null).getMessage(), 0, 2000));
            }
            // 设置方法名称
            String className = joinPoint.getTarget().getClass().getName();
            String methodName = joinPoint.getSignature().getName();
            operLog.setMethod(className + "." + methodName + "()");
            operLog.setRequestMethod(ServletUtils.getRequest().getMethod());
            operLog.setOperTime(new Date());
            // 处理设置注解上的参数
            getControllerMethodDescription(joinPoint, controllerLog, operLog, jsonResult);
            // 保存数据库
            operLogService.save(operLog);

        } catch (Exception exp) {
            log.error("异常信息:{}", exp.getMessage());
        }
    }

    /**
     * 获取注解中对方法的描述信息 用于Controller层注解
     *
     * @param log
     *            日志
     * @param operLog
     *            操作日志
     */
    public void getControllerMethodDescription(JoinPoint joinPoint, Log log, SysOperationLogEntity operLog,
            Object jsonResult) {
        // 设置操作业务类型
        operLog.setBusinessType(log.businessType().ordinal());
        // 设置标题
        operLog.setTitle(log.title());
        // 是否需要保存request，参数和值
        if (log.isSaveRequestData()) {
            // 设置参数的信息
            setRequestValue(joinPoint, operLog);
        }
        // 是否需要保存response，参数和值
        if (log.isSaveResponseData() && StringUtils.isNotNull(jsonResult)) {
            operLog.setJsonResult(StringUtils.substring(JSON.toJSONString(jsonResult), 0, 2000));
        }
    }

    /**
     * 获取请求的参数，放到log中
     *
     * @param operLog
     *            操作日志
     */
    private void setRequestValue(JoinPoint joinPoint, SysOperationLogEntity operLog) {
        String requsetMethod = operLog.getRequestMethod();
        if (HttpMethod.PUT.name().equals(requsetMethod) || HttpMethod.POST.name().equals(requsetMethod)) {
            String params = argsArrayToString(joinPoint.getArgs());
            operLog.setOperParam(StringUtils.substring(params, 0, 2000));
        } else {
            Map<?, ?> paramsMap = (Map<?, ?>) ServletUtils.getRequest()
                    .getAttribute(HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE);
            operLog.setOperParam(StringUtils.substring(paramsMap.toString(), 0, 2000));
        }
    }

    /**
     * 参数拼装
     */
    private String argsArrayToString(Object[] paramsArray) {
        StringBuilder params = new StringBuilder();
        if (paramsArray != null) {
            for (Object object : paramsArray) {
                // 不为空 并且是不需要过滤的 对象
                if (StringUtils.isNotNull(object) && !isFilterObject(object)) {
                    Object jsonObj = JSON.toJSON(object);
                    params.append(jsonObj.toString()).append(" ");
                }
            }
        }
        return params.toString().trim();
    }

    /**
     * 判断是否需要过滤的对象。
     *
     * @param object
     *            对象信息。
     * @return 如果是需要过滤的对象，则返回true；否则返回false。
     */
    @SuppressWarnings("rawtypes")
    public boolean isFilterObject(final Object object) {
        Class<?> clazz = object.getClass();
        if (clazz.isArray()) {
            return clazz.getComponentType().isAssignableFrom(MultipartFile.class);
        } else if (Collection.class.isAssignableFrom(clazz)) {
            Collection collection = (Collection) object;
            for (Object value : collection) {
                return value instanceof MultipartFile;
            }
        } else if (Map.class.isAssignableFrom(clazz)) {
            Map map = (Map) object;
            for (Object value : map.entrySet()) {
                Map.Entry entry = (Map.Entry) value;
                return entry.getValue() instanceof MultipartFile;
            }
        }
        return object instanceof MultipartFile || object instanceof HttpServletRequest
                || object instanceof HttpServletResponse || object instanceof BindingResult;
    }
}


--- File: play-admin/src/main/java/com/starry/admin/common/aspect/MybatisAspectj.java ---
package com.starry.admin.common.aspect;

import com.baomidou.mybatisplus.core.conditions.AbstractWrapper;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;

/**
 * @Author: huchuansai
 * @Date: 2023/8/2 4:38 PM
 * @Description:
 */
@Aspect
@Component
public class MybatisAspectj {

    // 配置织入点
    @Pointcut("execution(public * com.baomidou.mybatisplus.core.mapper.BaseMapper.selectOne(..))")
    public void selectOneAspect() {
    }

    @Before("selectOneAspect()")
    public void beforeSelect(JoinPoint point) {
        Object arg = point.getArgs()[0];
        if (arg instanceof AbstractWrapper) {
            arg = (AbstractWrapper) arg;
            ((AbstractWrapper) arg).last("limit 1");
        }
    }
}


--- File: play-admin/src/main/java/com/starry/admin/common/component/JwtToken.java ---
package com.starry.admin.common.component;

import cn.hutool.core.util.StrUtil;
import cn.hutool.extra.servlet.ServletUtil;
import cn.hutool.http.useragent.UserAgent;
import cn.hutool.http.useragent.UserAgentUtil;
import com.starry.admin.common.domain.LoginUser;
import com.starry.admin.common.security.entity.JwtUser;
import com.starry.common.constant.CacheConstants;
import com.starry.common.constant.Constants;
import com.starry.common.constant.SecurityConstants;
import com.starry.common.context.CustomSecurityContextHolder;
import com.starry.common.redis.RedisCache;
import com.starry.common.utils.IdUtils;
import com.starry.common.utils.ServletUtils;
import com.starry.common.utils.ip.AddressUtils;
import com.starry.common.utils.ip.IpUtils;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.stereotype.Component;

/**
 * @author admin token 组件
 * @since 2021/9/6
 */
@Slf4j
@Component
public class JwtToken {

    private static final String CLAIM_KEY_USERNAME = "sub";
    private static final String CLAIM_KEY_CREATED = "created";
    private static final Long MILLIS_MINUTE_TEN = 20 * 60 * 1000L;

    @Value("${jwt.secret}")
    private String secret;
    @Value("${jwt.expiration}")
    private Long expire;
    @Value("${jwt.tokenHeader}")
    private String tokenHeader;
    @Value("${jwt.tokenHead}")
    private String tokenHead;

    @Resource
    private RedisCache redisCache;

    /**
     * 从token中获取登录用户名
     */
    public String getUserNameFromToken(String token) {
        String username;
        try {
            Claims claims = getClaimsFromToken(token);
            username = claims.getSubject();
        } catch (Exception e) {
            username = null;
        }
        return username;
    }

    /**
     * 校验token
     */
    public boolean validateToken(String token, UserDetails userDetails) {
        String username = getUserNameFromToken(token);
        return username.equals(userDetails.getUsername()) && !isTokenExpired(token);
    }

    /**
     * 根据用户信息生成token
     */
    public String generateToken(UserDetails userDetails) {
        Map<String, Object> claims = new HashMap<>();
        claims.put(CLAIM_KEY_USERNAME, userDetails.getUsername());
        claims.put(CLAIM_KEY_CREATED, new Date());
        return generateToken(claims);
    }

    /**
     * 创建令牌
     *
     * @param jwtUser
     *            用户信息
     * @return 令牌
     */
    public String createToken(JwtUser jwtUser) {
        String token = IdUtils.getUuid();
        jwtUser.setToken(token);
        setUserAgent(jwtUser);
        refersToken(jwtUser);

        Map<String, Object> claims = new HashMap<>();
        claims.put(Constants.LOGIN_USER_KEY, token);
        return createToken(claims);
    }

    /**
     * 从数据声明生成令牌
     *
     * @param claims
     *            数据声明
     * @return 令牌
     */
    private String createToken(Map<String, Object> claims) {
        String token = Jwts.builder().setClaims(claims).signWith(SignatureAlgorithm.HS512, secret).compact();
        return token;
    }

    /**
     * 判断token是否已经失效
     */
    private boolean isTokenExpired(String token) {
        Date expiredDate = getClaimsFromToken(token).getExpiration();
        return expiredDate.before(new Date());
    }

    private String generateToken(Map<String, Object> claims) {
        return Jwts.builder().setClaims(claims).setExpiration(generateExpirationDate())
                // 签名算法
                .signWith(SignatureAlgorithm.HS512, secret).compact();
    }

    /**
     * 生成token的过期时间
     */
    private Date generateExpirationDate() {
        return new Date(System.currentTimeMillis() + expire * 1000);
    }

    /**
     * 从令牌中获取数据声明
     *
     * @param token
     *            令牌
     * @return 数据声明
     */
    private Claims getClaimsFromToken(String token) {
        Claims claims = null;
        try {
            claims = Jwts.parser().setSigningKey(secret).parseClaimsJws(token).getBody();
        } catch (Exception e) {
            log.info("JWT格式验证失败:{}", token);
        }
        return claims;
    }

    /**
     * 设置用户代理信息
     *
     * @param jwtUser
     *            登录信息
     */
    public void setUserAgent(JwtUser jwtUser) {
        UserAgent userAgent = UserAgentUtil.parse(ServletUtils.getRequest().getHeader("User-Agent"));
        String ip = ServletUtil.getClientIP(ServletUtils.getRequest());
        jwtUser.setIpaddr(ip);
        jwtUser.setLoginLocation(AddressUtils.getRealAddressByIp(ip));
        jwtUser.setBrowser(userAgent.getBrowser().getName());
        jwtUser.setOs(userAgent.getOs().getName());
    }

    public void refersToken(JwtUser jwtUser) {
        jwtUser.setLoginTime(System.currentTimeMillis());
        jwtUser.setExpireTime(jwtUser.getLoginTime() + expire * 1000);
        String userKey = getTokenKey(jwtUser.getToken());
        redisCache.setCacheObject(userKey, jwtUser, expire, TimeUnit.SECONDS);
        String key = "login:resource:" + jwtUser.getUserId();
        redisCache.setCacheObject(key, userKey, expire, TimeUnit.SECONDS);
    }

    private String getTokenKey(String uuid) {
        return CacheConstants.LOGIN_TOKEN_KEY + uuid;
    }

    /**
     * 获取登录用户身份信息
     *
     * @return 用户信息
     */
    public JwtUser getLoginUser(HttpServletRequest request) {
        String token = getToken(request);
        if (StrUtil.isNotBlank(token)) {
            try {
                Claims claims = getClaimsFromToken(token);
                String uuid = (String) claims.get(Constants.LOGIN_USER_KEY);
                String userKey = getTokenKey(uuid);
                JwtUser user = redisCache.getCacheObject(userKey);
                return user;
            } catch (Exception e) {

            }
        }
        return null;
    }

    /**
     * 获取请求token
     *
     * @param request
     * @return token
     */
    private String getToken(HttpServletRequest request) {
        // 获取请求头
        String token = request.getHeader(tokenHeader);
        if (StrUtil.isNotBlank(token) && token.startsWith(tokenHead)) {
            token = token.replace(tokenHead, "");
        }
        return token;
    }

    /**
     * 验证令牌有效期，相差不足20分钟，自动刷新缓存
     *
     * @param jwtUser
     * @return 令牌
     */

    /**
     * 删除用户身份信息
     */
    public void removeJwtUser(String token) {
        if (StrUtil.isNotBlank(token)) {
            String userKey = getTokenKey(token);
            redisCache.deleteObject(userKey);
        }
    }

    /**
     * 创建令牌
     */
    public Map<String, Object> createToken(LoginUser loginUser) {
        String token = IdUtils.getUuid();
        String userId = loginUser.getUser().getUserId();
        String userName = loginUser.getUser().getUserCode();
        String tenantId = loginUser.getUser().getTenantId();
        Long deptId = loginUser.getUser().getDeptId();
        loginUser.setToken(token);
        loginUser.setUserId(userId);
        loginUser.setUserName(userName);
        loginUser.setIpaddr(IpUtils.getIpAddr(ServletUtils.getRequest()));
        // 添加地址信息
        setUserAgent(loginUser);
        String userRedisKey = refreshToken(loginUser);

        String key = "login:resource:" + loginUser.getUserId();
        redisCache.setCacheObject(key, userRedisKey, expire, TimeUnit.SECONDS);

        // Jwt存储信息
        Map<String, Object> claimsMap = new HashMap<>(8);
        claimsMap.put(SecurityConstants.USER_KEY, token);
        claimsMap.put(SecurityConstants.DETAILS_USER_ID, userId);
        claimsMap.put(SecurityConstants.DETAILS_USERNAME, userName);
        // 租户id
        claimsMap.put(SecurityConstants.DETAILS_TENANT_ID, tenantId);
        // 部门id
        claimsMap.put(SecurityConstants.DETAILS_DEPT_ID, deptId);
        // 接口返回信息
        Map<String, Object> rspMap = new HashMap<>();
        rspMap.put("token", this.createToken(claimsMap));
        rspMap.put("expires_in", expire);
        rspMap.put("tenant_id", tenantId);
        rspMap.put("tokenHead", tokenHead);
        return rspMap;
    }

    /**
     * 设置用户代理信息
     *
     * @param loginUser
     *            登录信息
     */
    public void setUserAgent(LoginUser loginUser) {
        UserAgent userAgent = UserAgentUtil.parse(ServletUtils.getRequest().getHeader("User-Agent"));
        String ip = ServletUtil.getClientIP(ServletUtils.getRequest());
        loginUser.setIpaddr(ip);
        loginUser.setLoginLocation(AddressUtils.getRealAddressByIp(ip));
        loginUser.setBrowser(userAgent.getBrowser().getName());
        loginUser.setOs(userAgent.getOs().getName());
    }

    /**
     * 刷新令牌有效期
     *
     * @param loginUser
     *            登录信息
     * @return
     */
    public String refreshToken(LoginUser loginUser) {
        loginUser.setLoginTime(System.currentTimeMillis());
        loginUser.setExpireTime(loginUser.getLoginTime() + expire * 1000);
        // 根据uuid将loginUser缓存
        String userKey = getTokenKey(loginUser.getToken());
        redisCache.setCacheObject(userKey, loginUser, expire, TimeUnit.MINUTES);
        return userKey;
    }

    /**
     * 获取登录用户身份信息
     *
     * @return 用户信息
     */
    public LoginUser getNewLoginUser(HttpServletRequest request) {
        String token = getToken(request);
        if (StrUtil.isNotBlank(token)) {
            try {
                Claims claims = getClaimsFromToken(token);
                String uuid = (String) claims.get(SecurityConstants.USER_KEY);
                String userKey = getTokenKey(uuid);
                LoginUser loginUser = redisCache.getCacheObject(userKey);
                CustomSecurityContextHolder.set(SecurityConstants.DETAILS_TENANT_ID, loginUser.getUser().getTenantId());
                CustomSecurityContextHolder.set(SecurityConstants.LOGIN_USER, loginUser);
                return loginUser;
            } catch (Exception e) {
                log.error("getNewLoginUser error", e);
            }
        }
        return null;
    }

    /**
     * 验证令牌有效期，相差不足20分钟，自动刷新缓存
     *
     * @param loginUser
     * @return 令牌
     */
    public void verifyToken(LoginUser loginUser) {
        long expireTime = loginUser.getExpireTime();
        long currentTime = System.currentTimeMillis();
        if (expireTime - currentTime <= MILLIS_MINUTE_TEN) {
            refreshToken(loginUser);
        }
    }
}


--- File: play-admin/src/main/java/com/starry/admin/common/component/PermissionService.java ---
package com.starry.admin.common.component;

import com.starry.admin.common.domain.LoginUser;
import com.starry.admin.modules.system.module.entity.SysRoleEntity;
import com.starry.admin.utils.SecurityUtils;
import java.util.Set;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;

/**
 * 自定义权限实现，ss取自SpringSecurity首字母
 *
 * @author admin
 */
@Service("customSs")
public class PermissionService {
    /**
     * 所有权限标识
     */
    private static final String ALL_PERMISSION = "*:*:*";

    /**
     * 管理员角色权限标识
     */
    private static final String SUPER_ADMIN = "admin";

    private static final String ROLE_DELIMITER = ",";

    private static final String PERMISSION_DELIMETER = ",";

    /**
     * 验证用户是否具备某权限
     *
     * @param permission
     *            权限字符串
     * @return 用户是否具备某权限
     */
    public boolean hasPermission(String permission) {
        if (StringUtils.isEmpty(permission)) {
            return false;
        }
        LoginUser loginUser = SecurityUtils.getLoginUser();
        if (loginUser == null || CollectionUtils.isEmpty(loginUser.getPermissions())) {
            return false;
        }
        return hasPermissions(loginUser.getPermissions(), permission);
    }

    /**
     * 验证用户是否不具备某权限，与 hasPermission逻辑相反
     *
     * @param permission
     *            权限字符串
     * @return 用户是否不具备某权限
     */
    public boolean lacksPermission(String permission) {
        return hasPermission(permission) != true;
    }

    /**
     * 验证用户是否具有以下任意一个权限
     *
     * @param permissions
     *            以 PERMISSION_NAMES_DELIMETER 为分隔符的权限列表
     * @return 用户是否具有以下任意一个权限
     */
    public boolean hasAnyPermission(String permissions) {
        if (StringUtils.isEmpty(permissions)) {
            return false;
        }
        LoginUser loginUser = SecurityUtils.getLoginUser();
        if (loginUser == null || CollectionUtils.isEmpty(loginUser.getPermissions())) {
            return false;
        }
        Set<String> authorities = loginUser.getPermissions();
        for (String permission : permissions.split(PERMISSION_DELIMETER)) {
            if (permission != null && hasPermissions(authorities, permission)) {
                return true;
            }
        }
        return false;
    }

    /**
     * 判断用户是否拥有某个角色
     *
     * @param role
     *            角色字符串
     * @return 用户是否具备某角色
     */
    public boolean hasRole(String role) {
        if (StringUtils.isEmpty(role)) {
            return false;
        }
        LoginUser loginUser = SecurityUtils.getLoginUser();
        if (loginUser == null || CollectionUtils.isEmpty(loginUser.getUser().getRoles())) {
            return false;
        }
        for (SysRoleEntity sysRoleEntity : loginUser.getUser().getRoles()) {
            String roleKey = sysRoleEntity.getRoleKey();
            if (SUPER_ADMIN.equals(roleKey) || roleKey.equals(StringUtils.trim(role))) {
                return true;
            }
        }
        return false;
    }

    /**
     * 验证用户是否不具备某角色，与 isRole逻辑相反。
     *
     * @param role
     *            角色名称
     * @return 用户是否不具备某角色
     */
    public boolean lacksRole(String role) {
        return !hasRole(role);
    }

    /**
     * 验证用户是否具有以下任意一个角色
     *
     * @param roles
     *            以 ROLE_NAMES_DELIMITER 为分隔符的角色列表
     * @return 用户是否具有以下任意一个角色
     */
    public boolean hasAnyRoles(String roles) {
        if (StringUtils.isEmpty(roles)) {
            return false;
        }
        LoginUser loginUser = SecurityUtils.getLoginUser();
        if (loginUser == null || CollectionUtils.isEmpty(loginUser.getUser().getRoles())) {
            return false;
        }
        for (String role : roles.split(ROLE_DELIMITER)) {
            if (hasRole(role)) {
                return true;
            }
        }
        return false;
    }

    /**
     * 判断是否包含权限
     *
     * @param permissions
     *            权限列表
     * @param permission
     *            权限字符串
     * @return 用户是否具备某权限
     */
    private boolean hasPermissions(Set<String> permissions, String permission) {
        return permissions.contains(ALL_PERMISSION) || permissions.contains(StringUtils.trim(permission));
    }
}


--- File: play-admin/src/main/java/com/starry/admin/common/conf/AbstractListTypeHandler.java ---
package com.starry.admin.common.conf;

import cn.hutool.core.util.StrUtil;
import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.TypeReference;
import java.sql.CallableStatement;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.type.BaseTypeHandler;
import org.apache.ibatis.type.JdbcType;
import org.apache.ibatis.type.MappedJdbcTypes;
import org.apache.ibatis.type.MappedTypes;

/**
 * 数据库数据-String 和 List 自动转换
 *
 * @author admin
 */
@Slf4j
@MappedJdbcTypes(JdbcType.VARCHAR)
@MappedTypes({List.class})
public abstract class AbstractListTypeHandler<T> extends BaseTypeHandler<List<T>> {
    @Override
    public void setNonNullParameter(PreparedStatement ps, int i, List<T> parameter, JdbcType jdbcType)
            throws SQLException {
        String content = StrUtil.isEmptyIfStr(parameter) ? null : JSON.toJSONString(parameter);
        ps.setString(i, content);
    }

    @Override
    public List<T> getNullableResult(ResultSet rs, String columnName) throws SQLException {
        return this.getListByJsonArrayString(rs.getString(columnName));
    }

    @Override
    public List<T> getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
        return this.getListByJsonArrayString(rs.getString(columnIndex));
    }

    @Override
    public List<T> getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
        return this.getListByJsonArrayString(cs.getString(columnIndex));
    }

    private List<T> getListByJsonArrayString(String content) {
        return StrUtil.isEmptyIfStr(content) ? new ArrayList<>() : JSON.parseObject(content, this.specificType());
    }

    /**
     * 具体类型，由子类提供
     *
     * @return 具体类型
     */
    protected abstract TypeReference<List<T>> specificType();
}


--- File: play-admin/src/main/java/com/starry/admin/common/conf/DataSourceConfig.java ---
package com.starry.admin.common.conf;

import com.alibaba.druid.spring.boot.autoconfigure.DruidDataSourceBuilder;
import javax.sql.DataSource;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;

@Configuration
public class DataSourceConfig {

    // For flyway only
    @Bean(name = "primaryDataSource")
    @Primary
    public DataSource dataSource() {
        return DruidDataSourceBuilder.create().build();
    }

}


--- File: play-admin/src/main/java/com/starry/admin/common/conf/StringTypeHandler.java ---
package com.starry.admin.common.conf;

import com.alibaba.fastjson2.TypeReference;
import java.util.List;

/**
 * @author admin
 */
public class StringTypeHandler extends AbstractListTypeHandler<String> {
    @Override
    protected TypeReference<List<String>> specificType() {
        return new TypeReference<List<String>>() {
        };
    }
}


--- File: play-admin/src/main/java/com/starry/admin/common/conf/ThreadLocalRequestDetail.java ---
package com.starry.admin.common.conf;

import com.alibaba.ttl.TransmittableThreadLocal;
import com.starry.admin.modules.clerk.module.entity.PlayClerkUserInfoEntity;
import com.starry.admin.modules.custom.module.entity.PlayCustomUserInfoEntity;

/**
 * @author : huchuansai
 * @since : 2024/4/2 12:10 AM
 */
public class ThreadLocalRequestDetail {
    private static final TransmittableThreadLocal<Object> THREAD_LOCAL = new TransmittableThreadLocal<>();

    /**
     * 设置请求信息到当前线程中
     */
    public static void setRequestDetail(Object data) {
        THREAD_LOCAL.set(data);
    }

    /**
     * 从当前线程中获取请求信息
     */
    public static Object getRequestDetail() {
        return THREAD_LOCAL.get();
    }

    public static PlayClerkUserInfoEntity getClerkUserInfo() {
        return (PlayClerkUserInfoEntity) THREAD_LOCAL.get();
    }

    public static PlayCustomUserInfoEntity getCustomUserInfo() {
        return (PlayCustomUserInfoEntity) THREAD_LOCAL.get();
    }

    /**
     * 销毁
     */
    public static void remove() {
        THREAD_LOCAL.remove();
    }

}


--- File: play-admin/src/main/java/com/starry/admin/common/domain/LoginUser.java ---
package com.starry.admin.common.domain;

import com.starry.admin.modules.system.module.entity.SysUserEntity;
import java.util.Collection;
import java.util.Date;
import java.util.Set;
import lombok.Data;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;

/**
 * @author admin
 */
@Data
public class LoginUser implements UserDetails {
    private static final long serialVersionUID = 1L;

    /**
     * 用户唯一标识
     */
    private String token;
    /**
     * 用户名id
     */
    private String userId;

    /**
     * 用户名
     */
    private String userName;

    /**
     * 登录时间
     */
    private Long loginTime;

    /**
     * 过期时间
     */
    private Long expireTime;

    /**
     * 登录IP地址
     */
    private String ipaddr;
    /**
     * 登录地点
     */
    private String loginLocation;

    /**
     * 浏览器类型
     */
    private String browser;

    /**
     * 操作系统
     */
    private String os;

    /**
     * 权限列表
     */
    private Set<String> permissions;

    /**
     * 角色列表
     */
    private Set<String> roles;

    /**
     * 用户信息
     */
    private SysUserEntity user;

    /**
     * 租户租赁截止日期--dhr
     */
    private Date tenantEndDate;

    /**
     * 租户状态
     */
    private Integer tenantStatus;

    // 当前角色
    private String currentRole;

    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        return null;
    }

    @Override
    public String getPassword() {
        return user.getPassWord();
    }

    @Override
    public String getUsername() {
        return user.getUserCode();
    }

    /**
     * 账户是否未过期
     **/
    @Override
    public boolean isAccountNonExpired() {
        return true;
    }

    /**
     * 账户是否未锁定
     **/
    @Override
    public boolean isAccountNonLocked() {
        return true;
    }

    /**
     * 密码是否未过期
     **/
    @Override
    public boolean isCredentialsNonExpired() {
        return true;
    }

    /**
     * 账户是否激活
     **/
    @Override
    public boolean isEnabled() {
        return user.getStatus() == 0;
    }
}


--- File: play-admin/src/main/java/com/starry/admin/common/domain/TreeSelect.java ---
package com.starry.admin.common.domain;

import com.fasterxml.jackson.annotation.JsonInclude;
import com.starry.admin.modules.system.module.entity.SysDeptEntity;
import com.starry.admin.modules.system.module.entity.SysMenuEntity;
import java.io.Serializable;
import java.util.List;
import java.util.stream.Collectors;
import lombok.Data;

/**
 * @author Treeselect树结构实体类
 * @since 2022/7/4
 */
@Data
public class TreeSelect implements Serializable {

    /**
     * 节点ID
     */
    private Long id;

    /**
     * 节点名称
     */
    private String label;

    /**
     * 子节点
     */
    @JsonInclude(JsonInclude.Include.NON_EMPTY)
    private List<TreeSelect> children;

    public TreeSelect(SysMenuEntity menu) {
        this.id = menu.getMenuId();
        this.label = menu.getMenuName();
        this.children = menu.getChildren().stream().map(TreeSelect::new).collect(Collectors.toList());
    }

    public TreeSelect(SysDeptEntity dept) {
        this.id = dept.getDeptId();
        this.label = dept.getDeptName();
        this.children = dept.getChildren().stream().map(TreeSelect::new).collect(Collectors.toList());
    }
}


--- File: play-admin/src/main/java/com/starry/admin/common/exception/CustomException.java ---
package com.starry.admin.common.exception;

/**
 * 自定义异常处理器 * * @author admin
 */
public class CustomException extends RuntimeException {
    public CustomException(String msg) {
        super(msg);
    }
}


--- File: play-admin/src/main/java/com/starry/admin/common/exception/ServiceException.java ---
package com.starry.admin.common.exception;

import lombok.Data;
import lombok.EqualsAndHashCode;

/**
 * @author 业务异常
 * @since 2023/3/9
 */
@EqualsAndHashCode(callSuper = true)
@Data
public class ServiceException extends RuntimeException {

    private static final long serialVersionUID = 1L;

    /**
     * 错误码
     */
    private Integer code;

    /**
     * 错误提示
     */
    private String message;

    /**
     * 错误明细，内部调试错误
     */
    private String detailMessage;

    /**
     * 空构造方法，避免反序列化问题
     */
    public ServiceException() {
    }

    public ServiceException(String message) {
        this.message = message;
    }

    public ServiceException(String message, Integer code) {
        this.message = message;
        this.code = code;
    }

    public ServiceException setDetailMessage(String detailMessage) {
        this.detailMessage = detailMessage;
        return this;
    }

    @Override
    public String getMessage() {
        return message;
    }

    public ServiceException setMessage(String message) {
        this.message = message;
        return this;
    }
}


--- File: play-admin/src/main/java/com/starry/admin/common/exception/handler/GlobalExceptionHandler.java ---
package com.starry.admin.common.exception.handler;

import com.fasterxml.jackson.databind.exc.MismatchedInputException;
import com.starry.admin.common.exception.CustomException;
import com.starry.admin.common.exception.ServiceException;
import com.starry.common.result.R;
import com.starry.common.utils.StringUtils;
import javax.servlet.http.HttpServletRequest;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.validation.BindingResult;
import org.springframework.validation.FieldError;
import org.springframework.web.HttpRequestMethodNotSupportedException;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.MissingServletRequestParameterException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;

/**
 * @author 全局异常处理
 * @since 2023/3/9
 */
@RestControllerAdvice
public class GlobalExceptionHandler {
    private static final Logger log = LoggerFactory.getLogger(GlobalExceptionHandler.class);

    /**
     * 业务异常
     */
    @ExceptionHandler(ServiceException.class)
    public R handleServiceException(ServiceException e, HttpServletRequest request) {
        if ("token异常".equals(e.getMessage()) || "token为空".equals(e.getMessage())) {
            log.error("用户token异常");
        } else {
            log.error(e.getMessage(), e);
        }
        Integer code = e.getCode();
        return StringUtils.isNotNull(code) ? R.error(code, e.getMessage()) : R.error(e.getMessage());
    }

    /**
     * 拦截未知的运行时异常
     */
    @ExceptionHandler(RuntimeException.class)
    public R handleRuntimeException(RuntimeException e, HttpServletRequest request) {
        String requestUrl = request.getRequestURI();
        log.error("请求地址'{}',发生未知异常.", requestUrl, e);
        return R.error(e.getMessage());
    }

    /**
     * 系统异常
     */
    @ExceptionHandler(Exception.class)
    public R handleException(Exception e, HttpServletRequest request) {
        String requestUrl = request.getRequestURI();
        log.error("请求地址'{}',发生系统异常.", requestUrl, e);
        return R.error("系统出现内部错误，请联系管理员");
    }

    /**
     * 请求方法异常
     */
    @ExceptionHandler(HttpRequestMethodNotSupportedException.class)
    public R httpRequestMethodNotSupportedException() {
        return R.error("请求方法异常");
    }

    /**
     * 请求参数异常
     *
     * @param e
     *            HttpRequestMethodNotSupportedException
     */
    @ExceptionHandler(MethodArgumentNotValidException.class)
    public R methodArgumentNotValidException(MethodArgumentNotValidException e) {
        BindingResult bindingResult = e.getBindingResult();
        StringBuilder errorMessageBuilder = new StringBuilder();
        for (FieldError error : bindingResult.getFieldErrors()) {
            errorMessageBuilder.append(error.getDefaultMessage());
        }
        return R.error("请求参数异常," + errorMessageBuilder);
    }

    @ExceptionHandler(MismatchedInputException.class)
    public R mismatchedInputException(MismatchedInputException e) {
        return R.error("请求参数格式异常");
    }

    @ExceptionHandler(HttpMessageNotReadableException.class)
    public R httpMessageNotReadableException(HttpMessageNotReadableException e) {
        return R.error("请求参数格式异常");
    }

    @ExceptionHandler(MissingServletRequestParameterException.class)
    public R missingServletRequestParameterException(MissingServletRequestParameterException e) {
        return R.error("请求参数格式异常");
    }

    /**
     * 自定义异常
     *
     * @param e
     *            CustomException
     */
    @ExceptionHandler(CustomException.class)
    public R customException(CustomException e) {
        if ("token异常".equals(e.getMessage()) || "token为空".equals(e.getMessage())) {
            log.error("用户token异常");
        } else {
            log.error(e.getMessage(), e);
        }

        return R.error(e.getMessage());
    }
}


--- File: play-admin/src/main/java/com/starry/admin/common/mybatis/config/MybatisPlusConfig.java ---
package com.starry.admin.common.mybatis.config;

import com.alibaba.druid.spring.boot.autoconfigure.DruidDataSourceBuilder;
import com.baomidou.mybatisplus.annotation.DbType;
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.BlockAttackInnerInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.OptimisticLockerInnerInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.TenantLineInnerInterceptor;
import com.starry.admin.common.mybatis.handler.MyTenantLineHandler;
import javax.sql.DataSource;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.annotation.EnableTransactionManagement;

/**
 * @author admin
 */
@Configuration
@MapperScan("com.starry.**.mapper")
@EnableTransactionManagement
public class MybatisPlusConfig {

    /**
     * druid注入
     *
     * @return dataSource
     */
    @Bean(name = "dataSource")
    @ConfigurationProperties(prefix = "spring.datasource.druid")
    public DataSource dataSource() {
        return DruidDataSourceBuilder.create().build();
    }

    /**
     * 配置事物管理器
     *
     * @return DataSourceTransactionManager
     */
    @Bean(name = "transactionManager")
    public DataSourceTransactionManager transactionManager() {
        return new DataSourceTransactionManager(dataSource());
    }

    @Bean
    public MybatisPlusInterceptor mybatisPlusInterceptor() {
        MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
        // 租户插件
        interceptor.addInnerInterceptor(new TenantLineInnerInterceptor(new MyTenantLineHandler()));
        // 分页插件
        interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
        // 乐观锁插件
        interceptor.addInnerInterceptor(optimisticLockerInnerInterceptor());
        // 阻断插件
        interceptor.addInnerInterceptor(blockAttackInnerInterceptor());
        return interceptor;
    }

    /**
     * 乐观锁
     */
    @Bean
    public OptimisticLockerInnerInterceptor optimisticLockerInnerInterceptor() {
        return new OptimisticLockerInnerInterceptor();
    }

    /**
     * 防止全表更新与删除
     */
    @Bean
    public BlockAttackInnerInterceptor blockAttackInnerInterceptor() {
        return new BlockAttackInnerInterceptor();
    }
}


--- File: play-admin/src/main/java/com/starry/admin/common/mybatis/handler/MyMetaObjectHandler.java ---
package com.starry.admin.common.mybatis.handler;

import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;
import com.starry.admin.modules.weichat.service.WxTokenService;
import com.starry.admin.utils.SecurityUtils;
import com.starry.common.constant.Constants;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.util.Date;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.reflection.MetaObject;
import org.springframework.stereotype.Component;

/**
 * @author admin 字段默认值处理类
 * @since 2021/9/1
 */
@Slf4j
@Component
public class MyMetaObjectHandler implements MetaObjectHandler {

    @Resource
    private HttpServletRequest request;

    @Resource
    private WxTokenService tokenService;

    @Override
    public void insertFill(MetaObject metaObject) {

        this.setFieldValByName("createdTime", new Date(), metaObject);
        this.setFieldValByName("deleted", false, metaObject);
        this.setFieldValByName("version", 1L, metaObject);
        Object createUser = this.getFieldValByName("createdBy", metaObject);
        if (createUser == null) {
            if (SecurityUtils.isLogin()) {
                this.setFieldValByName("createdBy", getOperatorId(), metaObject);
            }
        }
    }

    @Override
    public void updateFill(MetaObject metaObject) {
        this.setFieldValByName("updatedTime", getDate(), metaObject);
        Object createUser = this.getFieldValByName("updatedBy", metaObject);
        if (createUser == null) {
            this.setFieldValByName("createdBy", getOperatorId(), metaObject);
        }
    }

    public Date getDate() {

        LocalDateTime localDateTime = LocalDateTime.now();
        return Date.from(localDateTime.atZone(ZoneId.systemDefault()).toInstant());
        // return Date.from(localDateTime.toInstant(ZoneOffset.ofHours(8)));
    }

    public String getOperatorId() {
        try {
            if (request.getServletPath().startsWith("/wx/")) {
                String tenantKey = request.getHeader("tenantkey");
                if (StrUtil.isBlankIfStr(tenantKey)) {
                    String clerkToken = request.getHeader(Constants.CLERK_USER_LOGIN_TOKEN);
                    String customToken = request.getHeader(Constants.CUSTOM_USER_LOGIN_TOKEN);
                    if (clerkToken != null) {
                        return tokenService.getWxUserIdByToken(clerkToken);
                    }
                    if (customToken != null) {
                        return tokenService.getWxUserIdByToken(customToken);
                    }
                }
                return "";
            } else {
                if (SecurityUtils.isLogin()) {
                    return SecurityUtils.getUserId();
                }
            }
        } catch (Exception ignored) {
            if (SecurityUtils.isLogin()) {
                return SecurityUtils.getUserId();
            }
        }
        return "";
    }
}


--- File: play-admin/src/main/java/com/starry/admin/common/mybatis/handler/MyTenantLineHandler.java ---
package com.starry.admin.common.mybatis.handler;

import com.baomidou.mybatisplus.extension.plugins.handler.TenantLineHandler;
import com.starry.admin.utils.SecurityUtils;
import com.starry.common.utils.StringUtils;
import java.util.Arrays;
import lombok.extern.slf4j.Slf4j;
import net.sf.jsqlparser.expression.Expression;
import net.sf.jsqlparser.expression.StringValue;
import org.springframework.stereotype.Component;

/**
 * @author admin 多租户处理器
 * @since 2023/3/7
 */
@Slf4j
@Component
public class MyTenantLineHandler implements TenantLineHandler {

    /**
     * 排除过滤的表
     */
    private static final String[] TABLE_FILTER = {"sys_login_log", "sys_menu", "sys_tenant_package", "sys_tenant",
            "sys_dict", "sys_dict_data", "sys_administrative_area_dict_info"};

    /**
     * 排除过滤的表前缀
     */
    private static final String[] TABLE_PRE = {"qrtz", "gen"};

    @Override
    public Expression getTenantId() {
        // 取出当前请求的服务商ID，通过解析器注入到SQL中。
        String tenantId = SecurityUtils.getTenantId();
        // if (!StrUtil.isBlankIfStr(tenantId)) {
        // throw new CustomException("租户信息获取异常");
        // }
        return new StringValue(tenantId);
    }

    /**
     * 跳过不需要加多租户的表
     */
    @Override
    public boolean ignoreTable(String tableName) {
        String prefix = StringUtils.substringBefore(tableName, "_");
        return Arrays.asList(TABLE_FILTER).contains(tableName) || Arrays.asList(TABLE_PRE).contains(prefix);
    }
}


--- File: play-admin/src/main/java/com/starry/admin/common/oss/OssProperties.java ---
package com.starry.admin.common.oss;

import lombok.Data;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;

@Data
@Configuration
@ConfigurationProperties(prefix = "aliyun")
@PropertySource(value = {"classpath:oss.properties"})
public class OssProperties implements InitializingBean {

    public static String ENDPOINT = "";
    public static String KEY_ID = "";
    public static String KEY_SECRET = "";
    public static String BUCKET_NAME = "";
    public String endpoint = "oss-cn-hangzhou.aliyuncs.com";
    public String accessKeyId = "LTAI5t9ojt6uovFZokYZfJx5";
    public String accessKeySecret = "xfCyNM5BQlAkoaa5NbcpoeipmCKZgf";
    public String bucketName = "live-cloud-cvoon";

    @Override
    public void afterPropertiesSet() {
        ENDPOINT = getEndpoint();
        KEY_ID = getAccessKeyId();
        KEY_SECRET = getAccessKeySecret();
        BUCKET_NAME = getBucketName();
    }
}


--- File: play-admin/src/main/java/com/starry/admin/common/oss/controller/CosController.java ---
package com.starry.admin.common.oss.controller;

import com.starry.admin.common.oss.service.IOssFileService;
import com.starry.common.result.R;
import io.swagger.annotations.ApiOperation;
import javax.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;

/**
 * @author admin cos存储前端控制器
 * @since 2022/11/13 17:51
 */
@Slf4j
@RestController
@RequestMapping("/cos")
public class CosController {

    @Resource
    IOssFileService ossFileService;

    @ApiOperation(value = "照片上传")
    @PostMapping("/upload/image")
    public R uploadImage(MultipartFile file) throws Exception {
        if (!file.isEmpty()) {
            String house = ossFileService.upload(file.getInputStream(), "house", file.getOriginalFilename());
            return R.ok(house);
        }
        return R.error("上传照片异常，请联系管理员");
    }
}


--- File: play-admin/src/main/java/com/starry/admin/common/oss/service/IOssFileService.java ---
package com.starry.admin.common.oss.service;

import java.io.InputStream;

public interface IOssFileService {

    /**
     * 文件上传只阿里云OSS
     *
     * @param inputStream
     *            文件流
     * @param module
     *            文件保存模块地址
     * @param originalFilename
     *            原始文件名
     * @return String
     * @author admin
     * @since 2024/4/11 10:24
     **/
    String upload(InputStream inputStream, String module, String originalFilename);

    /**
     * 删除文件
     *
     * @param url
     *            文件地址
     */
    void remove(String url);

}


--- File: play-admin/src/main/java/com/starry/admin/common/oss/service/impl/OssFileServiceImpl.java ---
package com.starry.admin.common.oss.service.impl;

import cn.hutool.core.date.DateTime;
import cn.hutool.core.io.FileTypeUtil;
import com.aliyun.oss.ClientException;
import com.aliyun.oss.OSS;
import com.aliyun.oss.OSSClientBuilder;
import com.aliyun.oss.OSSException;
import com.aliyun.oss.model.CannedAccessControlList;
import com.aliyun.oss.model.PutObjectRequest;
import com.starry.admin.common.exception.CustomException;
import com.starry.admin.common.oss.OssProperties;
import com.starry.admin.common.oss.service.IOssFileService;
import com.starry.common.utils.IdUtils;
import java.io.BufferedInputStream;
import java.io.IOException;
import java.io.InputStream;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.exception.ExceptionUtils;
import org.springframework.stereotype.Service;

/**
 * @author admin
 */
@Service
@Slf4j
public class OssFileServiceImpl implements IOssFileService {

    @Override
    public String upload(InputStream inputStream, String module, String filename) {

        BufferedInputStream bufferedInputStream = new BufferedInputStream(inputStream);
        bufferedInputStream.mark(0);
        String fileType = FileTypeUtil.getType(bufferedInputStream);
        try {
            bufferedInputStream.reset();
        } catch (IOException e) {
            throw new CustomException("文件上传到OSS失败");
        }
        // 创建OSSClient实例。
        OSS ossClient = new OSSClientBuilder().build(OssProperties.ENDPOINT, OssProperties.KEY_ID,
                OssProperties.KEY_SECRET);
        log.info("OSSClient实例创建成功");
        try {
            // 判断oss实例是否存在：如果不存在则创建，如果存在则获取
            if (!ossClient.doesBucketExist(OssProperties.BUCKET_NAME)) {
                // 创建bucket
                ossClient.createBucket(OssProperties.BUCKET_NAME);
                log.info("bucket存储空间【{}】创建成功", OssProperties.BUCKET_NAME);
                // 设置oss实例的访问权限：公共读
                ossClient.setBucketAcl(OssProperties.BUCKET_NAME, CannedAccessControlList.PublicRead);
                log.info("【{}】存储空间访问权限设置为公共读成功", OssProperties.BUCKET_NAME);
            }
            // 构建日期路径：avatar/2019/02/26/文件名
            String folder = new DateTime().toString("yyyy/MM/dd");
            // 文件名：uuid.扩展名

            filename = IdUtils.getUuid() + "." + fileType;
            // 文件根路径
            String key = module + "/" + folder + "/" + filename;
            // 创建PutObjectRequest对象。
            PutObjectRequest putObjectRequest = new PutObjectRequest(OssProperties.BUCKET_NAME, key,
                    bufferedInputStream);
            // 创建PutObject请求。
            ossClient.putObject(putObjectRequest);
            log.info("oss文件上传成功");
            // 阿里云文件绝对路径
            String endpoint = OssProperties.ENDPOINT;
            if (OssProperties.ENDPOINT.contains("//")) {
                endpoint = OssProperties.ENDPOINT.substring(OssProperties.ENDPOINT.lastIndexOf("//") + 2);
            }
            // 返回文件的访问路径
            return "https://" + OssProperties.BUCKET_NAME + "." + endpoint + "/" + key;
        } catch (OSSException oe) {
            log.error("OSSException 文件上传失败：", oe);
            throw new CustomException("OSS文件上传异常,{}" + oe.getMessage());
        } catch (ClientException ce) {
            log.error("ClientException 文件上传失败：{}", ExceptionUtils.getStackTrace(ce));
            throw new CustomException("OSS文件上传异常,{}" + ce.getMessage());
        } finally {
            if (ossClient != null) {
                ossClient.shutdown();
                log.info("关闭ossClient");
            }
        }
    }

    @Override
    public void remove(String url) {
        OSS ossClient = new OSSClientBuilder().build(OssProperties.ENDPOINT, OssProperties.KEY_ID,
                OssProperties.KEY_SECRET);
        log.info("OSSClient实例创建成功");
        try {
            String endpoint = OssProperties.ENDPOINT.substring(OssProperties.ENDPOINT.lastIndexOf("//") + 2);
            // 文件名（服务器上的文件路径）
            String host = "https://" + OssProperties.BUCKET_NAME + "." + endpoint + "/";
            String objectName = url.substring(host.length());
            // 删除文件或目录。如果要删除目录，目录必须为空。
            ossClient.deleteObject(OssProperties.BUCKET_NAME, objectName);
            log.info("{}文件删除成功", objectName);
        } catch (OSSException oe) {
            log.error("OSSException 文件删除失败", oe);
            throw new CustomException("OSS文件删除异常,{}" + oe.getMessage());
        } catch (ClientException ce) {
            log.error("ClientException 文件删除失败：{}", ExceptionUtils.getStackTrace(ce));
            throw new CustomException("OSS文件删除异常,{}" + ce.getMessage());
        } finally {
            if (ossClient != null) {
                // 关闭OSSClient。
                ossClient.shutdown();
                log.info("关闭ossClient");
            }
        }
    }

}


--- File: play-admin/src/main/java/com/starry/admin/common/security/config/SpringSecurityConfig.java ---
package com.starry.admin.common.security.config;

import com.starry.admin.common.security.entity.JwtUser;
import com.starry.admin.common.security.filter.JwtAuthenticationTokenFilter;
import com.starry.admin.common.security.handler.CustomAccessDeniedHandler;
import com.starry.admin.common.security.handler.CustomAuthenticationEntryPoint;
import com.starry.admin.common.security.handler.CustomLogoutSuccessHandler;
import com.starry.admin.modules.system.module.entity.SysUserEntity;
import com.starry.admin.modules.system.service.SysMenuService;
import com.starry.admin.modules.system.service.SysUserService;
import java.util.Set;
import javax.annotation.Resource;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
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
 */
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SpringSecurityConfig extends WebSecurityConfigurerAdapter {

    @Resource
    private CustomAccessDeniedHandler customAccessDeniedHandler;
    @Resource
    private CustomAuthenticationEntryPoint customAuthenticationEntryPoint;
    @Resource
    private SysUserService sysUserService;
    @Resource
    private SysMenuService menuService;
    @Resource
    private CustomLogoutSuccessHandler customLogoutSuccessHandler;

    @Override
    protected void configure(HttpSecurity httpSecurity) throws Exception {
        httpSecurity.csrf().disable()// 由于使用的是JWT，我们这里不需要csrf
                .sessionManagement()// 基于token，所以不需要session
                .sessionCreationPolicy(SessionCreationPolicy.STATELESS).and().authorizeRequests()
                // 允许对于网站静态资源的无授权访问
                .antMatchers(HttpMethod.GET, "/", "/*.html", "/favicon.ico", "/**/*.html", "/**/*.css", "/**/*.js",
                        "/swagger-resources/**", "/v2/api-docs/**")
                .permitAll()
                // 对登录注册要允许匿名访问
                .antMatchers("/login", "/captcha/get-captcha", "/wx/**").permitAll()
                // 允许健康检查接口匿名访问
                .antMatchers("/health", "/health/**").permitAll()
                // 跨域请求会先进行一次options请求
                .antMatchers(HttpMethod.OPTIONS).permitAll().anyRequest()// 除上面外的所有请求全部需要鉴权认证
                .authenticated().and().cors().configurationSource(this.corsConfigurationSource());
        // 禁用缓存
        httpSecurity.headers().cacheControl();
        // 添加Logout filter
        httpSecurity.logout().logoutUrl("/logout").logoutSuccessHandler(customLogoutSuccessHandler);
        // 添加JWT filter
        httpSecurity.addFilterBefore(jwtAuthenticationTokenFilter(), UsernamePasswordAuthenticationFilter.class);
        // 添加自定义未授权和未登录结果返回
        httpSecurity.exceptionHandling().accessDeniedHandler(customAccessDeniedHandler)
                .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
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(userDetailsService()).passwordEncoder(passwordEncoder());
    }

    @Bean
    @Override
    public UserDetailsService userDetailsService() {
        // 获取登录用户信息
        return username -> {
            SysUserEntity user = sysUserService.selectUserByUserName(username);
            if (user != null) {
                if (user.getStatus() == 1) {
                    throw new UsernameNotFoundException("对不起，您的账号：" + username + " 已停用");
                }
                // 获取菜单权限
                Set<String> permissionList = menuService.selectMenuPermsByUserId(user.getUserId());
                return new JwtUser(user, permissionList);
            }
            throw new UsernameNotFoundException("用户名或密码错误");
        };
    }

    /**
     * 装载BCrypt密码编码器
     */
    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

    /**
     * JWT filter
     */
    @Bean
    public JwtAuthenticationTokenFilter jwtAuthenticationTokenFilter() {
        return new JwtAuthenticationTokenFilter();
    }
}


--- File: play-admin/src/main/java/com/starry/admin/common/security/entity/JwtUser.java ---
package com.starry.admin.common.security.entity;

import com.starry.admin.modules.system.module.entity.SysUserEntity;
import java.util.Collection;
import java.util.Set;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;

/**
 * @author admin
 */
public class JwtUser implements UserDetails {

    /**
     * 用户ID
     */
    private Long userId;
    /**
     * 登录时间
     */
    private Long loginTime;

    /**
     * 用户唯一标识
     */
    private String token;

    /**
     * 过期时间
     */
    private Long expireTime;

    /**
     * 登录IP地址
     */
    private String ipaddr;

    /**
     * 登录地点
     */
    private String loginLocation;

    /**
     * 浏览器类型
     */
    private String browser;

    /**
     * 操作系统
     */
    private String os;

    /**
     * 用户信息
     */
    private SysUserEntity user;

    /**
     * 权限列表
     */
    private Set<String> permissions;

    public JwtUser(SysUserEntity user, Set<String> permissions) {
        this.user = user;
        this.permissions = permissions;
    }

    public JwtUser(Long userId, SysUserEntity user, Set<String> permissions) {
        this.userId = userId;
        this.user = user;
        this.permissions = permissions;
    }

    public Long getUserId() {
        return userId;
    }

    public void setUserId(Long userId) {
        this.userId = userId;
    }

    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        return null;
    }

    @Override
    public String getPassword() {
        return user.getPassWord();
    }

    @Override
    public String getUsername() {
        return user.getUserCode();
    }

    /**
     * 账户是否未过期
     **/
    @Override
    public boolean isAccountNonExpired() {
        return true;
    }

    /**
     * 账户是否未锁定
     **/
    @Override
    public boolean isAccountNonLocked() {
        return true;
    }

    /**
     * 密码是否未过期
     **/
    @Override
    public boolean isCredentialsNonExpired() {
        return true;
    }

    /**
     * 账户是否激活
     **/
    @Override
    public boolean isEnabled() {
        return user.getStatus() == 0;
    }

    public Long getLoginTime() {
        return loginTime;
    }

    public void setLoginTime(Long loginTime) {
        this.loginTime = loginTime;
    }

    public Set<String> getPermissions() {
        return permissions;
    }

    public void setPermissions(Set<String> permissions) {
        this.permissions = permissions;
    }

    public SysUserEntity getUser() {
        return user;
    }

    public void setUser(SysUserEntity user) {
        this.user = user;
    }

    public String getToken() {
        return token;
    }

    public void setToken(String token) {
        this.token = token;
    }

    public String getIpaddr() {
        return ipaddr;
    }

    public void setIpaddr(String ipaddr) {
        this.ipaddr = ipaddr;
    }

    public String getLoginLocation() {
        return loginLocation;
    }

    public void setLoginLocation(String loginLocation) {
        this.loginLocation = loginLocation;
    }

    public String getBrowser() {
        return browser;
    }

    public void setBrowser(String browser) {
        this.browser = browser;
    }

    public String getOs() {
        return os;
    }

    public void setOs(String os) {
        this.os = os;
    }

    public Long getExpireTime() {
        return expireTime;
    }

    public void setExpireTime(Long expireTime) {
        this.expireTime = expireTime;
    }
}


--- File: play-admin/src/main/java/com/starry/admin/common/security/filter/JwtAuthenticationTokenFilter.java ---
package com.starry.admin.common.security.filter;

import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONUtil;
import com.starry.admin.common.component.JwtToken;
import com.starry.admin.common.domain.LoginUser;
import com.starry.admin.common.exception.CustomException;
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.system.module.entity.SysTenantEntity;
import com.starry.admin.modules.system.service.ISysTenantService;
import com.starry.admin.modules.system.service.SysUserService;
import com.starry.admin.modules.weichat.service.WxTokenService;
import com.starry.admin.utils.SecurityUtils;
import com.starry.common.constant.Constants;
import com.starry.common.redis.RedisCache;
import com.starry.common.result.R;
import com.starry.common.result.ResultCodeEnum;
import java.io.IOException;
import java.util.Arrays;
import java.util.Date;
import java.util.HashSet;
import java.util.Set;
import javax.annotation.Resource;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.validation.constraints.NotNull;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
import org.springframework.web.filter.OncePerRequestFilter;
import org.springframework.web.servlet.HandlerExceptionResolver;

/**
 * @author admin JWT登录授权过滤器
 * @since 2021/9/6
 */
@Slf4j
public class JwtAuthenticationTokenFilter extends OncePerRequestFilter {

    @Resource
    WxTokenService tokenService;

    @Resource
    private JwtToken jwtToken;

    @Resource
    @Qualifier("handlerExceptionResolver")
    private HandlerExceptionResolver resolver;

    @Resource
    private PlayCustomUserInfoServiceImpl customUserInfoService;

    @Resource
    private PlayClerkUserInfoServiceImpl clerkUserInfoService;

    @Resource
    private ISysTenantService sysTenantService;

    @Resource
    private RedisCache redisCache;
    @Resource
    private SysUserService userService;

    /**
     * 不需要登录的的路径
     */
    Set<String> noLoginPathRequired = new HashSet<>(Arrays.asList("/wx/common/area/tree", "/wx/common/file/upload",
            "/wx/common/audio/upload", "/wx/oauth2/getConfigAddress", "/wx/clerk/user/queryByPage",
            "wx/clerk/user/queryGiftById", "/wx/clerk/user/queryPriceById", "/wx/clerk/user/queryTrendsById",
            "wx/clerk/user/queryEvaluateById"));

    @Override
    protected void doFilterInternal(@NotNull HttpServletRequest httpServletRequest,
            @NotNull HttpServletResponse httpServletResponse, @NotNull FilterChain filterChain)
            throws ServletException, IOException {
        log.info("url = {}", httpServletRequest.getRequestURI());

        // 微信支付回调接口,不需要验证
        if ("/wx/pay/jsCallback".equals(httpServletRequest.getServletPath())) {
            log.info("微信支付回调,不需要验证");
            filterChain.doFilter(httpServletRequest, httpServletResponse);
        } else {// 微信公众号的请求，必须存在tenantkey，否则抛出异常
            if (httpServletRequest.getServletPath().startsWith("/wx/")) {

                String tenantKey = httpServletRequest.getHeader("tenantkey");
                if (StrUtil.isBlank(tenantKey)) {
                    resolver.resolveException(httpServletRequest, httpServletResponse, null,
                            new CustomException("tenantkey不能为空"));
                    return;
                }
                if (noLoginPathRequired.contains(httpServletRequest.getServletPath())) {
                    // 非必须登录的请求，验证租户信息是否正常
                    String tenantId;
                    try {
                        tenantId = getTenantId(null, null, tenantKey);
                    } catch (Exception e) {
                        httpServletResponse.setCharacterEncoding("UTF-8");
                        httpServletResponse.setContentType("application/json");
                        httpServletResponse.getWriter()
                                .println(JSONUtil.parse(R.error(ResultCodeEnum.UNAUTHORIZED.getCode(),
                                        ResultCodeEnum.UNAUTHORIZED.getMessage())));
                        httpServletResponse.getWriter().flush();
                        return;
                        // 返回401‘
                        //
                        // return;
                        // throw new RuntimeException("token验证失败,需要重新登录");
                    }
                    if (!checkTenantId(tenantId)) {
                        // 返回500
                        resolver.resolveException(httpServletRequest, httpServletResponse, null,
                                new CustomException("租户信息异常"));
                        return;
                    }
                    SecurityUtils.setTenantId(tenantId);
                } else {
                    String clerkToken = httpServletRequest.getHeader(Constants.CLERK_USER_LOGIN_TOKEN);
                    String customToken = httpServletRequest.getHeader(Constants.CUSTOM_USER_LOGIN_TOKEN);
                    String tenantId;
                    try {
                        tenantId = getTenantId(clerkToken, customToken, tenantKey);
                    } catch (Exception e) {
                        httpServletResponse.setCharacterEncoding("UTF-8");
                        httpServletResponse.setContentType("application/json");
                        httpServletResponse.getWriter()
                                .println(JSONUtil.parse(R.error(ResultCodeEnum.UNAUTHORIZED.getCode(),
                                        ResultCodeEnum.UNAUTHORIZED.getMessage())));
                        httpServletResponse.getWriter().flush();
                        // 返回401
                        return;
                        // throw new CustomException("token验证失败,需要重新登录");
                    }
                    if (!checkTenantId(tenantId)) {
                        // 返回500
                        resolver.resolveException(httpServletRequest, httpServletResponse, null,
                                new CustomException("租户信息异常"));
                        return;
                    }
                    SecurityUtils.setTenantId(tenantId);
                }
            } else {
                // 管理端的请求
                LoginUser jwtUser = jwtToken.getNewLoginUser(httpServletRequest);
                if (null != jwtUser && null == SecurityContextHolder.getContext().getAuthentication()) {
                    jwtToken.verifyToken(jwtUser);
                    userService.selectUserById(jwtUser.getUserId());
                    UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(
                            jwtUser, null, jwtUser.getAuthorities());
                    authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(httpServletRequest));
                    SecurityContextHolder.getContext().setAuthentication(authentication);
                }
            }
            filterChain.doFilter(httpServletRequest, httpServletResponse);
        }
    }

    /**
     * 微信公众号端访问时，获取当前用户租户ID 如果用户（陪聊或客户）已登录，从token中获取租户ID 如果用户未登录，从tenantKey中获取租户ID
     *
     * @param clerkToken
     *            陪聊登录Key
     * @param customToken
     *            客户登录key
     * @param tenantKey
     *            租户标识
     */
    public String getTenantId(String clerkToken, String customToken, String tenantKey) {
        String tenantId = "";
        // 如果用户（陪聊或客户）已登录，从token中获取租户ID
        if (StrUtil.isNotBlank(clerkToken) || StrUtil.isNotBlank(customToken)) {
            String userId = tokenService.getWxUserIdByToken(StrUtil.isNotBlank(clerkToken) ? clerkToken : customToken);
            String redisKey = "TENANT_INFO:" + userId;
            SecurityUtils.setTenantId(redisCache.getCacheObject(redisKey));
            if (clerkToken != null) {
                PlayClerkUserInfoEntity entity = clerkUserInfoService.selectById(userId);
                if (entity == null) {
                    log.error("当前登录陪聊不存在,clerkToken={}，tenantKey={}", clerkToken, tenantKey);
                    throw new CustomException("用户不存在");
                }
                return entity.getTenantId();
            } else {
                PlayCustomUserInfoEntity entity = customUserInfoService.selectById(userId);
                if (entity == null) {
                    log.error("当前登录顾客不存在,customToken={}，tenantKey={}", customToken, tenantKey);
                    throw new CustomException("用户不存在");
                }
                return entity.getTenantId();
            }
        } else {
            // 如果用户未登录，从tenantKey中获取租户ID，然后验证租户ID是否存在，以及租户是否过期等
            SysTenantEntity entity = sysTenantService.selectByTenantKey(tenantKey);
            if (entity != null) {
                return entity.getTenantId();
            }
        }
        return tenantId;
    }

    /**
     * 校验租户是否正常
     *
     * @param tenantId
     *            租户ID
     * @return true:租户正常，false:租户不正常
     */
    public boolean checkTenantId(String tenantId) {
        if (StrUtil.isBlankIfStr(tenantId)) {
            return false;
        }
        if (tenantId.equals("-1")) {
            return true;
        }
        SysTenantEntity entity = sysTenantService.selectSysTenantByTenantId(tenantId);
        if (entity == null) {
            return false;
        }
        // 判断租户是否过期
        if (DateUtil.compare(entity.getTenantTime(), new Date()) < 0) {
            return false;
        }
        // ...
        // 校验通过
        return true;
    }
}


--- File: play-admin/src/main/java/com/starry/admin/common/security/handler/CustomAccessDeniedHandler.java ---
package com.starry.admin.common.security.handler;

import cn.hutool.json.JSONUtil;
import com.starry.common.result.R;
import com.starry.common.result.ResultCodeEnum;
import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.web.access.AccessDeniedHandler;
import org.springframework.stereotype.Component;

/**
 * @author admin 当访问接口没有权限时，自定义的返回结果
 * @since 2021/9/6
 */
@Component
public class CustomAccessDeniedHandler implements AccessDeniedHandler {

    @Override
    public void handle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse,
            AccessDeniedException e) throws IOException, ServletException {
        httpServletResponse.setCharacterEncoding("UTF-8");
        httpServletResponse.setContentType("application/json");
        httpServletResponse.getWriter().println(
                JSONUtil.parse(R.error(ResultCodeEnum.FORBIDDEN.getCode(), ResultCodeEnum.FORBIDDEN.getMessage())));
        httpServletResponse.getWriter().flush();
    }
}


--- File: play-admin/src/main/java/com/starry/admin/common/security/handler/CustomAuthenticationEntryPoint.java ---
package com.starry.admin.common.security.handler;

import cn.hutool.json.JSONUtil;
import com.starry.common.result.R;
import com.starry.common.result.ResultCodeEnum;
import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.stereotype.Component;

/**
 * @author admin 当未登录或者token失效访问接口时，自定义的返回结果
 * @since 2021/9/6
 */
@Component
public class CustomAuthenticationEntryPoint implements AuthenticationEntryPoint {
    @Override
    public void commence(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse,
            AuthenticationException e) throws IOException, ServletException {
        httpServletResponse.setCharacterEncoding("UTF-8");
        httpServletResponse.setContentType("application/json");
        httpServletResponse.getWriter().println(JSONUtil
                .parse(R.error(ResultCodeEnum.UNAUTHORIZED.getCode(), ResultCodeEnum.UNAUTHORIZED.getMessage())));
        httpServletResponse.getWriter().flush();
    }
}


--- File: play-admin/src/main/java/com/starry/admin/common/security/handler/CustomLogoutSuccessHandler.java ---
package com.starry.admin.common.security.handler;

import com.alibaba.fastjson2.JSON;
import com.starry.admin.common.component.JwtToken;
import com.starry.admin.common.security.entity.JwtUser;
import com.starry.admin.manager.AsyncManager;
import com.starry.admin.manager.factory.AsyncFactory;
import com.starry.common.constant.Constants;
import com.starry.common.result.R;
import com.starry.common.utils.ServletUtils;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.security.core.Authentication;
import org.springframework.security.web.authentication.logout.LogoutSuccessHandler;
import org.springframework.stereotype.Component;

/**
 * @author admin 自定义退出处理类 返回成功
 * @since 2022/7/8
 */
@Component
public class CustomLogoutSuccessHandler implements LogoutSuccessHandler {

    @Resource
    private JwtToken jwtToken;

    @Override
    public void onLogoutSuccess(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse,
            Authentication authentication) {
        JwtUser jwtUser = jwtToken.getLoginUser(httpServletRequest);
        if (null != jwtUser) {
            // 删除用户缓存记录
            jwtToken.removeJwtUser(jwtUser.getToken());
            // 记录用户退出日志
            AsyncManager.me().execute(AsyncFactory.recordLoginLog(jwtUser.getUsername(), Constants.LOGOUT, "退出成功"));
        }
        ServletUtils.renderString(httpServletResponse, JSON.toJSONString(R.ok("退出成功")));
    }
}


--- File: play-admin/src/main/java/com/starry/admin/common/task/ClerkPkJob.java ---
package com.starry.admin.common.task;

import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.starry.admin.modules.clerk.module.entity.ClerkPkEnum;
import com.starry.admin.modules.clerk.module.entity.PlayClerkPkEntity;
import com.starry.admin.modules.clerk.service.IPlayClerkPkService;
import java.util.Date;
import javax.annotation.Resource;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;

/**
 * @Author: huchuansai
 * @Date: 2024/8/2 4:31 PM
 * @Description:
 */
@Component
public class ClerkPkJob {

    @Resource
    private IPlayClerkPkService playClerkPkService;

    @Scheduled(fixedRate = 60000)
    public void pkJobStatus() {
        // 更新状态将待开始的状态改为进行中
        playClerkPkService.update(Wrappers.lambdaUpdate(PlayClerkPkEntity.class)
                .eq(PlayClerkPkEntity::getStatus, ClerkPkEnum.TO_BE_STARTED.name())
                .le(PlayClerkPkEntity::getPkBeginTime, new Date())
                .set(PlayClerkPkEntity::getStatus, ClerkPkEnum.IN_PROGRESS.name()));

        playClerkPkService.update(Wrappers.lambdaUpdate(PlayClerkPkEntity.class)
                .eq(PlayClerkPkEntity::getStatus, ClerkPkEnum.IN_PROGRESS.name())
                .le(PlayClerkPkEntity::getPkEndTime, new Date())
                .set(PlayClerkPkEntity::getStatus, ClerkPkEnum.FINISHED.name()));
    }
}


--- File: play-admin/src/main/java/com/starry/admin/common/task/ClerkWagesSettlementTask.java ---
package com.starry.admin.common.task;

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.PlayClerkWagesInfoEntity;
import com.starry.admin.modules.clerk.service.IPlayClerkUserInfoService;
import com.starry.admin.modules.clerk.service.IPlayClerkWagesDetailsInfoService;
import com.starry.admin.modules.clerk.service.IPlayClerkWagesInfoService;
import com.starry.admin.modules.order.module.entity.PlayOrderInfoEntity;
import com.starry.admin.modules.order.service.IPlayOrderInfoService;
import com.starry.admin.modules.system.module.entity.SysTenantEntity;
import com.starry.admin.modules.system.service.ISysTenantService;
import com.starry.admin.utils.SecurityUtils;
import com.starry.common.utils.IdUtils;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.temporal.ChronoUnit;
import java.util.List;
import javax.annotation.Resource;
import org.springframework.stereotype.Component;

/**
 * 订单工资结算
 *
 * @author admin
 * @since 2024/5/28 下午2:42
 **/
@Component
public class ClerkWagesSettlementTask {

    @Resource
    ISysTenantService sysTenantService;

    @Resource
    private IPlayClerkUserInfoService clerkUserInfoService;

    @Resource
    private IPlayOrderInfoService playOrderInfoService;

    @Resource
    private IPlayClerkWagesInfoService playClerkWagesInfoService;

    @Resource
    private IPlayClerkWagesDetailsInfoService playClerkWagesDetailsInfoService;

    /**
     * 每分钟查询未结算订单 如果订单完成时间超过24小时，可进行结算，生成工资
     */
    // @Scheduled(cron = "0 0/1 * * * ?")
    public void dailyRanking() {
        // 1、查询所有的租户信息
        List<SysTenantEntity> tenantEntities = sysTenantService.listAll();
        for (SysTenantEntity tenantEntity : tenantEntities) {
            SecurityUtils.setTenantId(tenantEntity.getTenantId());
            List<PlayClerkUserInfoEntity> clerkUserReturnVos = clerkUserInfoService.listAll();
            // 生成每个人的工资信息
            for (PlayClerkUserInfoEntity clerkUserInfo : clerkUserReturnVos) {
                updateClerkWagesInfo(clerkUserInfo.getId());
            }
        }
    }

    /**
     * 活动24小时前完成的订单，对订单进行结算
     *
     * @param clerkId
     *            店员ID
     */
    public void updateClerkWagesInfo(String clerkId) {
        // 获得24小时前完成,并且未结算的订单
        LocalDateTime lastTime = LocalDateTime.now().minusDays(1);
        // 查询当前店员上次结算工资到现在的订单
        List<PlayOrderInfoEntity> orderInfoEntities = playOrderInfoService.listByEndTime(clerkId, lastTime);
        PlayClerkWagesInfoEntity entity = playClerkWagesInfoService.getLastSettlement(clerkId);
        // 更新订单信息
        updateClerkWagesInfo(clerkId, entity == null ? IdUtils.getUuid() : entity.getId(), orderInfoEntities);
    }

    /**
     * 更新最新一次工资统计信息 更新订单记录
     *
     * @param orderInfoEntities
     *            订单列表
     */
    public void updateClerkWagesInfo(String clerkId, String wagesId, List<PlayOrderInfoEntity> orderInfoEntities) {
        // 修改订单状态并且新增订单结算详情
        for (PlayOrderInfoEntity orderInfo : orderInfoEntities) {
            // 修改订单状态
            orderInfo.setOrderSettlementState("1");
            orderInfo.setOrderSettlementTime(LocalDateTime.now());
            playOrderInfoService.update(orderInfo);

            PlayClerkWagesDetailsInfoEntity wagesDetailsInfo = playClerkWagesDetailsInfoService
                    .selectByOrderId(orderInfo.getId());
            if (wagesDetailsInfo == null) {
                wagesDetailsInfo = new PlayClerkWagesDetailsInfoEntity();
                wagesDetailsInfo.setId(IdUtils.getUuid());
            }
            // 新增订单结算详情
            wagesDetailsInfo.setWagesId(wagesId);
            wagesDetailsInfo.setClerkId(clerkId);
            wagesDetailsInfo.setOrderId(orderInfo.getId());
            wagesDetailsInfo.setOrderNo(orderInfo.getOrderNo());
            wagesDetailsInfo.setFinalAmount(orderInfo.getFinalAmount());
            wagesDetailsInfo.setEstimatedRevenue(orderInfo.getEstimatedRevenue());
            wagesDetailsInfo.setEndOrderTime(orderInfo.getOrderEndTime());
            playClerkWagesDetailsInfoService.saveOrUpdate(wagesDetailsInfo);
        }
        // 新增订单结算信息
        BigDecimal finalAmount = BigDecimal.ZERO;
        Integer orderContinueNumber = 0;
        BigDecimal orderContinueMoney = BigDecimal.ZERO;
        Integer ordersExpiredNumber = 0;
        BigDecimal estimatedRevenue = BigDecimal.ZERO;
        Integer orderNumber = 0;
        LocalDate lastTime = playClerkWagesInfoService.getLastSettlementTime(clerkId);
        for (PlayClerkWagesDetailsInfoEntity entity : playClerkWagesDetailsInfoService.selectByWagesId(wagesId)) {
            PlayOrderInfoEntity orderInfo = playOrderInfoService.selectOrderInfoById(entity.getOrderId());
            finalAmount = finalAmount.add(orderInfo.getFinalAmount());
            orderNumber++;
            estimatedRevenue = estimatedRevenue.add(orderInfo.getEstimatedRevenue());
            if ("0".equals(orderInfo.getFirstOrder())) {
                orderContinueNumber++;
                orderContinueMoney = orderContinueMoney.add(orderInfo.getFinalAmount());
            }
            if ("1".equals(orderInfo.getOrdersExpiredState())) {
                ordersExpiredNumber++;
            }
        }

        PlayClerkWagesInfoEntity wagesInfo = new PlayClerkWagesInfoEntity();
        wagesInfo.setId(wagesId);
        wagesInfo.setClerkId(clerkId);
        wagesInfo.setStartCountDate(lastTime);
        wagesInfo.setEndCountDate(LocalDate.now());
        wagesInfo.setOrderNumber(orderNumber);
        wagesInfo.setFinalAmount(finalAmount);
        wagesInfo.setOrderContinueNumber(orderContinueNumber);
        wagesInfo.setOrderContinueMoney(orderContinueMoney);
        wagesInfo.setOrdersExpiredNumber(ordersExpiredNumber);
        wagesInfo.setSettlementDate(LocalDate.now());
        wagesInfo.setEstimatedRevenue(estimatedRevenue);
        if (ChronoUnit.DAYS.between(LocalDate.now(), lastTime) <= 7) {
            wagesInfo.setHistoricalStatistics("0");
        } else {
            wagesInfo.setHistoricalStatistics("1");
        }

        playClerkWagesInfoService.saveOrUpdate(wagesInfo);
    }
}


--- File: play-admin/src/main/java/com/starry/admin/common/task/OrderRankingSettlementTask.java ---
package com.starry.admin.common.task;

import com.starry.admin.modules.clerk.module.entity.PlayClerkRankingInfoEntity;
import com.starry.admin.modules.clerk.module.entity.PlayClerkUserInfoEntity;
import com.starry.admin.modules.clerk.service.IPlayClerkRankingInfoService;
import com.starry.admin.modules.clerk.service.IPlayClerkUserInfoService;
import com.starry.admin.modules.order.module.entity.PlayOrderInfoEntity;
import com.starry.admin.modules.order.service.IPlayOrderInfoService;
import com.starry.admin.modules.system.module.entity.SysTenantEntity;
import com.starry.admin.modules.system.service.ISysTenantService;
import com.starry.admin.utils.SecurityUtils;
import com.starry.common.utils.IdUtils;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import javax.annotation.Resource;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;

/**
 * 订单排名结算信息
 *
 * @author admin
 * @since 2024/5/28 下午2:42
 **/
@Component
public class OrderRankingSettlementTask {

    @Resource
    ISysTenantService sysTenantService;

    @Resource
    private IPlayClerkUserInfoService clerkUserInfoService;

    @Resource
    private IPlayOrderInfoService orderInfoService;

    @Resource
    private IPlayClerkRankingInfoService clerkRankingInfoService;

    DateTimeFormatter df = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");

    /**
     * 当日订单结算，查询前一日的订单信息，生成订单排名数据 每天凌晨0点5分0秒时触发执行
     */
    @Scheduled(cron = "0 5 0 * * ?")
    public void dailyRanking() {
        DateTimeFormatter df = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
        String startTime = LocalDate.now().minusDays(1).atStartOfDay().format(df);
        String endTime = LocalDateTime.of(LocalDate.now().minusDays(1), LocalTime.MAX).format(df);
        rankingSettlement(startTime, endTime, "0");
    }

    /**
     * 每周订单结算，周一凌晨30分执行，查询上一周的订单，然后生成订单排行数据 每周一凌晨0点30分0秒时触发执行
     */
    @Scheduled(cron = "0 30 0 ? * MON")
    public void weeklyRankingSettlement() {

        String startTime = LocalDate.now().minusDays(8).atStartOfDay().format(df);
        String endTime = LocalDateTime.of(LocalDate.now().minusDays(1), LocalTime.MAX).format(df);
        rankingSettlement(startTime, endTime, "1");
    }

    /**
     * 生产排行信息 1、查询所有的租户信息 2、查询每个租户的用户，以及他们在对应时间段的订单 3、根据订单生产排行信息
     *
     * @param startTime
     *            接单开始时间
     * @param endTime
     *            接单截至时间
     * @param weeklyRanking
     *            weeklyRanking 日排名还是周排名（0:每日排名;1:每周排名）
     * @author admin
     * @since 2024/6/12 15:21
     **/
    public void rankingSettlement(String startTime, String endTime, String weeklyRanking) {
        // 1、查询所有的租户信息
        List<SysTenantEntity> tenantEntities = sysTenantService.listAll();
        // 2、查询每个租户的用户，以及他们的订单信息
        for (SysTenantEntity tenantEntity : tenantEntities) {
            SecurityUtils.setTenantId(tenantEntity.getTenantId());
            // 当前租户用户列表
            List<PlayClerkUserInfoEntity> clerkUserReturnVos = clerkUserInfoService.listAll();
            List<PlayClerkRankingInfoEntity> clerkRankingInfoEntities = new ArrayList<>();
            // 查询当前租户、当前排行、的最后一次统计编号
            Integer newSerialNumber = clerkRankingInfoService.selectSerialNumber(weeklyRanking);
            // 生成每个人的订单排行信息
            for (PlayClerkUserInfoEntity clerkUserInfo : clerkUserReturnVos) {
                // 生成订单排行数据
                clerkRankingInfoEntities.add(
                        getClerkRanking(clerkUserInfo.getId(), startTime, endTime, weeklyRanking, newSerialNumber));
            }
            // 更新排行名次
            clerkRankingInfoEntities.sort((p1, p2) -> p2.getOrderNumber() - p1.getOrderNumber());
            for (int i = 0; i < clerkRankingInfoEntities.size(); i++) {
                PlayClerkRankingInfoEntity item = clerkRankingInfoEntities.get(i);
                item.setRankingIndex(i + 1);
                if (i > 0) {
                    item.setPreviousMoney(
                            clerkRankingInfoEntities.get(i - 1).getOrderMoney().subtract(item.getOrderMoney()));
                }
            }
            // 排名数据生成后，将以往排名状态标记为历史排名
            clerkRankingInfoService.updateClerkRankingInfo(weeklyRanking, newSerialNumber);
            // 插入本次排名数据
            clerkRankingInfoService.saveBatch(clerkRankingInfoEntities);
        }
    }

    /**
     * 生产当前排行信息
     *
     * @param clerkId
     *            店员ID
     * @param startTime
     *            排行开始统计日期 yyyy-mm-dd 00:00:00
     * @param endTime
     *            排行结束统计日期 yyyy-mm-dd 23:59:59
     * @param weeklyRanking
     *            日排名还是周排名（0:每日排名;1:每周排名）
     * @param serialNumber
     *            查询当前租户、当前排行、的最后一次统计编号
     * @author admin
     * @since 2024/6/7 11:43
     **/
    public PlayClerkRankingInfoEntity getClerkRanking(String clerkId, String startTime, String endTime,
            String weeklyRanking, Integer serialNumber) {
        List<PlayOrderInfoEntity> orderInfoEntities = orderInfoService.listByTime(clerkId, startTime, endTime, null);
        int orderNumber = orderInfoEntities.size();
        BigDecimal orderMoney = BigDecimal.ZERO;
        Integer orderContinueNumber = 0;
        BigDecimal orderContinueMoney = BigDecimal.ZERO;
        int ordersExpiredNumber = 0;
        Set<String> customIds = new HashSet<>();
        for (PlayOrderInfoEntity orderInfoEntity : orderInfoEntities) {
            customIds.add(orderInfoEntity.getAcceptBy());
            orderMoney = orderMoney.add(orderInfoEntity.getOrderMoney());
            if ("0".equals(orderInfoEntity.getFirstOrder())) {
                orderContinueNumber++;
                orderContinueMoney = orderContinueMoney.add(orderInfoEntity.getOrderMoney());
            }
            if ("1".equals(orderInfoEntity.getOrdersExpiredState())) {
                ordersExpiredNumber++;
            }
        }
        BigDecimal orderContinueProportion = orderNumber == 0
                ? BigDecimal.ZERO
                : 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();
        rankingInfo.setId(IdUtils.getUuid());
        rankingInfo.setClerkId(clerkId);
        // 默认为当前统计
        rankingInfo.setHistoricalStatistics("0");
        rankingInfo.setWeeklyRanking(weeklyRanking);
        // 当前统计编号+1
        rankingInfo.setSerialNumber(serialNumber + 1);
        // 每个人的名次默认为1，后面在进行排序更新名次
        rankingInfo.setRankingIndex(1);
        // 统计时间
        rankingInfo.setStartCountDate(LocalDate.parse(startTime, df));
        rankingInfo.setEndCountDate(LocalDate.parse(endTime, df));
        rankingInfo.setOrderNumber(orderInfoEntities.size());
        rankingInfo.setOrderMoney(orderMoney);
        rankingInfo.setOrderContinueMoney(orderContinueMoney);
        rankingInfo.setOrderContinueNumber(orderContinueNumber);
        rankingInfo.setOrderContinueProportion(orderContinueProportion);
        rankingInfo.setOrdersExpiredNumber(ordersExpiredNumber);
        rankingInfo.setCustomNumber(customIds.size());
        rankingInfo.setAverageUnitPrice(averageUnitPrice);
        rankingInfo.setPreviousMoney(BigDecimal.ZERO);
        rankingInfo.setSettlementDate(LocalDate.now());
        return rankingInfo;
    }

}


--- File: play-admin/src/main/java/com/starry/admin/common/task/OverdueOrderHandlerTask.java ---
package com.starry.admin.common.task;

import com.starry.admin.modules.order.module.entity.PlayOrderInfoEntity;
import com.starry.admin.modules.order.service.IPlayOrderInfoService;
import com.starry.admin.utils.SecurityUtils;
import java.util.ArrayList;
import java.util.Set;
import javax.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;

/**
 * 超时未接单的订单退款处理任务
 *
 * @author admin
 * @since 2024/8/15 13:24
 **/
@Slf4j
@Component
public class OverdueOrderHandlerTask {

    private static final String QUEUE_KEY = "random_order_queue";

    private static final int MAX_TIME = 24 * 60 * 40;

    @Resource
    private StringRedisTemplate stringRedisTemplate;

    @Resource
    private IPlayOrderInfoService playOrderInfoService;

    public void enqueue(String message) {
        enqueue(message, MAX_TIME);
    }

    public void enqueue(String message, long delaySeconds) {
        long score = System.currentTimeMillis() / 1000 + delaySeconds;
        stringRedisTemplate.opsForZSet().add(QUEUE_KEY, message, score);
    }

    /**
     * 每10秒钟检测，判断任务是否到期
     *
     * @author admin
     * @since 2024/8/15 13:47
     **/
    @Scheduled(fixedRate = 10000)
    public void processMessages() {
        long now = System.currentTimeMillis() / 1000;
        Set<String> messages = stringRedisTemplate.opsForZSet().rangeByScore(QUEUE_KEY, 0, now);
        if (messages == null) {
            return;
        }
        for (String message : messages) {
            log.info("处理消息{}", message);
            // 处理消息
            // 订单退款
            String[] strs = message.split("_");
            orderHandler(strs[0], strs[1]);
            // 移除已经处理的消息
            stringRedisTemplate.opsForZSet().remove(QUEUE_KEY, message);
        }
    }

    public void orderHandler(String orderId, String tenantId) {
        SecurityUtils.setTenantId(tenantId);
        PlayOrderInfoEntity orderInfo = playOrderInfoService.getById(orderId);
        if (orderInfo == null) {
            return;
        }
        // 如果订单未接单，进行退款处理
        if ("0".equals(orderInfo.getOrderStatus())) {
            try {
                playOrderInfoService.updateStateTo4("2", "admin", orderInfo.getId(), "订单长时间未接单", new ArrayList<>());
            } catch (Exception e) {
                log.error("订单退款失败", e);
            }

        }
    }
}


--- File: play-admin/src/main/java/com/starry/admin/manager/AsyncManager.java ---
package com.starry.admin.manager;

import com.starry.common.utils.SpringUtils;
import com.starry.common.utils.ThreadsUtils;
import java.util.TimerTask;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

/**
 * @author admin 异步任务管理器
 * @since 2022/7/25
 */
public class AsyncManager {

    private static final AsyncManager me = new AsyncManager();
    /**
     * 操作延迟10毫秒
     */
    private final int OPERATE_DELAY_TIME = 10;
    /**
     * 异步操作任务调度线程池
     */
    private final ScheduledExecutorService executor = SpringUtils.getBean("scheduledExecutorService");

    /**
     * 单例模式
     */
    private AsyncManager() {
    }

    public static AsyncManager me() {
        return me;
    }

    /**
     * 执行任务
     *
     * @param task
     *            任务
     */
    public void execute(TimerTask task) {
        executor.schedule(task, OPERATE_DELAY_TIME, TimeUnit.MILLISECONDS);
    }

    /**
     * 停止任务线程池
     */
    public void shutdown() {
        ThreadsUtils.shutdownAndAwaitTermination(executor);
    }
}


--- File: play-admin/src/main/java/com/starry/admin/manager/ShutdownManager.java ---
package com.starry.admin.manager;

import javax.annotation.PreDestroy;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;

/**
 * 确保应用退出时能关闭后台线程
 *
 * @author ruoyi
 */
@Component
public class ShutdownManager {
    private static final Logger logger = LoggerFactory.getLogger("sys-user");

    @PreDestroy
    public void destroy() {
        shutdownAsyncManager();
    }

    /**
     * 停止异步执行任务
     */
    private void shutdownAsyncManager() {
        try {
            logger.info("====关闭后台任务任务线程池====");
            AsyncManager.me().shutdown();
        } catch (Exception e) {
            logger.error(e.getMessage(), e);
        }
    }
}


--- File: play-admin/src/main/java/com/starry/admin/manager/factory/AsyncFactory.java ---
package com.starry.admin.manager.factory;

import cn.hutool.extra.servlet.ServletUtil;
import cn.hutool.http.useragent.UserAgent;
import cn.hutool.http.useragent.UserAgentUtil;
import com.starry.admin.modules.system.module.entity.SysLoginLogEntity;
import com.starry.admin.modules.system.service.ISysLoginLogService;
import com.starry.common.constant.Constants;
import com.starry.common.utils.ServletUtils;
import com.starry.common.utils.SpringUtils;
import com.starry.common.utils.ip.AddressUtils;
import java.util.Date;
import java.util.TimerTask;
import lombok.extern.slf4j.Slf4j;

/**
 * @author admin
 */
@Slf4j
public class AsyncFactory {

    /**
     * 记录登录信息
     *
     * @param username
     *            用户名
     * @param status
     *            状态
     * @param message
     *            消息
     * @param args
     *            列表
     * @return 任务task
     */
    public static TimerTask recordLoginLog(final String username, final String status, final String message,
            final Object... args) {
        // 客户端操作系统、浏览器等信息
        final UserAgent userAgent = UserAgentUtil.parse(ServletUtils.getRequest().getHeader("User-Agent"));
        // 请求的IP地址
        final String ip = ServletUtil.getClientIP(ServletUtils.getRequest());
        return new TimerTask() {
            @Override
            public void run() {
                String address = AddressUtils.getRealAddressByIp(ip);
                // 获取客户端操作系统
                String os = userAgent.getOs().getName();
                // 获取客户端浏览器
                String browser = userAgent.getBrowser().getName();
                // 封装对象
                SysLoginLogEntity loginLog = new SysLoginLogEntity();
                loginLog.setUserCode(username);
                loginLog.setIpaddr(ip);
                loginLog.setLoginLocation(address);
                loginLog.setBrowser(browser);
                loginLog.setOs(os);
                loginLog.setMsg(message);
                loginLog.setLoginTime(new Date());
                // 日志状态
                if (Constants.LOGIN_FAIL.equals(status)) {
                    loginLog.setStatus(Integer.valueOf(Constants.FAIL));
                } else {
                    loginLog.setStatus(Integer.valueOf(Constants.SUCCESS));
                }
                // 插入数据
                SpringUtils.getBean(ISysLoginLogService.class).create(loginLog);
            }
        };
    }

}


--- File: play-admin/src/main/java/com/starry/admin/modules/clerk/controller/PlayAvatarFrameInfoController.java ---
package com.starry.admin.modules.clerk.controller;

import com.starry.admin.common.oss.service.IOssFileService;
import com.starry.admin.modules.clerk.module.entity.PlayAvatarFrameInfoEntity;
import com.starry.admin.modules.clerk.module.vo.PlayAvatarFrameInfoAddVo;
import com.starry.admin.modules.clerk.module.vo.PlayAvatarFrameInfoQueryVo;
import com.starry.admin.modules.clerk.module.vo.PlayAvatarFrameSendVo;
import com.starry.admin.modules.clerk.service.IPlayAvatarFrameInfoService;
import com.starry.admin.utils.SecurityUtils;
import com.starry.common.annotation.Log;
import com.starry.common.enums.BusinessType;
import com.starry.common.result.R;
import com.starry.common.utils.ConvertUtil;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiParam;
import io.swagger.annotations.ApiResponse;
import io.swagger.annotations.ApiResponses;
import java.io.IOException;
import javax.annotation.Resource;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;

/**
 * 店员头像框Controller
 *
 * @author admin
 * @since 2024-06-06
 */
@Api(tags = "店员头像框管理", description = "店员头像框信息管理相关接口，包括查询、新增、修改、删除和赠送等操作")
@RestController
@RequestMapping("/clerk/avatarFrame")
public class PlayAvatarFrameInfoController {
    @Resource
    private IPlayAvatarFrameInfoService playAvatarFrameInfoService;

    @Resource
    private IOssFileService ossFileService;

    @ApiOperation(value = "分页查询头像框", notes = "分页查询店员头像框信息列表")
    @ApiResponses({
            @ApiResponse(code = 200, message = "操作成功", response = PlayAvatarFrameInfoEntity.class, responseContainer = "Page")})
    @PostMapping("/listByPage")
    public R listByPage(
            @ApiParam(value = "查询条件", required = true) @Validated @RequestBody PlayAvatarFrameInfoQueryVo vo) {
        return R.ok(playAvatarFrameInfoService.selectByPage(vo));
    }

    /**
     * 查询店员头像框列表
     */
    @ApiOperation(value = "查询所有头像框", notes = "获取所有店员头像框信息列表")
    @ApiResponses({
            @ApiResponse(code = 200, message = "操作成功", response = PlayAvatarFrameInfoEntity.class, responseContainer = "List")})
    @GetMapping("/listAll")
    public R listAll() {
        return R.ok(playAvatarFrameInfoService.selectAll());
    }

    @ApiOperation(value = "上传头像框图片", notes = "上传店员头像框图片文件")
    @ApiResponses({@ApiResponse(code = 200, message = "操作成功", response = String.class, responseContainer = "R")})
    @PostMapping("/uploadFile")
    public R uploadFile(@ApiParam(value = "图片文件", required = true) @RequestParam("file") MultipartFile file)
            throws IOException {
        String fileAddress = ossFileService.upload(file.getInputStream(), SecurityUtils.getTenantId(),
                file.getOriginalFilename());
        return R.ok(fileAddress);
    }

    /**
     * 新增店员头像框
     */
    @ApiOperation(value = "新增头像框", notes = "创建新的店员头像框信息")
    @ApiResponses({@ApiResponse(code = 200, message = "操作成功"), @ApiResponse(code = 500, message = "添加失败")})
    @Log(title = "店员头像框", businessType = BusinessType.INSERT)
    @PostMapping("/create")
    public R create(@ApiParam(value = "头像框信息", required = true) @Validated @RequestBody PlayAvatarFrameInfoAddVo vo) {
        boolean success = playAvatarFrameInfoService
                .create(ConvertUtil.entityToVo(vo, PlayAvatarFrameInfoEntity.class));
        if (success) {
            return R.ok();
        }
        return R.error("添加失败");
    }

    /**
     * 修改店员头像框
     */
    @ApiOperation(value = "修改头像框", notes = "根据ID修改店员头像框信息")
    @ApiImplicitParam(name = "id", value = "头像框ID", required = true, paramType = "path", dataType = "String", example = "1")
    @ApiResponses({@ApiResponse(code = 200, message = "操作成功"), @ApiResponse(code = 500, message = "修改失败")})
    @Log(title = "店员头像框", businessType = BusinessType.UPDATE)
    @PostMapping(value = "/update/{id}")
    public R update(@PathVariable String id,
            @ApiParam(value = "头像框信息", required = true) @RequestBody PlayAvatarFrameInfoEntity playAvatarFrameInfo) {
        playAvatarFrameInfo.setId(id);
        boolean success = playAvatarFrameInfoService.update(playAvatarFrameInfo);
        if (success) {
            return R.ok();
        }
        return R.error("修改失败");
    }

    /**
     * 删除店员头像框
     */
    @ApiOperation(value = "删除头像框", notes = "根据ID批量删除店员头像框信息")
    @ApiImplicitParam(name = "ids", value = "头像框ID数组", required = true, paramType = "path", dataType = "String[]", example = "1,2,3")
    @ApiResponses({@ApiResponse(code = 200, message = "操作成功", response = Integer.class)})
    @Log(title = "店员头像框", businessType = BusinessType.DELETE)
    @DeleteMapping("/{ids}")
    public R remove(@PathVariable String[] ids) {
        return R.ok(playAvatarFrameInfoService.deletePlayAvatarFrameInfoByIds(ids));
    }

    /**
     * 新增店员头像框
     */
    @ApiOperation(value = "赠送头像框", notes = "赠送头像框给指定店员")
    @ApiResponses({@ApiResponse(code = 200, message = "操作成功"), @ApiResponse(code = 500, message = "添加失败")})
    @Log(title = "赠送店员头像框", businessType = BusinessType.INSERT)
    @PostMapping("/sendAvatarFrame")
    public R sendAvatarFrame(
            @ApiParam(value = "赠送信息", required = true) @Validated @RequestBody PlayAvatarFrameSendVo vo) {
        return R.error("添加失败");
    }

}


--- File: play-admin/src/main/java/com/starry/admin/modules/clerk/controller/PlayClerkArticleInfoController.java ---
package com.starry.admin.modules.clerk.controller;

import com.baomidou.mybatisplus.core.metadata.IPage;
import com.starry.admin.modules.clerk.module.entity.PlayClerkArticleInfoEntity;
import com.starry.admin.modules.clerk.module.vo.PlayClerkArticleQueryVo;
import com.starry.admin.modules.clerk.module.vo.PlayClerkArticleReturnVo;
import com.starry.admin.modules.clerk.module.vo.PlayClerkArticleReviewStateEditVo;
import com.starry.admin.modules.clerk.service.IPlayClerkArticleInfoService;
import com.starry.admin.modules.clerk.service.IPlayCustomArticleInfoService;
import com.starry.common.annotation.Log;
import com.starry.common.enums.BusinessType;
import com.starry.common.result.R;
import com.starry.common.utils.ConvertUtil;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiParam;
import io.swagger.annotations.ApiResponse;
import io.swagger.annotations.ApiResponses;
import javax.annotation.Resource;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;

/**
 * 店员动态信息Controller
 *
 * @author admin
 * @since 2024-05-04
 */
@Api(tags = "店员动态管理", description = "店员动态信息管理相关接口，包括查询、审核和删除等操作")
@RestController
@RequestMapping("/clerk/article")
public class PlayClerkArticleInfoController {
    @Resource
    private IPlayClerkArticleInfoService playClerkArticleInfoService;
    @Resource
    private IPlayCustomArticleInfoService playCustomArticleInfoService;

    /**
     * 查询店员动态信息列表
     */
    @ApiOperation(value = "分页查询动态列表", notes = "分页查询店员动态信息列表")
    @ApiResponses({
            @ApiResponse(code = 200, message = "操作成功", response = PlayClerkArticleReturnVo.class, responseContainer = "Page")})
    @PostMapping("/listByPage")
    public R list(@ApiParam(value = "查询条件", required = true) @Validated @RequestBody PlayClerkArticleQueryVo vo) {
        IPage<PlayClerkArticleReturnVo> list = playClerkArticleInfoService.selectByPage(vo, false);
        return R.ok(list);
    }

    /**
     * 修改店员动态信息
     */
    @ApiOperation(value = "修改动态审核状态", notes = "更新店员动态审核状态，通过或拒绝")
    @ApiResponses({@ApiResponse(code = 200, message = "操作成功"), @ApiResponse(code = 500, message = "修改失败")})
    // @PreAuthorize("@customSs.hasPermission('clerk:article:update')")
    @Log(title = "店员动态信息", businessType = BusinessType.UPDATE)
    @PostMapping(value = "/update")
    public R update(@ApiParam(value = "审核状态信息", required = true) @RequestBody PlayClerkArticleReviewStateEditVo vo) {
        PlayClerkArticleInfoEntity entity = ConvertUtil.entityToVo(vo, PlayClerkArticleInfoEntity.class);
        boolean success = playClerkArticleInfoService.update(entity);
        if (success) {
            return R.ok();
        }
        return R.error("修改失败");
    }

    /**
     * 删除店员动态信息
     */
    @ApiOperation(value = "删除动态", notes = "根据ID批量删除店员动态信息")
    @ApiImplicitParam(name = "ids", value = "动态ID数组", required = true, paramType = "path", dataType = "String[]", example = "1,2,3")
    @ApiResponses({@ApiResponse(code = 200, message = "操作成功")})
    // @PreAuthorize("@customSs.hasPermission('clerk:article:delete')")
    @Log(title = "店员动态信息", businessType = BusinessType.DELETE)
    @DeleteMapping("/{ids}")
    public R remove(@PathVariable String[] ids) {
        for (String id : ids) {
            playClerkArticleInfoService.deletePlayClerkArticleInfoById(id);
            playCustomArticleInfoService.deleteByArticleId(id);
        }
        return R.ok();
    }
}


--- File: play-admin/src/main/java/com/starry/admin/modules/clerk/controller/PlayClerkClassificationInfoController.java ---
package com.starry.admin.modules.clerk.controller;

import com.baomidou.mybatisplus.core.metadata.IPage;
import com.starry.admin.modules.clerk.module.entity.PlayClerkClassificationInfoEntity;
import com.starry.admin.modules.clerk.service.IPlayClerkClassificationInfoService;
import com.starry.common.annotation.Log;
import com.starry.common.enums.BusinessType;
import com.starry.common.result.R;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiParam;
import io.swagger.annotations.ApiResponse;
import io.swagger.annotations.ApiResponses;
import java.util.List;
import javax.annotation.Resource;
import org.springframework.web.bind.annotation.*;

/**
 * 店员分类Controller
 *
 * @author admin
 * @since 2024-04-06
 */
@Api(tags = "店员分类管理", description = "店员分类信息管理相关接口，包括查询、新增、修改和删除等操作")
@RestController
@RequestMapping("/clerk/class")
public class PlayClerkClassificationInfoController {
    @Resource
    private IPlayClerkClassificationInfoService playClerkClassificationInfoService;

    /**
     * 查询店员分类列表
     */
    @ApiOperation(value = "查询所有分类", notes = "获取所有店员分类信息列表")
    @ApiResponses({
            @ApiResponse(code = 200, message = "操作成功", response = PlayClerkClassificationInfoEntity.class, responseContainer = "List")})
    @GetMapping("/listAll")
    public R listAll() {
        List<PlayClerkClassificationInfoEntity> list = playClerkClassificationInfoService.selectAll();
        return R.ok(list);
    }

    /**
     * 查询店员分类列表
     */
    @ApiOperation(value = "分页查询分类", notes = "分页查询店员分类信息列表")
    @ApiResponses({
            @ApiResponse(code = 200, message = "操作成功", response = PlayClerkClassificationInfoEntity.class, responseContainer = "Page")})
    @GetMapping("/list")
    public R list(@ApiParam(value = "查询条件") PlayClerkClassificationInfoEntity playClerkClassificationInfo) {
        IPage<PlayClerkClassificationInfoEntity> list = playClerkClassificationInfoService
                .selectPlayClerkClassificationInfoByPage(playClerkClassificationInfo);
        return R.ok(list);
    }

    /**
     * 获取店员分类详细信息
     */
    @ApiOperation(value = "获取分类详情", notes = "根据ID获取店员分类详细信息")
    @ApiImplicitParam(name = "id", value = "分类ID", required = true, paramType = "path", dataType = "String", example = "1")
    @ApiResponses({@ApiResponse(code = 200, message = "操作成功", response = PlayClerkClassificationInfoEntity.class)})
    @GetMapping(value = "/{id}")
    public R getInfo(@PathVariable("id") String id) {
        return R.ok(playClerkClassificationInfoService.selectPlayClerkClassificationInfoById(id));
    }

    /**
     * 新增店员分类
     */
    @ApiOperation(value = "新增分类", notes = "创建新的店员分类信息")
    @ApiResponses({@ApiResponse(code = 200, message = "操作成功"), @ApiResponse(code = 500, message = "添加失败")})
    @Log(title = "店员分类", businessType = BusinessType.INSERT)
    @PostMapping("/create")
    public R create(
            @ApiParam(value = "分类信息", required = true) @RequestBody PlayClerkClassificationInfoEntity playClerkClassificationInfo) {
        boolean success = playClerkClassificationInfoService.create(playClerkClassificationInfo);
        if (success) {
            return R.ok();
        }
        return R.error("添加失败");
    }

    /**
     * 修改店员分类
     */
    @ApiOperation(value = "修改分类", notes = "根据ID修改店员分类信息")
    @ApiImplicitParam(name = "id", value = "分类ID", required = true, paramType = "path", dataType = "String", example = "1")
    @ApiResponses({@ApiResponse(code = 200, message = "操作成功"), @ApiResponse(code = 500, message = "修改失败")})
    @Log(title = "店员分类", businessType = BusinessType.UPDATE)
    @PostMapping(value = "/update/{id}")
    public R update(@PathVariable String id,
            @ApiParam(value = "分类信息", required = true) @RequestBody PlayClerkClassificationInfoEntity playClerkClassificationInfo) {
        playClerkClassificationInfo.setId(id);
        boolean success = playClerkClassificationInfoService.update(playClerkClassificationInfo);
        if (success) {
            return R.ok();
        }
        return R.error("修改失败");
    }

    /**
     * 删除店员分类
     */
    @ApiOperation(value = "删除分类", notes = "根据ID批量删除店员分类信息")
    @ApiImplicitParam(name = "ids", value = "分类ID数组", required = true, paramType = "path", dataType = "String[]", example = "1,2,3")
    @ApiResponses({@ApiResponse(code = 200, message = "操作成功", response = Integer.class)})
    @Log(title = "店员分类", businessType = BusinessType.DELETE)
    @DeleteMapping("/{ids}")
    public R remove(@PathVariable String[] ids) {
        return R.ok(playClerkClassificationInfoService.deletePlayClerkClassificationInfoByIds(ids));
    }
}


--- File: play-admin/src/main/java/com/starry/admin/modules/clerk/controller/PlayClerkCommodityController.java ---
package com.starry.admin.modules.clerk.controller;

import com.baomidou.mybatisplus.core.metadata.IPage;
import com.starry.admin.modules.clerk.module.entity.PlayClerkCommodityEntity;
import com.starry.admin.modules.clerk.module.vo.PlayClerkCommodityQueryVo;
import com.starry.admin.modules.clerk.service.IPlayClerkCommodityService;
import com.starry.common.annotation.Log;
import com.starry.common.enums.BusinessType;
import com.starry.common.result.R;
import com.starry.common.utils.ConvertUtil;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiParam;
import io.swagger.annotations.ApiResponse;
import io.swagger.annotations.ApiResponses;
import java.util.List;
import javax.annotation.Resource;
import org.springframework.web.bind.annotation.*;

/**
 * 陪聊服务项目Controller
 *
 * @author admin
 * @since 2024-03-31
 */
@Api(tags = "陪聊服务项目", description = "陪聊服务项目管理相关接口，包括查询、新增、修改和删除等操作")
@RestController
@RequestMapping("/clerk/commodity")
public class PlayClerkCommodityController {
    @Resource
    private IPlayClerkCommodityService playClerkCommodityService;

    /**
     * 查询陪聊服务项目列表
     */
    @ApiOperation(value = "查询所有服务项目", notes = "获取所有陪聊服务项目列表")
    @ApiResponses({
            @ApiResponse(code = 200, message = "操作成功", response = PlayClerkCommodityQueryVo.class, responseContainer = "List")})
    @GetMapping("/listAllCommodity")
    public R listAllCommodity() {
        List<PlayClerkCommodityEntity> list = playClerkCommodityService.selectAll();
        return R.ok(ConvertUtil.entityToVoList(list, PlayClerkCommodityQueryVo.class));
    }

    /**
     * 查询陪聊服务项目列表
     */
    @ApiOperation(value = "分页查询服务项目", notes = "分页查询陪聊服务项目列表")
    @ApiResponses({
            @ApiResponse(code = 200, message = "操作成功", response = PlayClerkCommodityEntity.class, responseContainer = "Page")})
    @GetMapping("/list")
    public R list(@ApiParam(value = "查询条件") PlayClerkCommodityEntity playClerkCommodity) {
        IPage<PlayClerkCommodityEntity> list = playClerkCommodityService
                .selectPlayClerkCommodityByPage(playClerkCommodity);
        return R.ok(list);
    }

    /**
     * 获取陪聊服务项目详细信息
     */
    @ApiOperation(value = "获取服务项目详情", notes = "根据ID获取陪聊服务项目详细信息")
    @ApiImplicitParam(name = "id", value = "服务项目ID", required = true, paramType = "path", dataType = "String", example = "1")
    @ApiResponses({@ApiResponse(code = 200, message = "操作成功", response = PlayClerkCommodityEntity.class)})
    @GetMapping(value = "/{id}")
    public R getInfo(@PathVariable("id") String id) {
        return R.ok(playClerkCommodityService.selectPlayClerkCommodityById(id));
    }

    /**
     * 新增陪聊服务项目
     */
    @ApiOperation(value = "新增服务项目", notes = "创建新的陪聊服务项目")
    @ApiResponses({@ApiResponse(code = 200, message = "操作成功"), @ApiResponse(code = 500, message = "添加失败")})
    @Log(title = "陪聊服务项目", businessType = BusinessType.INSERT)
    @PostMapping("/create")
    public R create(
            @ApiParam(value = "服务项目信息", required = true) @RequestBody PlayClerkCommodityEntity playClerkCommodity) {
        boolean success = playClerkCommodityService.create(playClerkCommodity);
        if (success) {
            return R.ok();
        }
        return R.error("添加失败");
    }

    /**
     * 修改陪聊服务项目
     */
    @ApiOperation(value = "修改服务项目", notes = "根据ID修改陪聊服务项目信息")
    @ApiImplicitParam(name = "id", value = "服务项目ID", required = true, paramType = "path", dataType = "String", example = "1")
    @ApiResponses({@ApiResponse(code = 200, message = "操作成功"), @ApiResponse(code = 500, message = "修改失败")})
    @Log(title = "陪聊服务项目", businessType = BusinessType.UPDATE)
    @PostMapping(value = "/update/{id}")
    public R update(@PathVariable String id,
            @ApiParam(value = "服务项目信息", required = true) @RequestBody PlayClerkCommodityEntity playClerkCommodity) {
        playClerkCommodity.setId(id);
        boolean success = playClerkCommodityService.update(playClerkCommodity);
        if (success) {
            return R.ok();
        }
        return R.error("修改失败");
    }

    /**
     * 删除陪聊服务项目
     */
    @ApiOperation(value = "删除服务项目", notes = "根据ID批量删除陪聊服务项目")
    @ApiImplicitParam(name = "ids", value = "服务项目ID数组", required = true, paramType = "path", dataType = "String[]", example = "1,2,3")
    @ApiResponses({@ApiResponse(code = 200, message = "操作成功", response = Integer.class)})
    @Log(title = "陪聊服务项目", businessType = BusinessType.DELETE)
    @DeleteMapping("/{ids}")
    public R remove(@PathVariable String[] ids) {
        return R.ok(playClerkCommodityService.deletePlayClerkCommodityByIds(ids));
    }
}


--- File: play-admin/src/main/java/com/starry/admin/modules/clerk/controller/PlayClerkDataReviewInfoController.java ---
package com.starry.admin.modules.clerk.controller;

import com.baomidou.mybatisplus.core.metadata.IPage;
import com.starry.admin.modules.clerk.module.vo.PlayClerkDataReviewQueryVo;
import com.starry.admin.modules.clerk.module.vo.PlayClerkDataReviewReturnVo;
import com.starry.admin.modules.clerk.module.vo.PlayClerkDataReviewStateEditVo;
import com.starry.admin.modules.clerk.service.IPlayClerkDataReviewInfoService;
import com.starry.common.annotation.Log;
import com.starry.common.enums.BusinessType;
import com.starry.common.result.R;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiParam;
import io.swagger.annotations.ApiResponse;
import io.swagger.annotations.ApiResponses;
import javax.annotation.Resource;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;

/**
 * 店员资料审核Controller
 *
 * @author admin
 * @since 2024-05-19
 */
@Api(tags = "店员资料内容审核", description = "店员资料内容审核相关接口，包括昵称、头像、相册、录音等资料的审核")
@RestController
@RequestMapping("/clerk/data/review")
public class PlayClerkDataReviewInfoController {

    @Resource
    private IPlayClerkDataReviewInfoService playClerkDataReviewInfoService;

    /**
     * 查询店员资料审核列表
     */
    @ApiOperation(value = "查询资料审核列表", notes = "分页查询店员资料审核列表")
    @ApiResponses({
            @ApiResponse(code = 200, message = "操作成功", response = PlayClerkDataReviewReturnVo.class, responseContainer = "Page")})
    @PostMapping("/listByPage")
    public R list(@ApiParam(value = "查询条件", required = true) @Validated @RequestBody PlayClerkDataReviewQueryVo vo) {
        IPage<PlayClerkDataReviewReturnVo> list = playClerkDataReviewInfoService.selectByPage(vo);
        return R.ok(list);
    }

    /**
     * 修改店员资料审核
     */
    @ApiOperation(value = "修改资料审核状态", notes = "更新店员资料审核状态，通过或拒绝")
    @ApiResponses({@ApiResponse(code = 200, message = "操作成功")})
    // @PreAuthorize("@customSs.hasPermission('clerk:dataReview:update')")
    @Log(title = "店员资料审核", businessType = BusinessType.UPDATE)
    @PostMapping(value = "/update")
    public R update(
            @ApiParam(value = "审核状态信息", required = true) @Validated @RequestBody PlayClerkDataReviewStateEditVo vo) {
        playClerkDataReviewInfoService.updateDataReviewState(vo);
        return R.ok();
    }

    /**
     * 删除店员资料审核
     */
    @ApiOperation(value = "删除资料审核记录", notes = "根据ID批量删除店员资料审核记录")
    @ApiImplicitParam(name = "ids", value = "审核记录ID数组", required = true, paramType = "path", dataType = "String[]", example = "1,2,3")
    @ApiResponses({@ApiResponse(code = 200, message = "操作成功", response = Integer.class)})
    // @PreAuthorize("@customSs.hasPermission('clerk:dataReview:delete')")
    @Log(title = "店员资料审核", businessType = BusinessType.DELETE)
    @DeleteMapping("/{ids}")
    public R remove(@PathVariable String[] ids) {
        return R.ok(playClerkDataReviewInfoService.deletePlayClerkDataReviewInfoByIds(ids));
    }
}


--- File: play-admin/src/main/java/com/starry/admin/modules/clerk/controller/PlayClerkLevelInfoController.java ---
package com.starry.admin.modules.clerk.controller;

import com.starry.admin.common.exception.CustomException;
import com.starry.admin.modules.clerk.module.entity.PlayClerkLevelInfoEntity;
import com.starry.admin.modules.clerk.module.vo.PlayClerkLevelAddVo;
import com.starry.admin.modules.clerk.module.vo.PlayClerkLevelEditVo;
import com.starry.admin.modules.clerk.service.IPlayClerkLevelInfoService;
import com.starry.common.annotation.Log;
import com.starry.common.enums.BusinessType;
import com.starry.common.result.R;
import com.starry.common.utils.ConvertUtil;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiParam;
import io.swagger.annotations.ApiResponse;
import io.swagger.annotations.ApiResponses;
import javax.annotation.Resource;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;

/**
 * 店员等级Controller
 *
 * @author admin
 * @since 2024-03-30
 */
@Api(tags = "店员等级管理", description = "店员等级信息管理相关接口，包括查询、新增、修改和删除等操作")
@RestController
@RequestMapping("/clerk/level")
public class PlayClerkLevelInfoController {
    @Resource
    private IPlayClerkLevelInfoService playClerkLevelInfoService;

    /**
     * 查询店员等级列表
     */
    @ApiOperation(value = "查询所有等级", notes = "获取所有店员等级信息列表")
    @ApiResponses({
            @ApiResponse(code = 200, message = "操作成功", response = PlayClerkLevelInfoEntity.class, responseContainer = "List")})
    @GetMapping("/listAll")
    public R listAll() {
        return R.ok(playClerkLevelInfoService.selectAll());
    }

    /**
     * 获取店员等级详细信息
     */
    @ApiOperation(value = "获取等级详情", notes = "根据ID获取店员等级详细信息")
    @ApiImplicitParam(name = "id", value = "等级ID", required = true, paramType = "path", dataType = "String", example = "1")
    @ApiResponses({@ApiResponse(code = 200, message = "操作成功", response = PlayClerkLevelInfoEntity.class)})
    @GetMapping(value = "/{id}")
    public R getInfo(@PathVariable("id") String id) {
        return R.ok(playClerkLevelInfoService.selectPlayClerkLevelInfoById(id));
    }

    /**
     * 新增店员等级
     */
    @ApiOperation(value = "新增等级", notes = "创建新的店员等级信息")
    @ApiResponses({@ApiResponse(code = 200, message = "操作成功"), @ApiResponse(code = 500, message = "添加失败")})
    @Log(title = "店员等级", businessType = BusinessType.INSERT)
    @PostMapping("/create")
    public R create(@ApiParam(value = "等级信息", required = true) @RequestBody PlayClerkLevelAddVo vo) {
        PlayClerkLevelInfoEntity entity = ConvertUtil.entityToVo(vo, PlayClerkLevelInfoEntity.class);
        int level = playClerkLevelInfoService.selectMaxLevel();
        entity.setLevel(level + 1);
        boolean success = playClerkLevelInfoService.create(entity);
        if (success) {
            return R.ok();
        }
        return R.error("添加失败");
    }

    /**
     * 修改店员等级
     */
    @ApiOperation(value = "修改等级", notes = "修改店员等级信息")
    @ApiResponses({@ApiResponse(code = 200, message = "操作成功"), @ApiResponse(code = 500, message = "修改失败")})
    @Log(title = "店员等级", businessType = BusinessType.UPDATE)
    @PostMapping(value = "/update")
    public R update(@ApiParam(value = "等级信息", required = true) @Validated @RequestBody PlayClerkLevelEditVo vo) {
        PlayClerkLevelInfoEntity entity = ConvertUtil.entityToVo(vo, PlayClerkLevelInfoEntity.class);
        boolean success = playClerkLevelInfoService.update(entity);
        if (success) {
            return R.ok();
        }
        return R.error("修改失败");
    }

    /**
     * 删除店员等级
     */
    @ApiOperation(value = "删除最高等级", notes = "删除系统中最高的店员等级")
    @ApiResponses({@ApiResponse(code = 200, message = "操作成功"), @ApiResponse(code = 500, message = "最后一级,不允许删除")})
    @Log(title = "店员等级", businessType = BusinessType.DELETE)
    @DeleteMapping("delMaxLevel")
    public R remove() {
        int level = playClerkLevelInfoService.selectMaxLevel();
        if (level <= 1) {
            throw new CustomException("最后一级,不允许删除");
        }
        playClerkLevelInfoService.delMaxLevelByLevel(level);
        return R.ok();
    }
}


--- File: play-admin/src/main/java/com/starry/admin/modules/clerk/controller/PlayClerkPkController.java ---
package com.starry.admin.modules.clerk.controller;

import com.baomidou.mybatisplus.core.metadata.IPage;
import com.starry.admin.modules.clerk.module.entity.PlayClerkPkEntity;
import com.starry.admin.modules.clerk.service.IPlayClerkPkService;
import com.starry.common.annotation.Log;
import com.starry.common.enums.BusinessType;
import com.starry.common.result.R;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiParam;
import io.swagger.annotations.ApiResponse;
import io.swagger.annotations.ApiResponses;
import javax.annotation.Resource;
import org.springframework.web.bind.annotation.*;

/**
 * 店员pkController
 *
 * @author admin
 * @since 2024-08-02
 */
@Api(tags = "店员PK管理", description = "店员PK信息管理相关接口，包括查询、新增、修改和删除等操作")
@RestController
@RequestMapping("/play/pk")
public class PlayClerkPkController {
    @Resource
    private IPlayClerkPkService playClerkPkService;

    /**
     * 查询店员pk列表
     */
    @ApiOperation(value = "分页查询PK列表", notes = "分页查询店员PK信息列表")
    @ApiResponses({
            @ApiResponse(code = 200, message = "操作成功", response = PlayClerkPkEntity.class, responseContainer = "Page")})
    @GetMapping("/list")
    public R list(@ApiParam(value = "查询条件") PlayClerkPkEntity playClerkPk) {
        IPage<PlayClerkPkEntity> list = playClerkPkService.selectPlayClerkPkByPage(playClerkPk);
        return R.ok(list);
    }

    /**
     * 获取店员pk详细信息
     */
    @ApiOperation(value = "获取PK详情", notes = "根据ID获取店员PK详细信息")
    @ApiImplicitParam(name = "id", value = "PK记录ID", required = true, paramType = "path", dataType = "String", example = "1")
    @ApiResponses({@ApiResponse(code = 200, message = "操作成功", response = PlayClerkPkEntity.class)})
    @GetMapping(value = "/{id}")
    public R getInfo(@PathVariable("id") String id) {
        return R.ok(playClerkPkService.selectPlayClerkPkById(id));
    }

    /**
     * 新增店员pk
     */
    @ApiOperation(value = "新增PK记录", notes = "创建新的店员PK信息")
    @ApiResponses({@ApiResponse(code = 200, message = "操作成功"), @ApiResponse(code = 500, message = "添加失败")})
    @Log(title = "店员pk", businessType = BusinessType.INSERT)
    @PostMapping("/create")
    public R create(@ApiParam(value = "PK信息", required = true) @RequestBody PlayClerkPkEntity playClerkPk) {
        boolean success = playClerkPkService.create(playClerkPk);
        if (success) {
            return R.ok();
        }
        return R.error("添加失败");
    }

    /**
     * 修改店员pk
     */
    @ApiOperation(value = "修改PK记录", notes = "根据ID修改店员PK信息")
    @ApiImplicitParam(name = "id", value = "PK记录ID", required = true, paramType = "path", dataType = "String", example = "1")
    @ApiResponses({@ApiResponse(code = 200, message = "操作成功"), @ApiResponse(code = 500, message = "修改失败")})
    @Log(title = "店员pk", businessType = BusinessType.UPDATE)
    @PostMapping(value = "/update/{id}")
    public R update(@PathVariable String id,
            @ApiParam(value = "PK信息", required = true) @RequestBody PlayClerkPkEntity playClerkPk) {
        playClerkPk.setId(id);
        boolean success = playClerkPkService.update(playClerkPk);
        if (success) {
            return R.ok();
        }
        return R.error("修改失败");
    }

    /**
     * 删除店员pk
     */
    @ApiOperation(value = "删除PK记录", notes = "根据ID批量删除店员PK信息")
    @ApiImplicitParam(name = "ids", value = "PK记录ID数组", required = true, paramType = "path", dataType = "String[]", example = "1,2,3")
    @ApiResponses({@ApiResponse(code = 200, message = "操作成功", response = Integer.class)})
    @Log(title = "店员pk", businessType = BusinessType.DELETE)
    @DeleteMapping("/{ids}")
    public R remove(@PathVariable String[] ids) {
        return R.ok(playClerkPkService.deletePlayClerkPkByIds(ids));
    }
}


--- File: play-admin/src/main/java/com/starry/admin/modules/clerk/controller/PlayClerkRankingInfoController.java ---
package com.starry.admin.modules.clerk.controller;

import com.baomidou.mybatisplus.core.metadata.IPage;
import com.starry.admin.modules.clerk.module.entity.IPlayClerkRankingInfoQueryVo;
import com.starry.admin.modules.clerk.module.entity.IPlayClerkRankingInfoReturnVo;
import com.starry.admin.modules.clerk.module.entity.PlayClerkRankingInfoEntity;
import com.starry.admin.modules.clerk.service.IPlayClerkRankingInfoService;
import com.starry.common.annotation.Log;
import com.starry.common.enums.BusinessType;
import com.starry.common.result.R;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiParam;
import io.swagger.annotations.ApiResponse;
import io.swagger.annotations.ApiResponses;
import javax.annotation.Resource;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;

/**
 * 店员排行Controller
 *
 * @author admin
 * @since 2024-05-25
 */
@Api(tags = "店员排行管理", description = "店员排行榜信息管理相关接口，包括查询、新增、修改和删除等操作")
@RestController
@RequestMapping("/clerk/ranking")
public class PlayClerkRankingInfoController {
    @Resource
    private IPlayClerkRankingInfoService playClerkRankingInfoService;

    /**
     * 查询店员排行列表
     */
    @ApiOperation(value = "查询排行列表", notes = "分页查询店员排行信息列表")
    @ApiResponses({
            @ApiResponse(code = 200, message = "操作成功", response = IPlayClerkRankingInfoReturnVo.class, responseContainer = "Page")})
    @PostMapping("/listByPage")
    public R list(@ApiParam(value = "查询条件", required = true) @Validated @RequestBody IPlayClerkRankingInfoQueryVo vo) {
        IPage<IPlayClerkRankingInfoReturnVo> list = playClerkRankingInfoService.selectByPage(vo);
        return R.ok(list);
    }

    /**
     * 获取店员排行详细信息
     */
    @ApiOperation(value = "获取排行详情", notes = "根据ID获取店员排行详细信息")
    @ApiImplicitParam(name = "id", value = "排行ID", required = true, paramType = "path", dataType = "String", example = "1")
    @ApiResponses({@ApiResponse(code = 200, message = "操作成功", response = PlayClerkRankingInfoEntity.class)})
    @GetMapping(value = "/{id}")
    public R getInfo(@PathVariable("id") String id) {
        return R.ok(playClerkRankingInfoService.selectPlayClerkRankingInfoById(id));
    }

    /**
     * 新增店员排行
     */
    @ApiOperation(value = "新增排行", notes = "创建新的店员排行记录")
    @ApiResponses({@ApiResponse(code = 200, message = "操作成功"), @ApiResponse(code = 500, message = "添加失败")})
    // @PreAuthorize("@customSs.hasPermission('clerk:ranking:add')")
    @Log(title = "店员排行", businessType = BusinessType.INSERT)
    @PostMapping("/create")
    public R create(
            @ApiParam(value = "排行信息", required = true) @RequestBody PlayClerkRankingInfoEntity playClerkRankingInfo) {
        boolean success = playClerkRankingInfoService.create(playClerkRankingInfo);
        if (success) {
            return R.ok();
        }
        return R.error("添加失败");
    }

    /**
     * 修改店员排行
     */
    @ApiOperation(value = "修改排行", notes = "根据ID修改店员排行信息")
    @ApiImplicitParam(name = "id", value = "排行ID", required = true, paramType = "path", dataType = "String", example = "1")
    @ApiResponses({@ApiResponse(code = 200, message = "操作成功"), @ApiResponse(code = 500, message = "修改失败")})
    // @PreAuthorize("@customSs.hasPermission('clerk:ranking:update')")
    @Log(title = "店员排行", businessType = BusinessType.UPDATE)
    @PostMapping(value = "/update/{id}")
    public R update(@PathVariable String id,
            @ApiParam(value = "排行信息", required = true) @RequestBody PlayClerkRankingInfoEntity playClerkRankingInfo) {
        playClerkRankingInfo.setId(id);
        boolean success = playClerkRankingInfoService.update(playClerkRankingInfo);
        if (success) {
            return R.ok();
        }
        return R.error("修改失败");
    }

    /**
     * 删除店员排行
     */
    @ApiOperation(value = "删除排行", notes = "根据ID批量删除店员排行信息")
    @ApiImplicitParam(name = "ids", value = "排行ID数组", required = true, paramType = "path", dataType = "String[]", example = "1,2,3")
    @ApiResponses({@ApiResponse(code = 200, message = "操作成功", response = Integer.class)})
    // @PreAuthorize("@customSs.hasPermission('clerk:ranking:delete')")
    @Log(title = "店员排行", businessType = BusinessType.DELETE)
    @DeleteMapping("/{ids}")
    public R remove(@PathVariable String[] ids) {
        return R.ok(playClerkRankingInfoService.deletePlayClerkRankingInfoByIds(ids));
    }
}


--- File: play-admin/src/main/java/com/starry/admin/modules/clerk/controller/PlayClerkTypeInfoController.java ---
package com.starry.admin.modules.clerk.controller;

import com.starry.admin.modules.clerk.module.entity.PlayClerkTypeInfoEntity;
import com.starry.admin.modules.clerk.module.vo.PlayClerkTypeInfoAddVo;
import com.starry.admin.modules.clerk.module.vo.PlayClerkTypeInfoQueryVo;
import com.starry.admin.modules.clerk.service.IPlayClerkTypeInfoService;
import com.starry.common.annotation.Log;
import com.starry.common.enums.BusinessType;
import com.starry.common.result.R;
import com.starry.common.utils.ConvertUtil;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiParam;
import io.swagger.annotations.ApiResponse;
import io.swagger.annotations.ApiResponses;
import javax.annotation.Resource;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;

/**
 * 店员分类信息Controller
 *
 * @author admin
 * @since 2024-05-31
 */
@Api(tags = "店员分类管理", description = "店员分类信息管理相关接口，包括查询、新增、修改和删除等操作")
@RestController
@RequestMapping("/clerk/type")
public class PlayClerkTypeInfoController {
    @Resource
    private IPlayClerkTypeInfoService playClerkTypeInfoService;

    /**
     * 查询店员分类信息列表
     */
    @ApiOperation(value = "查询所有分类", notes = "获取所有店员分类信息列表")
    @ApiResponses({
            @ApiResponse(code = 200, message = "操作成功", response = PlayClerkTypeInfoEntity.class, responseContainer = "List")})
    @GetMapping("/listByAll")
    public R listByAll() {
        return R.ok(playClerkTypeInfoService.selectByAll());
    }

    @ApiOperation(value = "分页查询分类", notes = "分页查询店员分类信息列表")
    @ApiResponses({
            @ApiResponse(code = 200, message = "操作成功", response = PlayClerkTypeInfoEntity.class, responseContainer = "Page")})
    @PostMapping("/listByPage")
    public R listByPage(
            @ApiParam(value = "查询条件", required = true) @Validated @RequestBody PlayClerkTypeInfoQueryVo vo) {
        return R.ok(playClerkTypeInfoService.selectByPage(vo));
    }

    /**
     *
     * /** 新增店员分类信息
     */
    @ApiOperation(value = "新增分类", notes = "创建新的店员分类信息")
    @ApiResponses({@ApiResponse(code = 200, message = "操作成功"), @ApiResponse(code = 500, message = "添加失败")})
    @Log(title = "店员分类信息", businessType = BusinessType.INSERT)
    @PostMapping("/create")
    public R create(@ApiParam(value = "分类信息", required = true) @Validated @RequestBody PlayClerkTypeInfoAddVo vo) {
        boolean success = playClerkTypeInfoService.create(ConvertUtil.entityToVo(vo, PlayClerkTypeInfoEntity.class));
        if (success) {
            return R.ok();
        }
        return R.error("添加失败");
    }

    /**
     * 修改店员分类信息
     */
    @ApiOperation(value = "修改分类", notes = "根据ID修改店员分类信息")
    @ApiImplicitParam(name = "id", value = "分类ID", required = true, paramType = "path", dataType = "String", example = "1")
    @ApiResponses({@ApiResponse(code = 200, message = "操作成功"), @ApiResponse(code = 500, message = "修改失败")})
    @Log(title = "店员分类信息", businessType = BusinessType.UPDATE)
    @PostMapping(value = "/update/{id}")
    public R update(@PathVariable String id,
            @ApiParam(value = "分类信息", required = true) @RequestBody PlayClerkTypeInfoEntity playClerkTypeInfo) {
        playClerkTypeInfo.setId(id);
        boolean success = playClerkTypeInfoService.update(playClerkTypeInfo);
        if (success) {
            return R.ok();
        }
        return R.error("修改失败");
    }

    /**
     * 删除店员分类信息
     */
    @ApiOperation(value = "删除分类", notes = "根据ID批量删除店员分类信息")
    @ApiImplicitParam(name = "ids", value = "分类ID数组", required = true, paramType = "path", dataType = "String[]", example = "1,2,3")
    @ApiResponses({@ApiResponse(code = 200, message = "操作成功", response = Integer.class)})
    @Log(title = "店员分类信息", businessType = BusinessType.DELETE)
    @DeleteMapping("/{ids}")
    public R remove(@PathVariable String[] ids) {
        return R.ok(playClerkTypeInfoService.deletePlayClerkTypeInfoByIds(ids));
    }
}


--- File: play-admin/src/main/java/com/starry/admin/modules/clerk/controller/PlayClerkUserInfoController.java ---
package com.starry.admin.modules.clerk.controller;

import com.alibaba.fastjson2.JSONObject;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.starry.admin.modules.clerk.module.entity.PlayClerkUserInfoEntity;
import com.starry.admin.modules.clerk.module.entity.PlayClerkUserQueryVo;
import com.starry.admin.modules.clerk.module.entity.PlayClerkUserReturnVo;
import com.starry.admin.modules.clerk.module.entity.PlayClerkUserTypeEditVo;
import com.starry.admin.modules.clerk.module.vo.PlayClerkUserEditVo;
import com.starry.admin.modules.clerk.module.vo.PlayClerkUserStateEditVo;
import com.starry.admin.modules.clerk.service.IPlayClerkUserInfoService;
import com.starry.admin.modules.custom.module.entity.PlayCustomUserInfoEntity;
import com.starry.admin.modules.custom.service.IPlayCustomUserInfoService;
import com.starry.admin.modules.weichat.entity.clerk.PlayClerkUserInfoQueryVo;
import com.starry.admin.modules.weichat.entity.clerk.PlayClerkUserInfoResultVo;
import com.starry.common.annotation.Log;
import com.starry.common.enums.BusinessType;
import com.starry.common.result.R;
import com.starry.common.utils.ConvertUtil;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiParam;
import io.swagger.annotations.ApiResponse;
import io.swagger.annotations.ApiResponses;
import java.util.List;
import javax.annotation.Resource;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;

/**
 * 店员Controller
 *
 * @author admin
 * @since 2024-03-30
 */
@Api(tags = "店员管理", description = "店员信息管理相关接口，包括店员的增删改查、分类、分组以及状态管理等功能")
@RestController
@RequestMapping("/clerk/user")
public class PlayClerkUserInfoController {

    @Resource
    private IPlayCustomUserInfoService customUserInfoService;
    @Resource
    private IPlayClerkUserInfoService playClerkUserInfoService;

    /**
     * 查询店员列表
     */
    @ApiOperation(value = "分页查询店员列表", notes = "根据条件分页查询店员列表信息，支持多种筛选条件")
    @ApiResponses({
            @ApiResponse(code = 200, message = "操作成功", response = PlayClerkUserReturnVo.class, responseContainer = "Page")})
    @PostMapping("listByPage")
    public R listByPage(@ApiParam(value = "查询条件", required = true) @Validated @RequestBody PlayClerkUserQueryVo vo) {
        IPage<PlayClerkUserReturnVo> list = playClerkUserInfoService.selectByPage(vo);
        return R.ok(list);
    }

    /**
     * 按照店员分类查询店员列表
     */
    @ApiOperation(value = "按分类查询店员列表", notes = "根据店员分类ID查询所有属于该分类的店员列表")
    @ApiImplicitParam(name = "id", value = "店员分类ID", required = true, paramType = "query", dataType = "String", example = "1")
    @ApiResponses({
            @ApiResponse(code = 200, message = "操作成功", response = PlayClerkUserInfoEntity.class, responseContainer = "List")})
    @GetMapping("listAllByTypeId")
    public R listAllByTypeId(@RequestParam("id") String id) {
        List<PlayClerkUserInfoEntity> list = playClerkUserInfoService.listAllByTypeId(id);
        return R.ok(list);
    }

    /**
     * 按照店员分组查询店员列表
     */
    @ApiOperation(value = "按分组查询店员列表", notes = "根据店员分组ID查询所有属于该分组的店员列表")
    @ApiImplicitParam(name = "id", value = "店员分组ID", required = true, paramType = "query", dataType = "String", example = "1")
    @ApiResponses({
            @ApiResponse(code = 200, message = "操作成功", response = PlayClerkUserInfoEntity.class, responseContainer = "List")})
    @GetMapping("listAllByGroupId")
    public R listAllByGroupId(@RequestParam("id") String id) {
        List<PlayClerkUserInfoEntity> list = playClerkUserInfoService.listAllByGroupId(id);
        return R.ok(list);
    }

    /**
     * 修改店员分类
     */
    @ApiOperation(value = "修改店员分类", notes = "将指定店员分配到指定分类中，会先清空该分类下所有店员，再重新分配")
    @ApiResponses({@ApiResponse(code = 200, message = "操作成功", response = String.class)})
    @PostMapping("editClerkType")
    public R listByPage(
            @ApiParam(value = "店员分类修改参数", required = true) @Validated @RequestBody PlayClerkUserTypeEditVo vo) {
        // 先清空当前分类下店员
        List<PlayClerkUserInfoEntity> list = playClerkUserInfoService.listAllByTypeId(vo.getTypeId());
        for (PlayClerkUserInfoEntity clerkUserInfo : list) {
            PlayClerkUserInfoEntity entity = new PlayClerkUserInfoEntity();
            entity.setId(clerkUserInfo.getId());
            entity.setTypeId("");
            playClerkUserInfoService.update(entity);
        }
        for (String clerkUserId : vo.getClerkUserIds()) {
            PlayClerkUserInfoEntity entity = new PlayClerkUserInfoEntity();
            entity.setId(clerkUserId);
            entity.setTypeId(vo.getTypeId());
            playClerkUserInfoService.update(entity);
        }

        return R.ok("成功");
    }

    /**
     * 1、修改店员分组 2、将对应账号角色ID设为组长
     */
    @ApiOperation(value = "修改店员分组", notes = "将指定店员分配到指定分组中，会先清空该分组下所有店员，再重新分配，并可设置组长")
    @ApiResponses({@ApiResponse(code = 200, message = "操作成功", response = String.class)})
    @PostMapping("editClerkGroup")
    public R editClerkGroup(
            @ApiParam(value = "店员分组修改参数", required = true) @Validated @RequestBody PlayClerkUserTypeEditVo vo) {
        // 先清空当前分类下店员
        List<PlayClerkUserInfoEntity> list = playClerkUserInfoService.listAllByGroupId(vo.getTypeId());
        for (PlayClerkUserInfoEntity clerkUserInfo : list) {
            PlayClerkUserInfoEntity entity = new PlayClerkUserInfoEntity();
            entity.setId(clerkUserInfo.getId());
            entity.setGroupId("");
            playClerkUserInfoService.update(entity);
        }
        for (String clerkUserId : vo.getClerkUserIds()) {
            PlayClerkUserInfoEntity entity = new PlayClerkUserInfoEntity();
            entity.setId(clerkUserId);
            entity.setGroupId(vo.getTypeId());
            playClerkUserInfoService.update(entity);
        }

        return R.ok("成功");
    }

    @ApiOperation(value = "获取简单列表", notes = "获取店员和客户的简单列表信息，用于下拉选择等场景")
    @ApiResponses({@ApiResponse(code = 200, message = "操作成功", response = JSONObject.class)})
    @GetMapping("/simple/list")
    public R simpleList() {
        List<PlayClerkUserInfoEntity> clerkList = playClerkUserInfoService.simpleList();
        List<PlayCustomUserInfoEntity> customerList = customUserInfoService.simpleList();
        return R.ok(new JSONObject().fluentPut("clerkList", clerkList).fluentPut("customerList", customerList));
    }

    /**
     * 查询店员列表
     */
    @ApiOperation(value = "查询店员列表", notes = "分页查询店员信息列表，支持多种条件筛选，返回详细的店员信息")
    @ApiResponses({
            @ApiResponse(code = 200, message = "操作成功", response = PlayClerkUserInfoResultVo.class, responseContainer = "Page")})
    @GetMapping("/list")
    public R list(@ApiParam(value = "查询条件") PlayClerkUserInfoQueryVo vo) {
        IPage<PlayClerkUserInfoResultVo> list = playClerkUserInfoService.selectPlayClerkUserInfoByPage(vo);
        return R.ok(list);
    }

    /**
     * 获取店员详细信息
     */
    @ApiOperation(value = "获取店员详情", notes = "根据店员ID获取店员的详细信息")
    @ApiImplicitParam(name = "id", value = "店员ID", required = true, paramType = "path", dataType = "String", example = "1")
    @ApiResponses({@ApiResponse(code = 200, message = "操作成功", response = PlayClerkUserInfoEntity.class)})
    @GetMapping(value = "/{id}")
    public R getInfo(@PathVariable("id") String id) {
        return R.ok(playClerkUserInfoService.selectById(id));
    }

    //
    // /**
    // * 微信端口新增店员
    // */
    // @Log(title = "店员", businessType = BusinessType.INSERT)
    // @PostMapping("/wx/add")
    // public R add(@Validated @RequestBody PlayClerkUserAddToWxVo vo) {
    // //微信申请成为店员，需要先创建账户。
    // PlayUserInfoEntity userInfoEntity = ConvertUtil.entityToVo(vo,
    // PlayUserInfoEntity.class);
    // userInfoEntity.setId(IdUtils.getUuid());
    // playUserInfoService.create(userInfoEntity);
    // //账号创建完成后，创建店员
    // PlayClerkUserInfoEntity clerkUserInfoEntity = ConvertUtil.entityToVo(vo,
    // PlayClerkUserInfoEntity.class);
    // clerkUserInfoEntity.setPlayUserId(userInfoEntity.getId());
    // boolean success = playClerkUserInfoService.create(clerkUserInfoEntity);
    // if (success) {
    // clerkCommodityService.initClerkCommodity(userInfoEntity.getId());
    // return R.ok();
    // }
    // return R.error("添加失败");
    // }

    // /**
    // * 新增店员
    // */
    // @Log(title = "店员", businessType = BusinessType.INSERT)
    // @PostMapping("/create")
    // public R create(@Validated @RequestBody PlayClerkUserAddVo vo) {
    // PlayClerkUserInfoEntity entity = ConvertUtil.entityToVo(vo,
    // PlayClerkUserInfoEntity.class);
    // boolean success = playClerkUserInfoService.create(entity);
    // if (success) {
    // clerkCommodityService.initClerkCommodity(vo.getPlayUserId());
    // return R.ok();
    // }
    // return R.error("添加失败");
    // }

    /**
     * 修改店员
     */
    // @PreAuthorize("@customSs.hasPermission('clerk:user:update')")
    @Log(title = "店员", businessType = BusinessType.UPDATE)
    @ApiOperation(value = "修改店员信息", notes = "修改店员的基本信息，如昵称、头像、签名等")
    @ApiResponses({@ApiResponse(code = 200, message = "操作成功"), @ApiResponse(code = 500, message = "修改失败")})
    @PostMapping(value = "/update")
    public R update(@ApiParam(value = "店员信息", required = true) @Validated @RequestBody PlayClerkUserEditVo vo) {
        PlayClerkUserInfoEntity entity = ConvertUtil.entityToVo(vo, PlayClerkUserInfoEntity.class);
        boolean success = playClerkUserInfoService.update(entity);
        if (success) {
            return R.ok();
        }
        return R.error("修改失败");
    }

    /**
     * 修改店员状态
     */
    // @PreAuthorize("@customSs.hasPermission('clerk:user:update')")
    @Log(title = "店员", businessType = BusinessType.UPDATE)
    @ApiOperation(value = "修改店员状态", notes = "修改店员的状态信息，如上下架状态、推荐状态、在线状态等")
    @ApiResponses({@ApiResponse(code = 200, message = "操作成功"), @ApiResponse(code = 500, message = "修改失败")})
    @PostMapping(value = "/updateState")
    public R updateState(
            @ApiParam(value = "店员状态信息", required = true) @Validated @RequestBody PlayClerkUserStateEditVo vo) {
        PlayClerkUserInfoEntity entity = ConvertUtil.entityToVo(vo, PlayClerkUserInfoEntity.class);
        boolean success = playClerkUserInfoService.update(entity);
        if (success) {
            return R.ok();
        }
        return R.error("修改失败");
    }

    /**
     * 删除店员
     */
    // @PreAuthorize("@customSs.hasPermission('clerk:user:remove')")
    @Log(title = "店员", businessType = BusinessType.DELETE)
    @ApiOperation(value = "删除店员", notes = "根据ID批量删除店员信息")
    @ApiImplicitParam(name = "ids", value = "店员ID数组", required = true, paramType = "path", dataType = "String[]", example = "1,2,3")
    @ApiResponses({@ApiResponse(code = 200, message = "操作成功", response = Integer.class)})
    @DeleteMapping("/{ids}")
    public R remove(@PathVariable String[] ids) {
        return R.ok(playClerkUserInfoService.deletePlayClerkUserInfoByIds(ids));
    }
}


--- File: play-admin/src/main/java/com/starry/admin/modules/clerk/controller/PlayClerkUserReviewInfoController.java ---
package com.starry.admin.modules.clerk.controller;

import com.baomidou.mybatisplus.core.metadata.IPage;
import com.starry.admin.modules.clerk.module.vo.PlayClerkUserReviewQueryVo;
import com.starry.admin.modules.clerk.module.vo.PlayClerkUserReviewReturnVo;
import com.starry.admin.modules.clerk.module.vo.PlayClerkUserReviewStateEditVo;
import com.starry.admin.modules.clerk.service.IPlayClerkUserReviewInfoService;
import com.starry.common.annotation.Log;
import com.starry.common.enums.BusinessType;
import com.starry.common.result.R;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiParam;
import io.swagger.annotations.ApiResponse;
import io.swagger.annotations.ApiResponses;
import javax.annotation.Resource;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;

/**
 * 店员资料审核Controller
 *
 * @author admin
 * @since 2024-05-19
 */
@Api(tags = "店员资料审核", description = "店员资料审核相关接口，包括审核列表查询、审核状态修改和删除等操作")
@RestController
@RequestMapping("/clerk/user/review")
public class PlayClerkUserReviewInfoController {
    @Resource
    private IPlayClerkUserReviewInfoService playClerkUserReviewInfoService;

    /**
     * 查询店员资料审核列表
     */
    @ApiOperation(value = "查询审核列表", notes = "分页查询店员资料审核列表")
    @ApiResponses({
            @ApiResponse(code = 200, message = "操作成功", response = PlayClerkUserReviewReturnVo.class, responseContainer = "Page")})
    @PostMapping("/listByPage")
    public R listByPage(
            @ApiParam(value = "查询条件", required = true) @Validated @RequestBody PlayClerkUserReviewQueryVo vo) {
        IPage<PlayClerkUserReviewReturnVo> list = playClerkUserReviewInfoService.selectByPage(vo);
        return R.ok(list);
    }

    /**
     * 修改店员资料审核
     */
    @ApiOperation(value = "修改审核状态", notes = "更新店员资料审核状态，通过或拒绝")
    @ApiResponses({@ApiResponse(code = 200, message = "修改成功")})
    // @PreAuthorize("@customSs.hasPermission('clerk:userReview:update')")
    @Log(title = "店员资料审核", businessType = BusinessType.UPDATE)
    @PostMapping(value = "/update")
    public R update(
            @ApiParam(value = "审核状态信息", required = true) @Validated @RequestBody PlayClerkUserReviewStateEditVo vo) {
        playClerkUserReviewInfoService.updateDataReviewState(vo);
        return R.ok("修改成功");
    }

    /**
     * 删除店员资料审核
     */
    @ApiOperation(value = "删除审核记录", notes = "根据ID批量删除店员资料审核记录")
    @ApiImplicitParam(name = "ids", value = "审核记录ID数组", required = true, paramType = "path", dataType = "String[]", example = "1,2,3")
    @ApiResponses({@ApiResponse(code = 200, message = "操作成功", response = Integer.class)})
    // @PreAuthorize("@customSs.hasPermission('clerk:userReview:delete')")
    @Log(title = "店员资料审核", businessType = BusinessType.DELETE)
    @DeleteMapping("/{ids}")
    public R remove(@PathVariable String[] ids) {
        return R.ok(playClerkUserReviewInfoService.deletePlayClerkUserReviewInfoByIds(ids));
    }
}


--- File: play-admin/src/main/java/com/starry/admin/modules/clerk/controller/PlayClerkWagesDetailsInfoController.java ---
package com.starry.admin.modules.clerk.controller;

import com.baomidou.mybatisplus.core.metadata.IPage;
import com.starry.admin.modules.clerk.module.entity.PlayClerkWagesDetailsInfoEntity;
import com.starry.admin.modules.clerk.service.IPlayClerkWagesDetailsInfoService;
import com.starry.common.annotation.Log;
import com.starry.common.enums.BusinessType;
import com.starry.common.result.R;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiParam;
import io.swagger.annotations.ApiResponse;
import io.swagger.annotations.ApiResponses;
import javax.annotation.Resource;
import org.springframework.web.bind.annotation.*;

/**
 * 店员工资明细信息Controller
 *
 * @author admin
 * @since 2024-05-31
 */
@Api(tags = "店员工资明细", description = "店员工资明细信息管理相关接口，包括查询、新增、修改和删除等操作")
@RestController
@RequestMapping("/wages/details")
public class PlayClerkWagesDetailsInfoController {
    @Resource
    private IPlayClerkWagesDetailsInfoService playClerkWagesDetailsInfoService;

    /**
     * 查询店员工资明细信息列表
     */
    @ApiOperation(value = "查询工资明细列表", notes = "分页查询店员工资明细信息列表")
    @ApiResponses({
            @ApiResponse(code = 200, message = "操作成功", response = PlayClerkWagesDetailsInfoEntity.class, responseContainer = "Page")})
    @GetMapping("/list")
    public R list(@ApiParam(value = "查询条件") PlayClerkWagesDetailsInfoEntity playClerkWagesDetailsInfo) {
        IPage<PlayClerkWagesDetailsInfoEntity> list = playClerkWagesDetailsInfoService
                .selectPlayClerkWagesDetailsInfoByPage(playClerkWagesDetailsInfo);
        return R.ok(list);
    }

    /**
     * 获取店员工资明细信息详细信息
     */
    @ApiOperation(value = "获取工资明细详情", notes = "根据ID获取店员工资明细详细信息")
    @ApiImplicitParam(name = "id", value = "工资明细ID", required = true, paramType = "path", dataType = "String", example = "1")
    @ApiResponses({@ApiResponse(code = 200, message = "操作成功", response = PlayClerkWagesDetailsInfoEntity.class)})
    @GetMapping(value = "/{id}")
    public R getInfo(@PathVariable("id") String id) {
        return R.ok(playClerkWagesDetailsInfoService.selectPlayClerkWagesDetailsInfoById(id));
    }

    /**
     * 新增店员工资明细信息
     */
    @ApiOperation(value = "新增工资明细", notes = "创建新的店员工资明细记录")
    @ApiResponses({@ApiResponse(code = 200, message = "操作成功"), @ApiResponse(code = 500, message = "添加失败")})
    @Log(title = "店员工资明细信息", businessType = BusinessType.INSERT)
    @PostMapping("/create")
    public R create(
            @ApiParam(value = "工资明细信息", required = true) @RequestBody PlayClerkWagesDetailsInfoEntity playClerkWagesDetailsInfo) {
        boolean success = playClerkWagesDetailsInfoService.create(playClerkWagesDetailsInfo);
        if (success) {
            return R.ok();
        }
        return R.error("添加失败");
    }

    /**
     * 修改店员工资明细信息
     */
    @ApiOperation(value = "修改工资明细", notes = "根据ID修改店员工资明细信息")
    @ApiImplicitParam(name = "id", value = "工资明细ID", required = true, paramType = "path", dataType = "String", example = "1")
    @ApiResponses({@ApiResponse(code = 200, message = "操作成功"), @ApiResponse(code = 500, message = "修改失败")})
    @Log(title = "店员工资明细信息", businessType = BusinessType.UPDATE)
    @PostMapping(value = "/update/{id}")
    public R update(@PathVariable String id,
            @ApiParam(value = "工资明细信息", required = true) @RequestBody PlayClerkWagesDetailsInfoEntity playClerkWagesDetailsInfo) {
        playClerkWagesDetailsInfo.setId(id);
        boolean success = playClerkWagesDetailsInfoService.update(playClerkWagesDetailsInfo);
        if (success) {
            return R.ok();
        }
        return R.error("修改失败");
    }

    /**
     * 删除店员工资明细信息
     */
    @ApiOperation(value = "删除工资明细", notes = "根据ID批量删除店员工资明细信息")
    @ApiImplicitParam(name = "ids", value = "工资明细ID数组", required = true, paramType = "path", dataType = "String[]", example = "1,2,3")
    @ApiResponses({@ApiResponse(code = 200, message = "操作成功", response = Integer.class)})
    @Log(title = "店员工资明细信息", businessType = BusinessType.DELETE)
    @DeleteMapping("/{ids}")
    public R remove(@PathVariable String[] ids) {
        return R.ok(playClerkWagesDetailsInfoService.deletePlayClerkWagesDetailsInfoByIds(ids));
    }
}


--- File: play-admin/src/main/java/com/starry/admin/modules/clerk/controller/PlayClerkWagesInfoController.java ---
package com.starry.admin.modules.clerk.controller;

import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.starry.admin.common.exception.CustomException;
import com.starry.admin.modules.clerk.module.entity.PlayClerkWagesDetailsInfoEntity;
import com.starry.admin.modules.clerk.module.vo.PlayClerkUnsettledWagesInfoQueryVo;
import com.starry.admin.modules.clerk.module.vo.PlayClerkUnsettledWagesInfoReturnVo;
import com.starry.admin.modules.clerk.module.vo.PlayClerkWagesInfoQueryVo;
import com.starry.admin.modules.clerk.service.IPlayClerkUserInfoService;
import com.starry.admin.modules.clerk.service.IPlayClerkWagesDetailsInfoService;
import com.starry.admin.modules.clerk.service.IPlayClerkWagesInfoService;
import com.starry.admin.modules.order.module.entity.PlayOrderInfoEntity;
import com.starry.admin.modules.weichat.entity.wages.ClerkWagesDetailsReturnVo;
import com.starry.common.result.R;
import com.starry.common.utils.ConvertUtil;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiParam;
import io.swagger.annotations.ApiResponse;
import io.swagger.annotations.ApiResponses;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.List;
import javax.annotation.Resource;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;

/**
 * 店员工资结算信息Controller
 *
 * @author admin
 * @since 2024-05-31
 */
@Api(tags = "店员工资结算", description = "店员工资结算信息管理相关接口，包括查询历史工资、未结算工资和工资明细等")
@RestController
@RequestMapping("/clerk/wages")
public class PlayClerkWagesInfoController {
    @Resource
    private IPlayClerkWagesInfoService playClerkWagesInfoService;

    @Resource
    private IPlayClerkWagesDetailsInfoService playClerkWagesDetailsInfoService;

    @Resource
    private IPlayClerkUserInfoService playClerkUserInfoService;

    /**
     * 查询店员工资结算信息列表
     */
    @ApiOperation(value = "查询历史工资列表", notes = "分页查询店员历史工资结算信息列表")
    @ApiResponses({@ApiResponse(code = 200, message = "操作成功", response = Object.class, responseContainer = "Page")})
    @PostMapping("/listByPage")
    public R list(@ApiParam(value = "查询条件", required = true) @Validated @RequestBody PlayClerkWagesInfoQueryVo vo) {
        return R.ok(playClerkWagesInfoService.selectHistoricalByPage(vo));
    }

    /**
     * 查询店员工未结算工资
     */
    @ApiOperation(value = "查询未结算工资", notes = "分页查询店员未结算工资信息列表")
    @ApiResponses({
            @ApiResponse(code = 200, message = "操作成功", response = PlayClerkUnsettledWagesInfoReturnVo.class, responseContainer = "Page")})
    @PostMapping("/listUnsettledWagesByPage")
    public R list(
            @ApiParam(value = "查询条件", required = true) @Validated @RequestBody PlayClerkUnsettledWagesInfoQueryVo vo) {
        IPage<PlayClerkUnsettledWagesInfoReturnVo> page = playClerkUserInfoService.listUnsettledWagesByPage(vo);
        for (PlayClerkUnsettledWagesInfoReturnVo record : page.getRecords()) {
            Integer orderState1Number = 0;
            BigDecimal orderState1Money = BigDecimal.ZERO;
            Integer orderState2Number = 0;
            BigDecimal orderState2Money = BigDecimal.ZERO;
            BigDecimal orderState2Revenue = BigDecimal.ZERO;
            Integer orderState3Number = 0;
            BigDecimal orderState3Money = BigDecimal.ZERO;
            BigDecimal orderState3Revenue = BigDecimal.ZERO;
            for (PlayOrderInfoEntity orderInfoEntity : record.getOrderInfoEntities()) {
                if ("1".equals(orderInfoEntity.getOrderStatus())) {
                    orderState1Number++;
                    orderState1Money = orderState1Money.add(orderInfoEntity.getFinalAmount());
                } else if ("2".equals(orderInfoEntity.getOrderStatus())) {
                    orderState2Number++;
                    orderState2Money = orderState2Money.add(orderInfoEntity.getFinalAmount());
                    orderState2Revenue = orderState2Revenue.add(orderInfoEntity.getEstimatedRevenue());
                } else if ("3".equals(orderInfoEntity.getOrderStatus())) {
                    orderState3Number++;
                    orderState3Money = orderState3Money.add(orderInfoEntity.getFinalAmount());
                    orderState3Revenue = orderState3Revenue.add(orderInfoEntity.getEstimatedRevenue());
                }
            }
            record.setOrderState1Number(orderState1Number);
            record.setOrderState1Money(orderState1Money);
            record.setOrderState2Number(orderState2Number);
            record.setOrderState2Money(orderState2Money);
            record.setOrderState2Revenue(orderState2Revenue);
            record.setOrderState3Number(orderState3Number);
            record.setOrderState3Money(orderState3Money);
            record.setOrderState3Revenue(orderState3Revenue);
        }
        return R.ok(page);
    }

    @ApiOperation(value = "查询工资明细", notes = "根据工资结算ID查询工资明细信息")
    @ApiImplicitParam(name = "id", value = "工资结算ID", required = true, paramType = "query", dataType = "String", example = "1")
    @ApiResponses({
            @ApiResponse(code = 200, message = "操作成功", response = ClerkWagesDetailsReturnVo.class, responseContainer = "List"),
            @ApiResponse(code = 500, message = "ID不能为空")})
    @GetMapping("queryWagesDetailsById")
    public R clerkQueryWagesDetails(@RequestParam("id") String id) {
        if (StrUtil.isBlankIfStr(id)) {
            throw new CustomException("ID不能为空");
        }
        List<PlayClerkWagesDetailsInfoEntity> list = playClerkWagesDetailsInfoService.selectByWagesId(id);
        List<ClerkWagesDetailsReturnVo> returnVos = new ArrayList<>(list.size());
        for (PlayClerkWagesDetailsInfoEntity entity : list) {
            returnVos.add(ConvertUtil.entityToVo(entity, ClerkWagesDetailsReturnVo.class));
        }
        return R.ok(returnVos);
    }

}


--- File: play-admin/src/main/java/com/starry/admin/modules/clerk/controller/PlayCustomArticleInfoController.java ---
package com.starry.admin.modules.clerk.controller;

import com.baomidou.mybatisplus.core.metadata.IPage;
import com.starry.admin.modules.clerk.module.entity.PlayCustomArticleInfoEntity;
import com.starry.admin.modules.clerk.service.IPlayCustomArticleInfoService;
import com.starry.common.annotation.Log;
import com.starry.common.enums.BusinessType;
import com.starry.common.result.R;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiParam;
import io.swagger.annotations.ApiResponse;
import io.swagger.annotations.ApiResponses;
import javax.annotation.Resource;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.*;

/**
 * 陪聊点赞动态信息Controller
 *
 * @author admin
 * @since 2024-05-04
 */
@Api(tags = "陪聊动态管理", description = "陪聊点赞动态信息管理相关接口，包括查询、新增、修改和删除等操作")
@RestController
@RequestMapping("/custom/article")
public class PlayCustomArticleInfoController {
    @Resource
    private IPlayCustomArticleInfoService playCustomArticleInfoService;

    /**
     * 查询陪聊点赞动态信息列表
     */
    @ApiOperation(value = "查询动态列表", notes = "分页查询陪聊点赞动态信息列表")
    @ApiResponses({
            @ApiResponse(code = 200, message = "操作成功", response = PlayCustomArticleInfoEntity.class, responseContainer = "Page")})
    @GetMapping("/list")
    public R list(@ApiParam(value = "查询条件") PlayCustomArticleInfoEntity playCustomArticleInfo) {
        IPage<PlayCustomArticleInfoEntity> list = playCustomArticleInfoService
                .selectPlayCustomArticleInfoByPage(playCustomArticleInfo);
        return R.ok(list);
    }

    /**
     * 获取陪聊点赞动态信息详细信息
     */
    @ApiOperation(value = "获取动态详情", notes = "根据ID获取陪聊点赞动态详细信息")
    @ApiImplicitParam(name = "id", value = "动态ID", required = true, paramType = "path", dataType = "String", example = "1")
    @ApiResponses({@ApiResponse(code = 200, message = "操作成功", response = PlayCustomArticleInfoEntity.class)})
    @PreAuthorize("@customSs.hasPermission('play:info:query')")
    @GetMapping(value = "/{id}")
    public R getInfo(@PathVariable("id") String id) {
        return R.ok(playCustomArticleInfoService.selectPlayCustomArticleInfoById(id));
    }

    /**
     * 新增陪聊点赞动态信息
     */
    @ApiOperation(value = "新增动态", notes = "创建新的陪聊点赞动态信息")
    @ApiResponses({@ApiResponse(code = 200, message = "操作成功"), @ApiResponse(code = 500, message = "添加失败")})
    @PreAuthorize("@customSs.hasPermission('play:info:create')")
    @Log(title = "陪聊点赞动态信息", businessType = BusinessType.INSERT)
    @PostMapping("/create")
    public R create(
            @ApiParam(value = "动态信息", required = true) @RequestBody PlayCustomArticleInfoEntity playCustomArticleInfo) {
        boolean success = playCustomArticleInfoService.create(playCustomArticleInfo);
        if (success) {
            return R.ok();
        }
        return R.error("添加失败");
    }

    /**
     * 修改陪聊点赞动态信息
     */
    @ApiOperation(value = "修改动态", notes = "根据ID修改陪聊点赞动态信息")
    @ApiImplicitParam(name = "id", value = "动态ID", required = true, paramType = "path", dataType = "String", example = "1")
    @ApiResponses({@ApiResponse(code = 200, message = "操作成功"), @ApiResponse(code = 500, message = "修改失败")})
    @PreAuthorize("@customSs.hasPermission('play:info:edit')")
    @Log(title = "陪聊点赞动态信息", businessType = BusinessType.UPDATE)
    @PostMapping(value = "/update/{id}")
    public R update(@PathVariable String id,
            @ApiParam(value = "动态信息", required = true) @RequestBody PlayCustomArticleInfoEntity playCustomArticleInfo) {
        playCustomArticleInfo.setId(id);
        boolean success = playCustomArticleInfoService.update(playCustomArticleInfo);
        if (success) {
            return R.ok();
        }
        return R.error("修改失败");
    }

    /**
     * 删除陪聊点赞动态信息
     */
    @ApiOperation(value = "删除动态", notes = "根据ID批量删除陪聊点赞动态信息")
    @ApiImplicitParam(name = "ids", value = "动态ID数组", required = true, paramType = "path", dataType = "String[]", example = "1,2,3")
    @ApiResponses({@ApiResponse(code = 200, message = "操作成功", response = Integer.class)})
    @PreAuthorize("@customSs.hasPermission('play:info:remove')")
    @Log(title = "陪聊点赞动态信息", businessType = BusinessType.DELETE)
    @DeleteMapping("/{ids}")
    public R remove(@PathVariable String[] ids) {
        return R.ok(playCustomArticleInfoService.deletePlayCustomArticleInfoByIds(ids));
    }
}


--- File: play-admin/src/main/java/com/starry/admin/modules/clerk/mapper/PlayAvatarFrameInfoMapper.java ---
package com.starry.admin.modules.clerk.mapper;

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.starry.admin.modules.clerk.module.entity.PlayAvatarFrameInfoEntity;

/**
 * 店员头像框Mapper接口
 *
 * @author admin
 * @since 2024-06-06
 */
public interface PlayAvatarFrameInfoMapper extends BaseMapper<PlayAvatarFrameInfoEntity> {

}


--- File: play-admin/src/main/java/com/starry/admin/modules/clerk/mapper/PlayClerkArticleInfoMapper.java ---
package com.starry.admin.modules.clerk.mapper;

import com.github.yulichang.base.MPJBaseMapper;
import com.starry.admin.modules.clerk.module.entity.PlayClerkArticleInfoEntity;

/**
 * 店员动态信息Mapper接口
 *
 * @author admin
 * @since 2024-05-04
 */
public interface PlayClerkArticleInfoMapper extends MPJBaseMapper<PlayClerkArticleInfoEntity> {

}


--- File: play-admin/src/main/java/com/starry/admin/modules/clerk/mapper/PlayClerkClassificationInfoMapper.java ---
package com.starry.admin.modules.clerk.mapper;

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.starry.admin.modules.clerk.module.entity.PlayClerkClassificationInfoEntity;

/**
 * 店员分类Mapper接口
 *
 * @author admin
 * @since 2024-04-06
 */
public interface PlayClerkClassificationInfoMapper extends BaseMapper<PlayClerkClassificationInfoEntity> {

}


--- File: play-admin/src/main/java/com/starry/admin/modules/clerk/mapper/PlayClerkCommodityMapper.java ---
package com.starry.admin.modules.clerk.mapper;

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.starry.admin.modules.clerk.module.entity.PlayClerkCommodityEntity;

/**
 * 陪聊服务项目Mapper接口
 *
 * @author admin
 * @since 2024-03-31
 */
public interface PlayClerkCommodityMapper extends BaseMapper<PlayClerkCommodityEntity> {

}


--- File: play-admin/src/main/java/com/starry/admin/modules/clerk/mapper/PlayClerkDataReviewInfoMapper.java ---
package com.starry.admin.modules.clerk.mapper;

import com.github.yulichang.base.MPJBaseMapper;
import com.starry.admin.modules.clerk.module.entity.PlayClerkDataReviewInfoEntity;

/**
 * 店员资料审核Mapper接口
 *
 * @author admin
 * @since 2024-05-19
 */
public interface PlayClerkDataReviewInfoMapper extends MPJBaseMapper<PlayClerkDataReviewInfoEntity> {

}


--- File: play-admin/src/main/java/com/starry/admin/modules/clerk/mapper/PlayClerkLevelInfoMapper.java ---
package com.starry.admin.modules.clerk.mapper;

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.starry.admin.modules.clerk.module.entity.PlayClerkLevelInfoEntity;

/**
 * 店员等级Mapper接口
 *
 * @author admin
 * @since 2024-03-30
 */
public interface PlayClerkLevelInfoMapper extends BaseMapper<PlayClerkLevelInfoEntity> {

}


--- File: play-admin/src/main/java/com/starry/admin/modules/clerk/mapper/PlayClerkPkMapper.java ---
package com.starry.admin.modules.clerk.mapper;

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.starry.admin.modules.clerk.module.entity.PlayClerkPkEntity;

/**
 * 店员pkMapper接口
 *
 * @author admin
 * @since 2024-08-02
 */
public interface PlayClerkPkMapper extends BaseMapper<PlayClerkPkEntity> {

}


--- File: play-admin/src/main/java/com/starry/admin/modules/clerk/mapper/PlayClerkRankingInfoMapper.java ---
package com.starry.admin.modules.clerk.mapper;

import com.github.yulichang.base.MPJBaseMapper;
import com.starry.admin.modules.clerk.module.entity.PlayClerkRankingInfoEntity;

/**
 * 店员排行Mapper接口
 *
 * @author admin
 * @since 2024-05-25
 */
public interface PlayClerkRankingInfoMapper extends MPJBaseMapper<PlayClerkRankingInfoEntity> {

}


--- File: play-admin/src/main/java/com/starry/admin/modules/clerk/mapper/PlayClerkTypeInfoMapper.java ---
package com.starry.admin.modules.clerk.mapper;

import com.github.yulichang.base.MPJBaseMapper;
import com.starry.admin.modules.clerk.module.entity.PlayClerkTypeInfoEntity;

/**
 * 店员分类信息Mapper接口
 *
 * @author admin
 * @since 2024-05-31
 */
public interface PlayClerkTypeInfoMapper extends MPJBaseMapper<PlayClerkTypeInfoEntity> {

}


--- File: play-admin/src/main/java/com/starry/admin/modules/clerk/mapper/PlayClerkTypeUserInfoMapper.java ---
package com.starry.admin.modules.clerk.mapper;

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.starry.admin.modules.clerk.module.entity.PlayClerkTypeUserInfoEntity;

/**
 * 店员和分类关系Mapper接口
 *
 * @author admin
 * @since 2024-05-31
 */
public interface PlayClerkTypeUserInfoMapper extends BaseMapper<PlayClerkTypeUserInfoEntity> {

}


--- File: play-admin/src/main/java/com/starry/admin/modules/clerk/mapper/PlayClerkUserInfoMapper.java ---
package com.starry.admin.modules.clerk.mapper;

import com.github.yulichang.base.MPJBaseMapper;
import com.starry.admin.modules.clerk.module.entity.PlayClerkUserInfoEntity;

/**
 * 店员Mapper接口
 *
 * @author admin
 * @since 2024-03-30
 */
public interface PlayClerkUserInfoMapper extends MPJBaseMapper<PlayClerkUserInfoEntity> {

}


--- File: play-admin/src/main/java/com/starry/admin/modules/clerk/mapper/PlayClerkUserReviewInfoMapper.java ---
package com.starry.admin.modules.clerk.mapper;

import com.github.yulichang.base.MPJBaseMapper;
import com.starry.admin.modules.clerk.module.entity.PlayClerkUserReviewInfoEntity;

/**
 * 店员资料审核Mapper接口
 *
 * @author admin
 * @since 2024-05-19
 */
public interface PlayClerkUserReviewInfoMapper extends MPJBaseMapper<PlayClerkUserReviewInfoEntity> {

}


--- File: play-admin/src/main/java/com/starry/admin/modules/clerk/mapper/PlayClerkWagesDetailsInfoMapper.java ---
package com.starry.admin.modules.clerk.mapper;

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.starry.admin.modules.clerk.module.entity.PlayClerkWagesDetailsInfoEntity;

/**
 * 店员工资明细信息Mapper接口
 *
 * @author admin
 * @since 2024-05-31
 */
public interface PlayClerkWagesDetailsInfoMapper extends BaseMapper<PlayClerkWagesDetailsInfoEntity> {

}


--- File: play-admin/src/main/java/com/starry/admin/modules/clerk/mapper/PlayClerkWagesInfoMapper.java ---
package com.starry.admin.modules.clerk.mapper;

import com.github.yulichang.base.MPJBaseMapper;
import com.starry.admin.modules.clerk.module.entity.PlayClerkWagesInfoEntity;

/**
 * 店员工资结算信息Mapper接口
 *
 * @author admin
 * @since 2024-05-31
 */
public interface PlayClerkWagesInfoMapper extends MPJBaseMapper<PlayClerkWagesInfoEntity> {

}


--- File: play-admin/src/main/java/com/starry/admin/modules/clerk/mapper/PlayCustomArticleInfoMapper.java ---
package com.starry.admin.modules.clerk.mapper;

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.starry.admin.modules.clerk.module.entity.PlayCustomArticleInfoEntity;

/**
 * 陪聊点赞动态信息Mapper接口
 *
 * @author admin
 * @since 2024-05-04
 */
public interface PlayCustomArticleInfoMapper extends BaseMapper<PlayCustomArticleInfoEntity> {

}


--- File: play-admin/src/main/java/com/starry/admin/modules/clerk/module/entity/ClerkPkEnum.java ---
package com.starry.admin.modules.clerk.module.entity;

import lombok.AllArgsConstructor;
import lombok.Getter;

/**
 * @Author: huchuansai
 * @Date: 2021/11/1 4:33 下午
 * @Description:
 */
@AllArgsConstructor
public enum ClerkPkEnum {

    TO_BE_STARTED("TO_BE_STARTED", "待开始"), IN_PROGRESS("IN_PROGRESS", "进行中"), FINISHED("FINISHED", "已完成"),;

    @Getter
    private String value;
    @Getter
    private String desc;
}


--- File: play-admin/src/main/java/com/starry/admin/modules/clerk/module/entity/IPlayClerkRankingInfoQueryVo.java ---
package com.starry.admin.modules.clerk.module.entity;

import com.fasterxml.jackson.annotation.JsonFormat;
import com.starry.common.domain.BasePageEntity;
import java.time.LocalDate;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.springframework.format.annotation.DateTimeFormat;

/**
 * @author admin
 * @since 2024/6/7 上午9:49
 **/
@EqualsAndHashCode(callSuper = true)
@Data
public class IPlayClerkRankingInfoQueryVo extends BasePageEntity {

    /**
     * 日排名还是周排名（0:每日排名;1:每周排名）
     */
    private String weeklyRanking;

    /**
     * 是否为历史统计(1:是;0:不是)
     */
    private String historicalStatistics;

    /**
     * 店员ID
     */
    private String clerkId;

    /**
     * 店员性别
     */
    private String sex;

    /**
     * 统计时间
     */
    @DateTimeFormat(pattern = "yyyy-MM-dd")
    @JsonFormat(pattern = "yyyy-MM-dd", timezone = "GMT+8")
    private LocalDate settlementDate;

}


--- File: play-admin/src/main/java/com/starry/admin/modules/clerk/module/entity/IPlayClerkRankingInfoReturnVo.java ---
package com.starry.admin.modules.clerk.module.entity;

import com.fasterxml.jackson.annotation.JsonFormat;
import java.math.BigDecimal;
import java.time.LocalDate;
import lombok.Data;
import org.springframework.format.annotation.DateTimeFormat;

/**
 * @author admin
 * @since 2024/6/7 上午9:49
 **/
@Data
public class IPlayClerkRankingInfoReturnVo {

    /**
     * UUID
     */
    private String id;

    /**
     * 租户ID
     */
    private String tenantId;

    /**
     * 店员ID
     */
    private String clerkId;

    /**
     * 店员性别[0:未知;1:男;2:女]
     */
    private String clerkSex;

    /**
     * 店员昵称
     */
    private String clerkNickname;

    /**
     * 店员分组名称
     */
    private String clerkGroupName;

    /**
     * 排序名次
     */
    private Integer rankingIndex;

    /**
     * 开始统计时间
     */
    @DateTimeFormat(pattern = "yyyy-MM-dd")
    @JsonFormat(pattern = "yyyy-MM-dd", timezone = "GMT+8")
    private LocalDate startCountDate;

    /**
     * 结束统计时间
     */
    @DateTimeFormat(pattern = "yyyy-MM-dd")
    @JsonFormat(pattern = "yyyy-MM-dd", timezone = "GMT+8")
    private LocalDate endCountDate;

    /**
     * 订单总数
     */
    private Integer orderNumber;

    /**
     * 订单金额
     */
    private BigDecimal orderMoney;

    /**
     * 续单数
     */
    private Integer orderContinueNumber;

    /**
     * 续单金额
     */
    private BigDecimal orderContinueMoney;

    /**
     * 超时未接单数量
     */
    private Integer ordersExpiredNumber = 0;

    /**
     * 续单比例
     */
    private BigDecimal orderContinueProportion = BigDecimal.ZERO;

    /**
     * 用户数
     */
    private Integer customNumber;

    /**
     * 订单平均价格
     */
    private BigDecimal averageUnitPrice = BigDecimal.ZERO;
    /**
     * 距离前一名相差金额
     */
    private BigDecimal previousMoney;

    /**
     * 统计时间
     */
    @DateTimeFormat(pattern = "yyyy-MM-dd")
    @JsonFormat(pattern = "yyyy-MM-dd", timezone = "GMT+8")
    private LocalDate settlementDate;

}


--- File: play-admin/src/main/java/com/starry/admin/modules/clerk/module/entity/PlayAvatarFrameInfoEntity.java ---
package com.starry.admin.modules.clerk.module.entity;

import com.baomidou.mybatisplus.annotation.TableName;
import com.starry.common.domain.BaseEntity;
import java.time.LocalDateTime;
import lombok.Data;
import lombok.EqualsAndHashCode;

/**
 * 店员头像框对象 play_avatar_frame_info
 *
 * @author admin
 * @since 2024-06-06
 */
@Data
@EqualsAndHashCode(callSuper = false)
@TableName("play_avatar_frame_info")
public class PlayAvatarFrameInfoEntity extends BaseEntity<PlayAvatarFrameInfoEntity> {

    /**
     * UUID
     */
    private String id;

    /**
     * 租户ID
     */
    private String tenantId;

    /**
     * 头像框名称
     */
    private String avatarFrameName;

    /**
     * 头像框地址
     */
    private String avatarFrameAddress;

    /**
     * 获取方式(0:手动赠送;1:自动赠送)
     */
    private String accessType;

    /**
     * 获取条件名称
     */
    private String accessName;

    /**
     * 获取条件
     */
    private String accessValue;

    /**
     * 有效期（单位：天，-1:永久有效）
     */
    private String periodValidity;

    /**
     * 添加时间
     */
    private LocalDateTime addTime;

}


--- File: play-admin/src/main/java/com/starry/admin/modules/clerk/module/entity/PlayClerkArticleInfoEntity.java ---
package com.starry.admin.modules.clerk.module.entity;

import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableName;
import com.starry.admin.common.conf.StringTypeHandler;
import com.starry.common.domain.BaseEntity;
import java.time.LocalDateTime;
import java.util.List;
import lombok.Data;
import lombok.EqualsAndHashCode;

/**
 * 店员动态信息对象 play_clerk_article_info
 *
 * @author admin
 * @since 2024-05-04
 */
@Data
@EqualsAndHashCode(callSuper = false)
@TableName(value = "play_clerk_article_info", autoResultMap = true)
public class PlayClerkArticleInfoEntity extends BaseEntity<PlayClerkArticleInfoEntity> {

    /**
     * UUID
     */
    private String id;

    /**
     * 租户ID
     */
    private String tenantId;

    /**
     * 陪聊用户ID
     */
    private String clerkId;

    /**
     * 动态内容
     */
    private String articleCon;

    /**
     * 动态附件类型（0:图片;1:视频;2:音频）
     */
    private String annexType;

    /**
     * 动态附件内容
     */
    @TableField(typeHandler = StringTypeHandler.class)
    private List<String> annexCon;

    /**
     * 发布时间
     */
    private LocalDateTime releaseTime;

    /**
     * 审核状态（0：未审核:1：审核通过，2：审核不通过）
     */
    private String reviewState;

    /**
     * 审核时间
     */
    private LocalDateTime reviewTime;

    /**
     * 备注（审核理由）
     */
    private String reviewCon;

}


--- File: play-admin/src/main/java/com/starry/admin/modules/clerk/module/entity/PlayClerkClassificationInfoEntity.java ---
package com.starry.admin.modules.clerk.module.entity;

import com.baomidou.mybatisplus.annotation.TableName;
import com.starry.common.domain.BaseEntity;
import lombok.Data;
import lombok.EqualsAndHashCode;

/**
 * 店员分类对象 play_clerk_classification_info
 *
 * @author admin
 * @since 2024-04-06
 */
@Data
@EqualsAndHashCode(callSuper = false)
@TableName("play_clerk_classification_info")
public class PlayClerkClassificationInfoEntity extends BaseEntity<PlayClerkClassificationInfoEntity> {

    /**
     * UUID
     */
    private String id;

    /**
     * 租户ID
     */
    private String tenantId;

    /**
     * 类型名称
     */
    private String name;

    /**
     * （排序字段）
     */
    private Integer sort;

}


--- File: play-admin/src/main/java/com/starry/admin/modules/clerk/module/entity/PlayClerkCommodityEntity.java ---
package com.starry.admin.modules.clerk.module.entity;

import com.baomidou.mybatisplus.annotation.TableName;
import com.starry.common.domain.BaseEntity;
import lombok.Data;
import lombok.EqualsAndHashCode;

/**
 * 陪聊服务项目对象 play_clerk_commodity
 *
 * @author admin
 * @since 2024-03-31
 */
@Data
@EqualsAndHashCode(callSuper = false)
@TableName("play_clerk_commodity_info")
public class PlayClerkCommodityEntity extends BaseEntity<PlayClerkCommodityEntity> {

    /**
     * UUID
     */
    private String id;

    /**
     * 租户ID
     */
    private String tenantId;

    /**
     * 店员用户ID
     */
    private String clerkId;

    /**
     * 服务项目ID
     */
    private String commodityId;

    /**
     * 服务项目名称
     */
    private String commodityName;

    /**
     * 服务项目排序
     */
    private Integer sort;

    /**
     * 服务启动状态 0:停用 1:启用
     */
    private String enablingState;

}


--- File: play-admin/src/main/java/com/starry/admin/modules/clerk/module/entity/PlayClerkDataReviewInfoEditVo.java ---
package com.starry.admin.modules.clerk.module.entity;

import lombok.Data;
import lombok.EqualsAndHashCode;

/**
 * 店员申请对象 play_clerk_data_review_info
 *
 * @author admin
 * @since 2024-04-11
 */
@Data
@EqualsAndHashCode(callSuper = false)
public class PlayClerkDataReviewInfoEditVo {

    /**
     * UUID
     */
    private String id;

    /**
     * 资料类型[0:店员申请,1:头像;2:相册;3:录音]
     */
    private String dataType;

    /**
     * 审核状态 0：未审核 1：审核通过 2：审核未通过
     */
    private String state;

    /**
     * 备注
     */
    private String remark;

}


--- File: play-admin/src/main/java/com/starry/admin/modules/clerk/module/entity/PlayClerkDataReviewInfoEntity.java ---
package com.starry.admin.modules.clerk.module.entity;

import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableName;
import com.starry.admin.utils.ListToStringHandle;
import com.starry.common.domain.BaseEntity;
import java.time.LocalDateTime;
import java.util.List;
import lombok.Data;
import lombok.EqualsAndHashCode;

/**
 * 店员资料审核对象 play_clerk_data_review_info
 *
 * @author admin
 * @since 2024-05-19
 */
@Data
@EqualsAndHashCode(callSuper = false)
@TableName(value = "play_clerk_data_review_info", autoResultMap = true)
public class PlayClerkDataReviewInfoEntity extends BaseEntity<PlayClerkDataReviewInfoEntity> {

    /**
     * UUID
     */
    private String id;

    /**
     * 租户ID
     */
    private String tenantId;

    /**
     * 店员ID
     */
    private String clerkId;

    /**
     * 资料类型[0:昵称;1:头像;2:相册;3:录音]
     */
    private String dataType;

    /**
     * 资料内容
     */
    @TableField(typeHandler = ListToStringHandle.class)
    private List<String> dataContent;

    /**
     * 审核状态（0：未审核:1：审核通过，2：审核不通过）
     */
    private String reviewState;

    /**
     * 资料添加时间
     */
    private LocalDateTime addTime;

    /**
     * 审核时间
     */
    private LocalDateTime reviewTime;

    /**
     * 审核人
     */
    private String reviewBy;

    /**
     * 审核内容
     */
    private String reviewCon;

    /**
     * 备注
     */
    private String remark;

}


--- File: play-admin/src/main/java/com/starry/admin/modules/clerk/module/entity/PlayClerkDataReviewInfoQueryVo.java ---
package com.starry.admin.modules.clerk.module.entity;

import com.starry.common.domain.BasePageEntity;
import java.util.Date;
import lombok.Data;
import lombok.EqualsAndHashCode;

/**
 * 店员申请对象 play_clerk_data_review_info
 *
 * @author admin
 * @since 2024-04-11
 */
@Data
@EqualsAndHashCode(callSuper = false)
public class PlayClerkDataReviewInfoQueryVo extends BasePageEntity {

    /**
     * UUID
     */
    private String id;

    /**
     * 资料类型[0:店员申请,1:头像;2:相册;3:录音]
     */
    private String dataType;

    /**
     * 资料内容（JSON格式）
     */
    private String content;

    /**
     * 审核状态 0：未审核 1：审核通过 2：审核未通过
     */
    private String state = "0";

    /**
     * 资料添加时间
     */
    private Date addTime;

    /**
     * 审核时间
     */
    private Date reviewTime;

    /**
     * 审核人
     */
    private String reviewBy;

    /**
     * 备注
     */
    private String remark;

}


--- File: play-admin/src/main/java/com/starry/admin/modules/clerk/module/entity/PlayClerkLevelInfoEntity.java ---
package com.starry.admin.modules.clerk.module.entity;

import com.baomidou.mybatisplus.annotation.TableName;
import com.starry.common.domain.BaseEntity;
import lombok.Data;
import lombok.EqualsAndHashCode;

/**
 * 店员等级对象 play_clerk_level_info
 *
 * @author admin
 * @since 2024-03-30
 */
@Data
@EqualsAndHashCode(callSuper = false)
@TableName("play_clerk_level_info")
public class PlayClerkLevelInfoEntity extends BaseEntity<PlayClerkLevelInfoEntity> {

    /**
     * UUID
     */
    private String id;

    /**
     * 租户ID
     */
    private String tenantId;

    /**
     * 等级名称
     */
    private String name;

    /**
     * 等级数字（排序字段）
     */
    private Integer level;

    /**
     * 首次固定单比例[0 - 100%]
     */
    private Integer firstRegularRatio;

    /**
     * 非首次固定单比例[0 - 100%]
     */
    private Integer notFirstRegularRatio;

    /**
     * 首次打赏比例[0 - 100%]
     */
    private Integer firstRewardRatio;

    /**
     * 非首次打赏比例[0 - 100%]
     */
    private Integer notFirstRewardRatio;

    /**
     * 首次随机单比例[0 - 100%]
     */
    private Integer firstRandomRadio;

    /**
     * 非首次随机单比例[0 - 100%]
     */
    private Integer notFirstRandomRadio;

    private Integer styleType;

    private String styleImageUrl;
}


--- File: play-admin/src/main/java/com/starry/admin/modules/clerk/module/entity/PlayClerkPkEntity.java ---
package com.starry.admin.modules.clerk.module.entity;

import com.baomidou.mybatisplus.annotation.TableName;
import com.starry.common.domain.BaseEntity;
import java.util.Date;
import lombok.Data;
import lombok.EqualsAndHashCode;

/**
 * 店员pk对象 play_clerk_pk
 *
 * @author admin
 * @since 2024-08-02
 */
@Data
@EqualsAndHashCode(callSuper = false)
@TableName("play_clerk_pk")
public class PlayClerkPkEntity extends BaseEntity<PlayClerkPkEntity> {

    /**
     * UUID
     */
    private String id;

    /**
     * 租户ID
     */
    private String tenantId;

    /**
     * 创建人的id
     */
    private String createdBy;

    /**
     * 创建时间
     */
    private Date createdTime;

    /**
     * 修改人的id
     */
    private String updatedBy;

    /**
     * 修改时间
     */
    private Date updatedTime;

    /**
     * 数据版本
     */
    private Long version;

    /**
     * 店员A
     */
    private String clerkA;

    /**
     * 店员B
     */
    private String clerkB;

    /**
     * 比例
     */
    private Integer radio;

    /**
     * 开始时间
     */
    private Date pkBeginTime;

    /**
     * 结束时间
     */
    private Date pkEndTime;

    /**
     * 惩罚公示时间
     */
    private Date punishmentTime;

    /**
     * 状态
     */
    private String status;

}


--- File: play-admin/src/main/java/com/starry/admin/modules/clerk/module/entity/PlayClerkRankingInfoEntity.java ---
package com.starry.admin.modules.clerk.module.entity;

import com.baomidou.mybatisplus.annotation.TableName;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.starry.common.domain.BaseEntity;
import java.math.BigDecimal;
import java.time.LocalDate;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.springframework.format.annotation.DateTimeFormat;

/**
 * 店员排行对象 play_clerk_ranking_info
 *
 * @author admin
 * @since 2024-05-25
 */
@Data
@EqualsAndHashCode(callSuper = false)
@TableName("play_clerk_ranking_info")
public class PlayClerkRankingInfoEntity extends BaseEntity<PlayClerkRankingInfoEntity> {

    /**
     * UUID
     */
    private String id;

    /**
     * 租户ID
     */
    private String tenantId;

    /**
     * 店员ID
     */
    private String clerkId;

    /**
     * 是否为历史统计(1:是;0:不是)
     */
    private String historicalStatistics;

    /**
     * 日排名还是周排名（0:每日排名;1:每周排名）
     */
    private String weeklyRanking;

    /**
     * 统计批次
     */
    private Integer serialNumber;

    /**
     * 排序名次
     */
    private Integer rankingIndex;

    /**
     * 开始统计时间
     */
    @DateTimeFormat(pattern = "yyyy-MM-dd")
    @JsonFormat(pattern = "yyyy-MM-dd", timezone = "GMT+8")
    private LocalDate startCountDate;

    /**
     * 结束统计时间
     */
    @DateTimeFormat(pattern = "yyyy-MM-dd")
    @JsonFormat(pattern = "yyyy-MM-dd", timezone = "GMT+8")
    private LocalDate endCountDate;

    /**
     * 订单总数
     */
    private Integer orderNumber;

    /**
     * 订单金额
     */
    private BigDecimal orderMoney;

    /**
     * 续单数
     */
    private Integer orderContinueNumber;

    /**
     * 续单金额
     */
    private BigDecimal orderContinueMoney;

    /**
     * 超时未接单数量
     */
    private Integer ordersExpiredNumber = 0;

    /**
     * 续单比例
     */
    private BigDecimal orderContinueProportion = BigDecimal.ZERO;

    /**
     * 用户数
     */
    private Integer customNumber;

    /**
     * 订单平均价格
     */
    private BigDecimal averageUnitPrice = BigDecimal.ZERO;
    /**
     * 距离前一名相差金额
     */
    private BigDecimal previousMoney;

    /**
     * 统计时间
     */
    @DateTimeFormat(pattern = "yyyy-MM-dd")
    @JsonFormat(pattern = "yyyy-MM-dd", timezone = "GMT+8")
    private LocalDate settlementDate;

}


--- File: play-admin/src/main/java/com/starry/admin/modules/clerk/module/entity/PlayClerkTypeInfoEntity.java ---
package com.starry.admin.modules.clerk.module.entity;

import com.baomidou.mybatisplus.annotation.TableName;
import com.starry.common.domain.BaseEntity;
import lombok.Data;
import lombok.EqualsAndHashCode;

/**
 * 店员分类信息对象 play_clerk_type_info
 *
 * @author admin
 * @since 2024-05-31
 */
@Data
@EqualsAndHashCode(callSuper = false)
@TableName("play_clerk_type_info")
public class PlayClerkTypeInfoEntity extends BaseEntity<PlayClerkTypeInfoEntity> {

    /**
     * UUID
     */
    private String id;

    /**
     * 租户ID
     */
    private String tenantId;

    /**
     * 分类名称
     */
    private String typeName;

    /**
     * 是否在首页显示（0:不显示,1:显示）
     */
    private String homeDisplayed;

    /**
     * 排序
     */
    private Integer sort;

}


--- File: play-admin/src/main/java/com/starry/admin/modules/clerk/module/entity/PlayClerkTypeUserInfoEntity.java ---
package com.starry.admin.modules.clerk.module.entity;

import com.baomidou.mybatisplus.annotation.TableName;
import com.starry.common.domain.BaseEntity;
import lombok.Data;
import lombok.EqualsAndHashCode;

/**
 * 店员和分类关系对象 play_clerk_type_user_info
 *
 * @author admin
 * @since 2024-05-31
 */
@Data
@EqualsAndHashCode(callSuper = false)
@TableName("play_clerk_type_user_info")
public class PlayClerkTypeUserInfoEntity extends BaseEntity<PlayClerkTypeUserInfoEntity> {

    /**
     * UUID
     */
    private String id;

    /**
     * 租户ID
     */
    private String tenantId;

    /**
     * 分组ID
     */
    private String typeId;

    /**
     * 店员ID
     */
    private String clerkId;

    /**
     * 排序
     */
    private Integer sort;

}


--- File: play-admin/src/main/java/com/starry/admin/modules/clerk/module/entity/PlayClerkUserDetailResultVo.java ---
package com.starry.admin.modules.clerk.module.entity;

import java.util.List;
import lombok.Data;

/**
 * @author admin
 */
@Data
public class PlayClerkUserDetailResultVo {

    /**
     * UUID
     */
    private String id;

    /**
     * 店员昵称
     */
    private String nickname;

    /**
     * 店员等级
     */
    private String levelId;

    /**
     * 店员等级
     */
    private String levelName;

    /**
     * 性别[0:未知;1:男;2:女]
     */
    private String sex;

    /**
     * 头像
     */
    private String avatar;

    /**
     * 头像框
     */
    private String avatarFrameId;

    /**
     * 音频
     */
    private String audio;

    /**
     * 标签
     */
    private String label;

    /**
     * 个性签名
     */
    private String signature;

    /**
     * 年龄
     */
    private Integer age;

    /**
     * 关注（0：未关注，1:已关注）
     */
    private String followState = "0";
    /**
     * 价格
     */
    private String price;

    /**
     * 礼物列表
     */

    private List<PlayGiftInfoVo> gifts;

    /**
     * 服务项目
     */
    private List<PlayClerkCommodityEntity> commodity;

}


--- File: play-admin/src/main/java/com/starry/admin/modules/clerk/module/entity/PlayClerkUserEvaluateInfoEntity.java ---
package com.starry.admin.modules.clerk.module.entity;

import java.util.Date;
import lombok.Data;

/**
 * 店员评价信息
 *
 * @author admin
 */
@Data
public class PlayClerkUserEvaluateInfoEntity {

    private String id;

    /**
     * 评价人ID
     */
    private String evaluatorId;

    /**
     * 评价人昵称
     */
    private String evaluatorUsername;

    /**
     * 评价人头像
     */
    private String evaluatorAvatar;

    /**
     * 评价内容
     */
    private String con;

    /**
     * 评价时间
     */
    private Date evaluateTime;

    /**
     * 订单ID
     */
    private String orderId;

    /**
     * 店员昵称
     */
    private String clerkUsername;

    /**
     * 商品ID
     */
    private String commodityId;

    /**
     * 商品名称
     */
    private String commodityName;

    /**
     * 商品单位
     */
    private String commodityUnit;

    /**
     * 点赞数
     */
    public Integer likeCount;

    public PlayClerkUserEvaluateInfoEntity(String id, String evaluatorId, String evaluatorUsername,
            String evaluatorAvatar, String con, Date evaluateTime, String orderId, String clerkUsername,
            String commodityId, String commodityName, String commodityUnit) {
        this.id = id;
        this.evaluatorId = evaluatorId;
        this.evaluatorUsername = evaluatorUsername;
        this.evaluatorAvatar = evaluatorAvatar;
        this.con = con;
        this.evaluateTime = evaluateTime;
        this.orderId = orderId;
        this.clerkUsername = clerkUsername;
        this.commodityId = commodityId;
        this.commodityName = commodityName;
        this.commodityUnit = commodityUnit;
    }

    public PlayClerkUserEvaluateInfoEntity(String id, String evaluatorId, String evaluatorUsername,
            String evaluatorAvatar, String con, Date evaluateTime, String orderId, String clerkUsername,
            String commodityId, String commodityName, String commodityUnit, int likeCount) {
        this.id = id;
        this.evaluatorId = evaluatorId;
        this.evaluatorUsername = evaluatorUsername;
        this.evaluatorAvatar = evaluatorAvatar;
        this.con = con;
        this.evaluateTime = evaluateTime;
        this.orderId = orderId;
        this.clerkUsername = clerkUsername;
        this.commodityId = commodityId;
        this.commodityName = commodityName;
        this.commodityUnit = commodityUnit;
        this.likeCount = likeCount;
    }

}


--- File: play-admin/src/main/java/com/starry/admin/modules/clerk/module/entity/PlayClerkUserInfoEntity.java ---
package com.starry.admin.modules.clerk.module.entity;

import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableName;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.starry.admin.common.conf.StringTypeHandler;
import com.starry.common.domain.BaseEntity;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.List;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.springframework.format.annotation.DateTimeFormat;

/**
 * 店员对象 play_clerk_user_info
 *
 * @author admin
 * @since 2024-03-30
 */
@Data
@EqualsAndHashCode(callSuper = false)
@TableName(value = "play_clerk_user_info", autoResultMap = true)
public class PlayClerkUserInfoEntity extends BaseEntity<PlayClerkUserInfoEntity> {

    /**
     * UUID
     */
    private String id;

    /**
     * 租户ID
     */
    private String tenantId;

    /**
     * 电话号码
     */
    private String phone;

    /**
     * 微信号码
     */
    private String weiChatCode;
    private String weiChatAvatar;

    /**
     * 陪聊用户ID
     */
    private String sysUserId;

    /**
     * 用户的标识，对当前公众号唯一
     */
    private String openid;

    /**
     * 店员昵称
     */
    private String nickname;

    /**
     * 是否固定等级(0:固定等级，1:不固定)
     */
    private String fixingLevel;

    /**
     * 分组ID
     */
    private String groupId;

    /**
     * 店员等级
     */
    private String levelId;

    /**
     * 店员类型
     */
    private String typeId;

    /**
     * 性别[0:未知;1:男;2:女]
     */
    private String sex;

    /**
     * 身份证号码
     */
    private String code;

    /**
     * 头像
     */
    private String avatar;

    /**
     * 头像框
     */
    private String avatarFrameId;

    /**
     * 音频
     */
    private String audio;

    /**
     * 星座
     */
    private String constellation;

    /**
     * 标签
     */
    @TableField(typeHandler = StringTypeHandler.class)
    private List<String> label;

    /**
     * 个性签名
     */
    private String signature;

    /**
     * 年龄
     */
    private Integer age;

    /**
     * 所在国家
     */
    private String country;

    /**
     * 所在省份
     */
    private String province;

    /**
     * 所在城市
     */
    private String city;

    /**
     * 账户余额
     */
    private BigDecimal accountBalance;

    /**
     * 备注
     */
    private String remark;

    /**
     * 在职状态（1：在职，0：离职）
     */
    private String onboardingState;

    /**
     * 入职时间
     */
    @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    @JsonFormat(pattern = "yyyy-MM-dd", timezone = "GMT+8")
    private LocalDateTime entryTime;

    /**
     * 离职时间
     */
    @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    @JsonFormat(pattern = "yyyy-MM-dd", timezone = "GMT+8")
    private LocalDateTime resignationTime;

    /**
     * 在职天数
     */
    private Integer workingHours = 0;

    /**
     * 是否推荐状态（1：已推荐，0：未推荐）
     */
    private String recommendationState;

    /**
     * 是否置顶状态（1：已置顶，0：未置顶）
     */
    private String pinToTopState;

    /**
     * 在线状态【1：在线，0：离线】
     */
    private String onlineState;

    /**
     * 上架状态【1：上架，0：下架】
     */
    private String listingState;

    /**
     * 显示状态【1：显示，0：隐藏】
     */
    private String displayState;

    /**
     * 实名状态【1：已实名，0：未实名】
     */
    private String realState;

    /**
     * 是否必须实名【0:非必须实名;1:必须实名;2:跟随店铺设置】
     */
    private String mandatoryRealState;

    /**
     * 随机接单状态【1：允许，0：禁止】
     */
    private String randomOrderState;

    /**
     * 店员状态（0:不是陪聊，1:陪聊，2:资料审核中）
     */
    private String clerkState;
    /**
     * 相册
     */
    @TableField(typeHandler = StringTypeHandler.class)
    private List<String> album;
    /**
     * 微信二维码
     **/
    private String weChatCodeImage;

    /**
     * 微信收款码图片
     **/
    private String weChatPayImage;

    /**
     * 支付宝收款码图片
     **/
    private String alipayImage;

    /**
     * 创建时间
     **/
    @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
    private LocalDateTime addTime;

    /**
     * 最近一次登录token
     */
    @JsonIgnore
    private String token;

    public PlayClerkUserInfoEntity() {
    }

    public PlayClerkUserInfoEntity(String id, BigDecimal accountBalance) {
        this.id = id;
        this.accountBalance = accountBalance;
    }
}


--- File: play-admin/src/main/java/com/starry/admin/modules/clerk/module/entity/PlayClerkUserQueryVo.java ---
package com.starry.admin.modules.clerk.module.entity;

import com.starry.common.domain.BasePageEntity;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import java.util.List;
import lombok.Data;
import lombok.EqualsAndHashCode;

/**
 * 店员对象 play_clerk_user_info
 *
 * @author admin
 * @since 2024-03-30
 */
@EqualsAndHashCode(callSuper = true)
@Data
@ApiModel(value = "店员分页查询参数", description = "分页查询店员列表的条件参数")
public class PlayClerkUserQueryVo extends BasePageEntity {

    /**
     * UUID
     */
    @ApiModelProperty(value = "店员ID", notes = "唯一标识")
    private String id;

    /**
     * 昵称
     */
    @ApiModelProperty(value = "店员昵称", notes = "支持模糊查询")
    private String nickname;

    /**
     * 电话号码
     */
    @ApiModelProperty(value = "电话号码")
    private String phone;

    /**
     * 分组ID
     */
    @ApiModelProperty(value = "分组ID")
    private String groupId;

    /**
     * 性别[0:未知;1:男;2:女]
     */
    @ApiModelProperty(value = "性别", notes = "0:未知;1:男;2:女")
    private String sex;

    /**
     * 是否固定等级(0:固定等级，1:不固定)
     */
    @ApiModelProperty(value = "是否固定等级", notes = "0:固定等级，1:不固定")
    private String fixingLevel;

    /**
     * 店员等级
     */
    @ApiModelProperty(value = "店员等级ID")
    private String levelId;

    /**
     * 年龄
     */
    @ApiModelProperty(value = "年龄范围", notes = "年龄区间查询，如[18, 25]")
    private List<Integer> ages;

    /**
     * 所在国家
     */
    @ApiModelProperty(value = "所在国家", example = "中国")
    private String country;

    /**
     * 所在省份
     */
    @ApiModelProperty(value = "所在省份", example = "广东省")
    private String province;

    /**
     * 所在城市
     */
    @ApiModelProperty(value = "所在城市", example = "深圳市")
    private String city;

    /**
     * 在职状态（1：在职，0：离职）
     */
    @ApiModelProperty(value = "在职状态", notes = "1：在职，0：离职", example = "1")
    private String onboardingState = "1";

    /**
     * 是否推荐状态（1：已推荐，0：未推荐）
     */
    @ApiModelProperty(value = "是否推荐", notes = "1：已推荐，0：未推荐")
    private String recommendationState;

    /**
     * 是否置顶状态（1：已置顶，0：未置顶）
     */
    @ApiModelProperty(value = "是否置顶", notes = "1：已置顶，0：未置顶")
    private String pinToTopState;

    /**
     * 在线状态【1：在线，0：离线】
     */
    @ApiModelProperty(value = "在线状态", notes = "1：在线，0：离线")
    private String onlineState;

    /**
     * 上架状态【1：上架，0：下架】
     */
    @ApiModelProperty(value = "上架状态", notes = "1：上架，0：下架")
    private String listingState;

    /**
     * 显示状态【1：显示，0：隐藏】
     */
    @ApiModelProperty(value = "显示状态", notes = "1：显示，0：隐藏")
    private String displayState;

    /**
     * 实名状态【1：已实名，0：未实名】
     */
    @ApiModelProperty(value = "实名状态", notes = "1：已实名，0：未实名")
    private String realState;

    /**
     * 是否必须实名【0:非必须实名;1:必须实名;2:跟随店铺设置】
     */
    @ApiModelProperty(value = "是否必须实名", notes = "0:非必须实名;1:必须实名;2:跟随店铺设置")
    private String mandatoryRealState;

    /**
     * 随机接单状态【1：允许，0：禁止】
     */
    @ApiModelProperty(value = "随机接单状态", notes = "1：允许，0：禁止")
    private String randomOrderState;

    /**
     * 店员状态（0:不是陪聊，1:陪聊，2:资料审核中）
     */
    @ApiModelProperty(value = "店员状态", notes = "0:不是陪聊，1:陪聊，2:资料审核中", example = "1")
    private String clerkState = "1";

    /**
     * 身份证号
     */
    @ApiModelProperty(value = "身份证号")
    private String code;

}


--- File: play-admin/src/main/java/com/starry/admin/modules/clerk/module/entity/PlayClerkUserReturnVo.java ---
package com.starry.admin.modules.clerk.module.entity;

import com.fasterxml.jackson.annotation.JsonFormat;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.starry.admin.modules.order.module.entity.PlayOrderInfoEntity;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
import lombok.Data;
import org.springframework.format.annotation.DateTimeFormat;

/**
 * 店员分页查询对象
 *
 * @author admin
 * @since 2024-03-30
 */
@Data
@ApiModel(value = "店员详细信息返回对象", description = "分页查询店员详细信息的返回结果")
public class PlayClerkUserReturnVo {

    /**
     * UUID
     */
    @ApiModelProperty(value = "店员ID", notes = "唯一标识")
    private String id;

    /**
     * 店员昵称
     */
    @ApiModelProperty(value = "店员昵称")
    private String nickname;

    /**
     * 店员等级
     */
    @ApiModelProperty(value = "店员等级ID")
    private String levelId;

    /**
     * 店员等级
     */
    @ApiModelProperty(value = "店员等级名称")
    private String levelName;

    /**
     * 分组ID
     */
    @ApiModelProperty(value = "分组ID")
    private String groupId;

    /**
     * 分组ID
     */
    @ApiModelProperty(value = "分组名称")
    private String groupName;

    /**
     * 性别[0:未知;1:男;2:女]
     */
    @ApiModelProperty(value = "性别", notes = "0:未知;1:男;2:女")
    private String sex;

    /**
     * 头像
     */
    @ApiModelProperty(value = "头像", notes = "头像图片URL")
    private String avatar;

    /**
     * 音频
     */
    @ApiModelProperty(value = "音频", notes = "音频文件URL")
    private String audio;

    /**
     * 星座
     */
    @ApiModelProperty(value = "星座", example = "天蝎座")
    private String constellation;

    /**
     * 标签
     */
    @ApiModelProperty(value = "标签列表", notes = "店员个人标签")
    private List<String> label = new ArrayList<>();

    /**
     * 相册
     */
    @ApiModelProperty(value = "相册列表", notes = "店员相册图片URL列表")
    private List<String> album = new ArrayList<>();

    /**
     * 个性签名
     */
    @ApiModelProperty(value = "个性签名")
    private String signature;

    /**
     * 年龄
     */
    @ApiModelProperty(value = "年龄", example = "25")
    private Integer age;

    /**
     * 所在省份
     */
    @ApiModelProperty(value = "所在省份", example = "广东省")
    private String province;

    /**
     * 所在城市
     */
    @ApiModelProperty(value = "所在城市", example = "深圳市")
    private String city;

    /**
     * 在线状态【1：在线，0：离线】
     */
    @ApiModelProperty(value = "在线状态", notes = "1：在线，0：离线")
    private String onlineState;

    /**
     * 上架状态【1：上架，0：下架】
     */
    @ApiModelProperty(value = "上架状态", notes = "1：上架，0：下架")
    private String listingState;

    /**
     * 实名状态【1：已实名，0：未实名】
     */
    @ApiModelProperty(value = "实名状态", notes = "1：已实名，0：未实名")
    private String realState;

    /**
     * 是否必须实名【0:非必须实名;1:必须实名;2:跟随店铺设置】
     */
    @ApiModelProperty(value = "是否必须实名", notes = "0:非必须实名;1:必须实名;2:跟随店铺设置")
    private String mandatoryRealState;

    /**
     * 随机接单状态【1：允许，0：禁止】
     */
    @ApiModelProperty(value = "随机接单状态", notes = "1：允许，0：禁止")
    private String randomOrderState;

    /**
     * 地址
     */
    @ApiModelProperty(value = "地址", notes = "店员所在详细地址")
    private String address;

    /**
     * 地址
     */
    @ApiModelProperty(value = "备注")
    private String remark;

    /**
     * 订单列表
     */
    @JsonIgnore
    @ApiModelProperty(value = "订单列表", hidden = true)
    private List<PlayOrderInfoEntity> orderInfos;

    /**
     * 订单总数
     */
    @ApiModelProperty(value = "订单总数")
    private String orderNumber;

    /**
     * 续单总数
     */
    @ApiModelProperty(value = "续单总数")
    private String orderContinueNumber;

    /**
     * 订单总金额
     */
    @ApiModelProperty(value = "订单总金额")
    private BigDecimal orderTotalAmount;

    @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
    @ApiModelProperty(value = "添加时间", example = "2024-01-01 12:00:00")
    private LocalDateTime addTime;

    /**
     * 入职时间
     */
    @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    @JsonFormat(pattern = "yyyy-MM-dd", timezone = "GMT+8")
    @ApiModelProperty(value = "入职时间", example = "2024-01-01")
    private LocalDateTime entryTime;

    /**
     * 离职时间
     */
    @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    @JsonFormat(pattern = "yyyy-MM-dd", timezone = "GMT+8")
    @ApiModelProperty(value = "离职时间", example = "2024-01-01")
    private LocalDateTime resignationTime;

    /**
     * 是否推荐状态（1：已推荐，0：未推荐）
     */
    @ApiModelProperty(value = "是否推荐", notes = "1：已推荐，0：未推荐")
    private String recommendationState;

    /**
     * 是否置顶状态（1：已置顶，0：未置顶）
     */
    @ApiModelProperty(value = "是否置顶", notes = "1：已置顶，0：未置顶")
    private String pinToTopState;

    /**
     * 显示状态【1：显示，0：隐藏】
     */
    @ApiModelProperty(value = "显示状态", notes = "1：显示，0：隐藏")
    private String displayState;

    /**
     * 是否固定等级(0:固定等级，1:不固定)
     */
    @ApiModelProperty(value = "是否固定等级", notes = "0:固定等级，1:不固定")
    private String fixingLevel;

    /**
     * 在职状态（1：在职，0：离职）
     */
    @ApiModelProperty(value = "在职状态", notes = "1：在职，0：离职")
    private String onboardingState;

    /**
     * 在职天数
     */
    @ApiModelProperty(value = "在职天数", example = "365")
    private Integer workingHours = 0;

}


--- File: play-admin/src/main/java/com/starry/admin/modules/clerk/module/entity/PlayClerkUserReviewInfoEntity.java ---
package com.starry.admin.modules.clerk.module.entity;

import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableName;
import com.starry.admin.common.conf.StringTypeHandler;
import com.starry.common.domain.BaseEntity;
import java.time.LocalDateTime;
import java.util.List;
import lombok.Data;
import lombok.EqualsAndHashCode;

/**
 * 店员申请对象 play_clerk_user_review_info
 *
 * @author admin
 * @since 2024-05-19
 */
@Data
@EqualsAndHashCode(callSuper = false)
@TableName(value = "play_clerk_user_review_info", autoResultMap = true)
public class PlayClerkUserReviewInfoEntity extends BaseEntity<PlayClerkUserReviewInfoEntity> {

    /**
     * UUID
     */
    private String id;

    /**
     * 租户ID
     */
    private String tenantId;

    /**
     * 店员ID
     */
    private String clerkId;

    /**
     * 微信号码
     */
    private String weiChatCode;

    /**
     * 手机号码
     */
    private String phone;

    /**
     * 昵称
     */
    private String nickname;

    /**
     * 性别[0:未知;1:男;2:女]
     */
    private String sex;

    /**
     * 年龄
     */
    private Integer age;

    /**
     * 所在省份
     */
    private String province;

    /**
     * 所在城市
     */
    private String city;

    /**
     * 备注（相关经验）
     */
    private String remark;

    /**
     * 音频
     */
    private String audio;

    /**
     * 照片
     */
    @TableField(typeHandler = StringTypeHandler.class)
    private List<String> album;

    /**
     * 申请时间
     */
    private LocalDateTime addTime;

    /**
     * 审核状态（0：未审核:1：审核通过，2：审核不通过）
     */
    private String reviewState;

    /**
     * 审核时间
     */
    private LocalDateTime reviewTime;

    /**
     * 审核人
     */
    private String reviewBy;

    /**
     * 审核内容
     */
    private String reviewCon;

}


--- File: play-admin/src/main/java/com/starry/admin/modules/clerk/module/entity/PlayClerkUserTrendsInfoEntity.java ---
package com.starry.admin.modules.clerk.module.entity;

import java.util.Date;
import lombok.Data;

/**
 * 店员动态
 *
 * @author admin
 */
@Data
public class PlayClerkUserTrendsInfoEntity {

    private String id;

    private String title;

    /**
     * 动态类型（0:照片，1:视频）
     */
    private String type;

    private String con;

    private Date releaseTime;

    public PlayClerkUserTrendsInfoEntity(String id, String title, String con, Date releaseTime) {
        this.id = id;
        this.title = title;
        this.con = con;
        this.releaseTime = releaseTime;
        this.type = "1";
    }

    public PlayClerkUserTrendsInfoEntity(String id, String title, String type, String con, Date releaseTime) {
        this.id = id;
        this.title = title;
        this.type = type;
        this.con = con;
        this.releaseTime = releaseTime;
    }
}


--- File: play-admin/src/main/java/com/starry/admin/modules/clerk/module/entity/PlayClerkUserTypeEditVo.java ---
package com.starry.admin.modules.clerk.module.entity;

import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import java.util.List;
import javax.validation.constraints.NotNull;
import lombok.Data;

/**
 * 店员对象 play_clerk_user_info
 *
 * @author admin
 * @since 2024-03-30
 */
@Data
@ApiModel(value = "店员分类编辑对象", description = "修改店员分类或分组的参数对象")
public class PlayClerkUserTypeEditVo {

    /**
     * 店员分类ID
     */
    @NotNull(message = "类型不能为空")
    @ApiModelProperty(value = "店员分类/分组ID", required = true, notes = "分类ID或分组ID")
    private String typeId;

    /**
     * 店员列表
     */
    @NotNull(message = "店员列表不能为空")
    @ApiModelProperty(value = "店员ID列表", required = true, notes = "要添加到分类/分组中的店员ID列表")
    private List<String> clerkUserIds;

}


--- File: play-admin/src/main/java/com/starry/admin/modules/clerk/module/entity/PlayClerkWagesDetailsInfoEntity.java ---
package com.starry.admin.modules.clerk.module.entity;

import com.baomidou.mybatisplus.annotation.TableName;
import com.starry.common.domain.BaseEntity;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import lombok.Data;
import lombok.EqualsAndHashCode;

/**
 * 店员工资明细信息对象 play_clerk_wages_details_info
 *
 * @author admin
 * @since 2024-05-31
 */
@Data
@EqualsAndHashCode(callSuper = false)
@TableName("play_clerk_wages_details_info")
public class PlayClerkWagesDetailsInfoEntity extends BaseEntity<PlayClerkWagesDetailsInfoEntity> {

    /**
     * UUID
     */
    private String id;

    /**
     * 租户ID
     */
    private String tenantId;

    /**
     * 工资统计ID
     */
    private String wagesId;

    /**
     * 店员ID
     */
    private String clerkId;

    /**
     * 订单ID
     */
    private String orderId;

    /**
     * 订单编号
     */
    private String orderNo;

    /**
     * 订单最终金额（支付金额）
     */
    private BigDecimal finalAmount;

    /**
     * 店员收入
     */
    private BigDecimal estimatedRevenue;

    /**
     * 订单完成时间
     */
    private LocalDateTime endOrderTime;

}


--- File: play-admin/src/main/java/com/starry/admin/modules/clerk/module/entity/PlayClerkWagesInfoEntity.java ---
package com.starry.admin.modules.clerk.module.entity;

import com.baomidou.mybatisplus.annotation.TableName;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.starry.common.domain.BaseEntity;
import java.math.BigDecimal;
import java.time.LocalDate;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.springframework.format.annotation.DateTimeFormat;

/**
 * 店员工资结算信息对象 play_clerk_wages_info
 *
 * @author admin
 * @since 2024-05-31
 */
@Data
@EqualsAndHashCode(callSuper = false)
@TableName("play_clerk_wages_info")
public class PlayClerkWagesInfoEntity extends BaseEntity<PlayClerkWagesInfoEntity> {

    /**
     * UUID
     */
    private String id;

    /**
     * 租户ID
     */
    private String tenantId;

    /**
     * 店员ID
     */
    private String clerkId;

    /**
     * 是否为往期统计(1:是;0:不是)
     **/
    private String historicalStatistics;

    /**
     * 统计序号
     */
    private Long serialNumber;

    /**
     * 排行序号
     */
    private Long rankingIndex;

    /**
     * 开始统计时间
     */
    @DateTimeFormat(pattern = "yyyy-MM-dd")
    @JsonFormat(pattern = "yyyy-MM-dd", timezone = "GMT+8")
    private LocalDate startCountDate;

    /**
     * 结束统计时间
     */
    @DateTimeFormat(pattern = "yyyy-MM-dd")
    @JsonFormat(pattern = "yyyy-MM-dd", timezone = "GMT+8")
    private LocalDate endCountDate;

    /**
     * 结算时间
     */
    @DateTimeFormat(pattern = "yyyy-MM-dd")
    @JsonFormat(pattern = "yyyy-MM-dd", timezone = "GMT+8")
    private LocalDate settlementDate;

    /**
     * 订单总数
     */
    private Integer orderNumber;

    /**
     * 订单金额
     */
    private BigDecimal finalAmount;

    /**
     * 续单数
     */
    private Integer orderContinueNumber;

    /**
     * 续单比例
     */
    private double orderContinueProportion = 0.0;

    /**
     * 续单金额
     */
    private BigDecimal orderContinueMoney;

    /**
     * 超时未接单数
     */
    private Integer ordersExpiredNumber;

    /**
     * 店员收入
     */
    private BigDecimal estimatedRevenue;

}


--- File: play-admin/src/main/java/com/starry/admin/modules/clerk/module/entity/PlayCustomArticleInfoEntity.java ---
package com.starry.admin.modules.clerk.module.entity;

import com.baomidou.mybatisplus.annotation.TableName;
import com.starry.common.domain.BaseEntity;
import java.time.LocalDateTime;
import lombok.Data;
import lombok.EqualsAndHashCode;

/**
 * 陪聊点赞动态信息对象 play_custom_article_info
 *
 * @author admin
 * @since 2024-05-04
 */
@Data
@EqualsAndHashCode(callSuper = false)
@TableName("play_custom_article_info")
public class PlayCustomArticleInfoEntity extends BaseEntity<PlayCustomArticleInfoEntity> {

    /**
     * UUID
     */
    private String id;

    /**
     * 租户ID
     */
    private String tenantId;

    /**
     * 动态ID
     */
    private String articleId;

    /**
     * 店员ID
     */
    private String clerkId;

    /**
     * 顾客ID
     */
    private String customId;

    /**
     * 点赞时间
     */
    private LocalDateTime endorseTime;

    /**
     * 赞同状态(1:赞同,0:未赞同)
     */
    private String endorseState;

    /**
     * 操作类型(1:关注;0:收藏)
     */
    private String endorseType;

}


--- File: play-admin/src/main/java/com/starry/admin/modules/clerk/module/entity/PlayGiftInfoVo.java ---
package com.starry.admin.modules.clerk.module.entity;

import lombok.Data;

/**
 * 礼物信息
 *
 * @author admin
 */

@Data
public class PlayGiftInfoVo {

    /**
     * 礼物ID
     */
    private String id;

    /**
     * 礼物名称
     */
    private String name;

    /**
     * 礼物地址
     */
    private String url;

    /**
     * 获得数量
     */
    private Integer number;

    /**
     * 礼物状态（0：正常，1：已下架）
     */
    private String state;

    public PlayGiftInfoVo(String id, String name, String url, Integer number, String state) {
        this.id = id;
        this.name = name;
        this.url = url;
        this.number = number;
        this.state = state;
    }
}


--- File: play-admin/src/main/java/com/starry/admin/modules/clerk/module/vo/PlayAvatarFrameInfoAddVo.java ---
package com.starry.admin.modules.clerk.module.vo;

import com.fasterxml.jackson.annotation.JsonFormat;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import java.time.LocalDateTime;
import javax.validation.constraints.NotNull;
import lombok.Data;
import org.springframework.format.annotation.DateTimeFormat;

/**
 * @author admin
 * @since 2024/6/6 下午11:13
 **/
@Data
@ApiModel(value = "店员头像框添加参数", description = "新增店员头像框信息的请求参数")
public class PlayAvatarFrameInfoAddVo {

    /**
     * 添加时间
     */
    @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
    @ApiModelProperty(value = "添加时间", hidden = true, notes = "系统自动生成，无需传入")
    private final LocalDateTime addTime = LocalDateTime.now();
    /**
     * 头像框名称
     */
    @NotNull(message = "头像框名称不能为空")
    @ApiModelProperty(value = "头像框名称", required = true, example = "年度贡献者", notes = "头像框的名称")
    private String avatarFrameName;
    /**
     * 头像框地址
     */
    @NotNull(message = "头像框图片不能为空")
    @ApiModelProperty(value = "头像框地址", required = true, example = "https://example.com/frame.png", notes = "头像框图片的URL地址")
    private String avatarFrameAddress;
    /**
     * 获取方式(0:手动赠送;1:自动赠送)
     */
    @NotNull(message = "头像框获取方式不能为空")
    @ApiModelProperty(value = "获取方式", required = true, example = "0", notes = "0:手动赠送;1:自动赠送")
    private String accessType;
    /**
     * 获取条件名称
     */
    @ApiModelProperty(value = "获取条件名称", example = "年度贡献奖", notes = "获取该头像框的条件名称")
    private String accessName;
    /**
     * 获取条件
     */
    @ApiModelProperty(value = "获取条件", example = "年度贡献超过10000元", notes = "获取该头像框的具体条件")
    private String accessValue;
    /**
     * 有效期（单位：天，-1:永久有效）
     */
    @NotNull(message = "有效期不能为空")
    @ApiModelProperty(value = "有效期", required = true, example = "365", notes = "头像框的有效期，单位：天，-1表示永久有效")
    private String periodValidity;
}


--- File: play-admin/src/main/java/com/starry/admin/modules/clerk/module/vo/PlayAvatarFrameInfoQueryVo.java ---
package com.starry.admin.modules.clerk.module.vo;

import com.starry.common.domain.BasePageEntity;
import io.swagger.annotations.ApiModel;
import lombok.Data;
import lombok.EqualsAndHashCode;

/**
 * @author admin
 * @since 2024/6/6 下午11:13
 **/
@EqualsAndHashCode(callSuper = true)
@Data
@ApiModel(value = "店员头像框查询参数", description = "查询店员头像框信息的条件参数")
public class PlayAvatarFrameInfoQueryVo extends BasePageEntity {

}


--- File: play-admin/src/main/java/com/starry/admin/modules/clerk/module/vo/PlayAvatarFrameSendVo.java ---
package com.starry.admin.modules.clerk.module.vo;

import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import javax.validation.constraints.NotNull;
import lombok.Data;

/**
 * @author admin
 * @since 2024/6/6 下午11:13
 **/
@Data
@ApiModel(value = "店员头像框赠送参数", description = "赠送头像框给店员的请求参数")
public class PlayAvatarFrameSendVo {

    @ApiModelProperty(value = "店员ID", required = true, example = "1", notes = "接收头像框的店员ID")
    private String clerkId;

    @NotNull(message = "头像框获取方式不能为空")
    @ApiModelProperty(value = "头像框ID", required = true, example = "1", notes = "要赠送的头像框ID")
    private String avatarFrameId;
}


--- File: play-admin/src/main/java/com/starry/admin/modules/clerk/module/vo/PlayClerkArticleQueryVo.java ---
package com.starry.admin.modules.clerk.module.vo;

import com.starry.common.domain.BasePageEntity;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import java.util.List;
import lombok.Data;
import lombok.EqualsAndHashCode;

/**
 * @author admin 动态信息查询对象
 */
@EqualsAndHashCode(callSuper = true)
@Data
@ApiModel(value = "店员动态查询参数", description = "查询店员动态信息的条件参数")
public class PlayClerkArticleQueryVo extends BasePageEntity {

    @ApiModelProperty(value = "动态ID", example = "1", notes = "特定动态的ID")
    private String id;

    /**
     * 店员ID
     */
    @ApiModelProperty(value = "店员ID", example = "1", notes = "特定店员的ID")
    private String clerkId;
    /**
     * 审核状态（0：未审核:1：审核通过，2：审核不通过）
     */
    @ApiModelProperty(value = "审核状态", example = "0", notes = "0：未审核:1：审核通过，2：审核不通过")
    private String reviewState;

    /**
     * 发布时间
     */
    @ApiModelProperty(value = "发布时间范围", example = "['2024-01-01 00:00:00','2024-12-31 23:59:59']", notes = "动态发布时间范围，包含开始和结束时间")
    private List<String> releaseTime;

    /**
     * 顾客ID
     */
    @ApiModelProperty(value = "顾客ID", example = "1", notes = "特定顾客的ID")
    private String customId;
}


--- File: play-admin/src/main/java/com/starry/admin/modules/clerk/module/vo/PlayClerkArticleReturnVo.java ---
package com.starry.admin.modules.clerk.module.vo;

import com.fasterxml.jackson.annotation.JsonFormat;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.starry.admin.modules.clerk.module.entity.PlayCustomArticleInfoEntity;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import java.time.LocalDateTime;
import java.util.List;
import lombok.Data;
import org.springframework.format.annotation.DateTimeFormat;

/**
 * @author admin 动态信息查询返回对象
 */
@Data
@ApiModel(value = "店员动态返回数据", description = "店员动态信息的返回数据")
public class PlayClerkArticleReturnVo {

    /**
     * UUID
     */
    @ApiModelProperty(value = "动态ID", example = "1")
    private String id;
    /**
     * 陪聊用户ID
     */
    @ApiModelProperty(value = "店员ID", example = "1")
    private String clerkId;

    /**
     * 陪聊用户昵称
     */
    @ApiModelProperty(value = "店员昵称", example = "小明")
    private String clerkNickname;

    /**
     * 陪聊用户头像
     */
    @ApiModelProperty(value = "店员头像", example = "https://example.com/avatar.jpg")
    private String clerkAvatar;

    /**
     * 动态内容
     */
    @ApiModelProperty(value = "动态内容", example = "今天天气真好，感谢大家的支持！")
    private String articleCon;

    /**
     * 动态附件类型（0:图片;1:视频;2:音频）
     */
    @ApiModelProperty(value = "附件类型", example = "0", notes = "0:图片;1:视频;2:音频")
    private String annexType;

    /**
     * 动态附件内容
     */
    @ApiModelProperty(value = "附件内容", example = "[\"https://example.com/photo1.jpg\"]", notes = "附件内容，根据附件类型有不同格式")
    private List<String> annexCon;

    /**
     * 发布时间
     */
    @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
    @ApiModelProperty(value = "发布时间", example = "2024-01-01 12:00:00")
    private LocalDateTime releaseTime;

    /**
     * 备注(审核理由)
     */
    @ApiModelProperty(value = "审核理由", example = "内容健康，通过审核")
    private String reviewCon;

    /**
     * 点赞人数
     */
    @ApiModelProperty(value = "点赞人数", example = "10")
    private Integer agreedQuantity = 0;

    /**
     * 审核状态（0：未审核:1：审核通过，2：审核不通过）
     */
    @ApiModelProperty(value = "审核状态", example = "0", notes = "0：未审核:1：审核通过，2：审核不通过")
    private String reviewState;

    /**
     * 陪聊点赞动态
     */
    @JsonIgnore
    @ApiModelProperty(value = "点赞动态", hidden = true)
    private List<PlayCustomArticleInfoEntity> articleInfoEntities;
}


--- File: play-admin/src/main/java/com/starry/admin/modules/clerk/module/vo/PlayClerkArticleReviewStateEditVo.java ---
package com.starry.admin.modules.clerk.module.vo;

import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import java.time.LocalDateTime;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.Pattern;
import lombok.Data;

/**
 * @author admin 动态审核
 */
@Data
@ApiModel(value = "店员动态审核状态修改参数", description = "修改店员动态审核状态的请求参数")
public class PlayClerkArticleReviewStateEditVo {

    @NotBlank(message = "ID不能为空")
    @ApiModelProperty(value = "动态ID", required = true, example = "1", notes = "店员动态的ID")
    private String id;

    /**
     * 审核状态（0：未审核:1：审核通过，2：审核不通过）
     */
    @NotBlank(message = "审核状态不能为空")
    @Pattern(regexp = "[12]", message = "审核状态必须为1或2")
    @ApiModelProperty(value = "审核状态", required = true, example = "1", notes = "1：审核通过，2：审核不通过")
    private String reviewState;

    /**
     * 发布时间
     */
    @ApiModelProperty(value = "备注", example = "内容健康，通过审核", notes = "审核的备注内容或拒绝原因")
    private String remark;

    /**
     * 审核时间
     */
    @ApiModelProperty(value = "审核时间", hidden = true, notes = "系统自动生成，无需传入")
    private LocalDateTime reviewTime = LocalDateTime.now();
}


--- File: play-admin/src/main/java/com/starry/admin/modules/clerk/module/vo/PlayClerkCommodityEditVo.java ---
package com.starry.admin.modules.clerk.module.vo;

import javax.validation.constraints.NotNull;
import javax.validation.constraints.Pattern;
import lombok.Data;

/**
 * @author admin
 */
@Data
public class PlayClerkCommodityEditVo {

    /**
     * 项目类型
     */
    @NotNull(message = "项目类型名称不能为空")
    private String commodityType;

    /**
     * 服务启动状态 0:停用 1:启用
     */
    @NotNull(message = "服务状态不能为空")
    @Pattern(regexp = "[01]", message = "服务状态必须为0或1")
    private String enablingState;
}


--- File: play-admin/src/main/java/com/starry/admin/modules/clerk/module/vo/PlayClerkCommodityQueryVo.java ---
package com.starry.admin.modules.clerk.module.vo;

import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;

/**
 * @author admin
 */
@Data
@ApiModel(value = "陪聊服务项目查询参数", description = "查询陪聊服务项目的条件参数")
public class PlayClerkCommodityQueryVo {

    /**
     * 项目类型
     */
    @ApiModelProperty(value = "项目类型", example = "1", notes = "服务项目的类型")
    private String commodityType;

    /**
     * 服务启动状态 0:停用 1:启用
     */
    @ApiModelProperty(value = "启用状态", example = "1", notes = "0:停用,1:启用")
    private String enablingState;

    public PlayClerkCommodityQueryVo(String commodityType, String enablingState) {
        this.commodityType = commodityType;
        this.enablingState = enablingState;
    }
}


--- File: play-admin/src/main/java/com/starry/admin/modules/clerk/module/vo/PlayClerkDataReviewQueryVo.java ---
package com.starry.admin.modules.clerk.module.vo;

import com.starry.common.domain.BasePageEntity;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import java.util.List;
import lombok.Data;
import lombok.EqualsAndHashCode;

/**
 * 店员资料审核对象 play_clerk_data_review_info
 *
 * @author admin
 * @since 2024-05-19
 */
@Data
@EqualsAndHashCode(callSuper = false)
@ApiModel(value = "店员资料内容审核查询参数", description = "查询店员资料内容审核信息的条件参数")
public class PlayClerkDataReviewQueryVo extends BasePageEntity {

    /**
     * UUID
     */
    @ApiModelProperty(value = "店员ID", example = "1", notes = "特定店员的ID")
    private String clerkId;

    /**
     * 资料类型[0:昵称;1:头像;2:相册;3:录音]
     */
    @ApiModelProperty(value = "资料类型", example = "1", notes = "0:昵称;1:头像;2:相册;3:录音")
    private String dataType;

    /**
     * 审核状态（0：未审核:1：审核通过，2：审核不通过）
     */
    @ApiModelProperty(value = "审核状态", example = "0", notes = "0：未审核:1：审核通过，2：审核不通过")
    private String reviewState;

    /**
     * 资料添加时间
     */
    @ApiModelProperty(value = "添加时间范围", example = "['2024-01-01 00:00:00','2024-12-31 23:59:59']", notes = "资料添加时间范围，包含开始和结束时间")
    private List<String> addTime;
}


--- File: play-admin/src/main/java/com/starry/admin/modules/clerk/module/vo/PlayClerkDataReviewReturnVo.java ---
package com.starry.admin.modules.clerk.module.vo;

import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import java.time.LocalDateTime;
import java.util.List;
import lombok.Data;

/**
 * 店员资料审核对象 play_clerk_data_review_info
 *
 * @author admin
 * @since 2024-05-19
 */
@Data
@ApiModel(value = "店员资料内容审核返回数据", description = "店员资料内容审核信息的返回数据")
public class PlayClerkDataReviewReturnVo {

    /**
     * UUID
     */
    @ApiModelProperty(value = "审核记录ID", example = "1")
    private String id;

    /**
     * 店员ID
     */
    @ApiModelProperty(value = "店员ID", example = "1")
    private String clerkId;

    /**
     * 店员昵称
     */
    @ApiModelProperty(value = "店员昵称", example = "小明")
    private String clerkNickname;

    /**
     * 店员头像
     */
    @ApiModelProperty(value = "店员头像", example = "https://example.com/avatar.jpg")
    private String clerkAvatar;

    /**
     * 资料类型[0:昵称;1:头像;2:相册;3:录音]
     */
    @ApiModelProperty(value = "资料类型", example = "1", notes = "0:昵称;1:头像;2:相册;3:录音")
    private String dataType;

    /**
     * 资料内容
     */
    @ApiModelProperty(value = "资料内容", example = "[\"https://example.com/photo1.jpg\"]", notes = "资料内容，根据资料类型有不同格式")
    private List<String> dataContent;

    /**
     * 审核状态（0：未审核:1：审核通过，2：审核不通过）
     */
    @ApiModelProperty(value = "审核状态", example = "0", notes = "0：未审核:1：审核通过，2：审核不通过")
    private String reviewState;

    /**
     * 资料添加时间
     */
    @ApiModelProperty(value = "添加时间", example = "2024-01-01 12:00:00")
    private LocalDateTime addTime;

    /**
     * 审核时间
     */
    @ApiModelProperty(value = "审核时间", example = "2024-01-02 12:00:00")
    private LocalDateTime reviewTime;

    /**
     * 审核人
     */
    @ApiModelProperty(value = "审核人", example = "admin")
    private String reviewBy;

    /**
     * 审核内容
     */
    @ApiModelProperty(value = "审核内容", example = "资料完整，通过审核")
    private String reviewCon;

    /**
     * 备注
     */
    @ApiModelProperty(value = "备注", example = "资料清晰可见")
    private String remark;

}


--- File: play-admin/src/main/java/com/starry/admin/modules/clerk/module/vo/PlayClerkDataReviewStateEditVo.java ---
package com.starry.admin.modules.clerk.module.vo;

import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import java.time.LocalDateTime;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.Pattern;
import lombok.Data;

/**
 * @author admin 动态审核
 */
@Data
@ApiModel(value = "店员资料审核状态修改参数", description = "修改店员资料审核状态的请求参数")
public class PlayClerkDataReviewStateEditVo {

    @NotBlank(message = "ID不能为空")
    @ApiModelProperty(value = "审核记录ID", required = true, example = "1", notes = "店员资料审核记录的ID")
    private String id;

    /**
     * 审核状态（0：未审核:1：审核通过，2：审核不通过）
     */
    @NotBlank(message = "审核状态不能为空")
    @Pattern(regexp = "[12]", message = "审核状态必须为1或2")
    @ApiModelProperty(value = "审核状态", required = true, example = "1", notes = "1：审核通过，2：审核不通过")
    private String reviewState;

    /**
     * 审核内容
     */
    @ApiModelProperty(value = "审核内容", example = "资料完整，通过审核", notes = "审核的备注内容或拒绝原因")
    private String reviewCon;

    /**
     * 审核时间
     */
    @ApiModelProperty(value = "审核时间", hidden = true, notes = "系统自动生成，无需传入")
    private LocalDateTime reviewTime = LocalDateTime.now();
}


--- File: play-admin/src/main/java/com/starry/admin/modules/clerk/module/vo/PlayClerkLevelAddVo.java ---
package com.starry.admin.modules.clerk.module.vo;

import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import javax.validation.constraints.NotBlank;
import lombok.Data;

/**
 * @author admin
 */
@Data
@ApiModel(value = "店员等级添加参数", description = "新增店员等级信息的请求参数")
public class PlayClerkLevelAddVo {

    /**
     * 等级名称
     */
    @NotBlank(message = "等级名称不能为空")
    @ApiModelProperty(value = "等级名称", required = true, example = "高级店员", notes = "店员等级的名称")
    private String name;

    /**
     * 首次固定单比例[0 - 100%]
     */
    @ApiModelProperty(value = "首次固定单比例", example = "70", notes = "首次固定单提成比例，范围0-100%")
    private Integer firstRegularRatio;

    /**
     * 非首次固定单比例[0 - 100%]
     */
    @ApiModelProperty(value = "非首次固定单比例", example = "60", notes = "非首次固定单提成比例，范围0-100%")
    private Integer notFirstRegularRatio;

    /**
     * 首次打赏比例[0 - 100%]
     */
    @ApiModelProperty(value = "首次打赏比例", example = "80", notes = "首次打赏提成比例，范围0-100%")
    private Integer firstRewardRatio;

    /**
     * 非首次打赏比例[0 - 100%]
     */
    @ApiModelProperty(value = "非首次打赏比例", example = "70", notes = "非首次打赏提成比例，范围0-100%")
    private Integer notFirstRewardRatio;

    /**
     * 首次随机单比例[0 - 100%]
     */
    @ApiModelProperty(value = "首次随机单比例", example = "75", notes = "首次随机单提成比例，范围0-100%")
    private Integer firstRandomRadio;

    /**
     * 非首次随机单比例[0 - 100%]
     */
    @ApiModelProperty(value = "非首次随机单比例", example = "65", notes = "非首次随机单提成比例，范围0-100%")
    private Integer notFirstRandomRadio;

}


--- File: play-admin/src/main/java/com/starry/admin/modules/clerk/module/vo/PlayClerkLevelEditVo.java ---
package com.starry.admin.modules.clerk.module.vo;

import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import javax.validation.constraints.NotBlank;
import lombok.Data;

/**
 * @author admin
 */
@Data
@ApiModel(value = "店员等级修改参数", description = "修改店员等级信息的请求参数")
public class PlayClerkLevelEditVo {

    /**
     * 等级名称
     */
    @NotBlank(message = "ID不能为空")
    @ApiModelProperty(value = "等级ID", required = true, example = "1", notes = "店员等级的ID")
    private String id;

    /**
     * 等级名称
     */
    @NotBlank(message = "等级名称不能为空")
    @ApiModelProperty(value = "等级名称", required = true, example = "高级店员", notes = "店员等级的名称")
    private String name;

    /**
     * 首次固定单比例[0 - 100%]
     */
    @ApiModelProperty(value = "首次固定单比例", example = "70", notes = "首次固定单提成比例，范围0-100%")
    private Integer firstRegularRatio;

    /**
     * 非首次固定单比例[0 - 100%]
     */
    @ApiModelProperty(value = "非首次固定单比例", example = "60", notes = "非首次固定单提成比例，范围0-100%")
    private Integer notFirstRegularRatio;

    /**
     * 首次打赏比例[0 - 100%]
     */
    @ApiModelProperty(value = "首次打赏比例", example = "80", notes = "首次打赏提成比例，范围0-100%")
    private Integer firstRewardRatio;

    /**
     * 非首次打赏比例[0 - 100%]
     */
    @ApiModelProperty(value = "非首次打赏比例", example = "70", notes = "非首次打赏提成比例，范围0-100%")
    private Integer notFirstRewardRatio;

    /**
     * 首次随机单比例[0 - 100%]
     */
    @ApiModelProperty(value = "首次随机单比例", example = "75", notes = "首次随机单提成比例，范围0-100%")
    private Integer firstRandomRadio;

    /**
     * 非首次随机单比例[0 - 100%]
     */
    @ApiModelProperty(value = "非首次随机单比例", example = "65", notes = "非首次随机单提成比例，范围0-100%")
    private Integer notFirstRandomRadio;

    @ApiModelProperty(value = "样式类型", example = "1", notes = "等级样式类型")
    private Integer styleType;

    @ApiModelProperty(value = "样式图片URL", example = "https://example.com/style.jpg", notes = "等级样式图片URL")
    private String styleImageUrl;

}


--- File: play-admin/src/main/java/com/starry/admin/modules/clerk/module/vo/PlayClerkLevelQueryReturnVo.java ---
package com.starry.admin.modules.clerk.module.vo;

import lombok.Data;

/**
 * 店员等级查询接口
 *
 * @author admin
 */
@Data
public class PlayClerkLevelQueryReturnVo {

    /**
     * 等级名称
     */
    private String name;

}


--- File: play-admin/src/main/java/com/starry/admin/modules/clerk/module/vo/PlayClerkTypeInfoAddVo.java ---
package com.starry.admin.modules.clerk.module.vo;

import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import javax.validation.constraints.NotNull;
import lombok.Data;

/**
 * @author admin
 * @since 2024/6/2 下午5:59
 **/
@Data
@ApiModel(value = "店员分类添加参数", description = "新增店员分类信息的请求参数")
public class PlayClerkTypeInfoAddVo {

    /**
     * 分类名称
     */
    @NotNull(message = "分类名称不能为空")
    @ApiModelProperty(value = "分类名称", required = true, example = "新人店员", notes = "店员分类的名称")
    private String typeName;

    /**
     * 是否在首页显示（0:不显示,1:显示）
     */
    @NotNull(message = "是否在首页显示不能为空")
    @ApiModelProperty(value = "是否在首页显示", required = true, example = "1", notes = "0:不显示,1:显示")
    private String homeDisplayed;

    /**
     * 排序
     */
    @NotNull(message = "排序不能为空")
    @ApiModelProperty(value = "排序", required = true, example = "1", notes = "数字越小排序越靠前")
    private Integer sort;

}


--- File: play-admin/src/main/java/com/starry/admin/modules/clerk/module/vo/PlayClerkTypeInfoQueryVo.java ---
package com.starry.admin.modules.clerk.module.vo;

import com.starry.common.domain.BasePageEntity;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import lombok.EqualsAndHashCode;

/**
 * @author admin
 * @since 2024/6/16 下午6:51
 **/
@EqualsAndHashCode(callSuper = true)
@Data
@ApiModel(value = "店员分类查询参数", description = "查询店员分类信息的条件参数")
public class PlayClerkTypeInfoQueryVo extends BasePageEntity {

    @ApiModelProperty(value = "分类ID", example = "1", notes = "店员分类的ID")
    private String id;
}


--- File: play-admin/src/main/java/com/starry/admin/modules/clerk/module/vo/PlayClerkTypeInfoReturnVo.java ---
package com.starry.admin.modules.clerk.module.vo;

import lombok.Data;

/**
 * @author admin
 * @since 2024/6/16 下午6:51
 **/
@Data
public class PlayClerkTypeInfoReturnVo {

    private String id;
    /**
     * 分类名称
     */
    private String typeName;

    /**
     * 是否在首页显示（0:不显示,1:显示）
     */
    private String homeDisplayed;

    /**
     * 排序
     */
    private Integer sort;

    /**
     * 店员数
     */
    private Integer clerkNumber;

}


--- File: play-admin/src/main/java/com/starry/admin/modules/clerk/module/vo/PlayClerkUnsettledWagesInfoQueryVo.java ---
package com.starry.admin.modules.clerk.module.vo;

import com.starry.common.domain.BasePageEntity;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import lombok.EqualsAndHashCode;

/**
 * @author admin
 * @since 2024/6/3 下午10:08
 **/
@EqualsAndHashCode(callSuper = true)
@Data
@ApiModel(value = "未结算工资查询参数", description = "查询店员未结算工资信息的条件参数")
public class PlayClerkUnsettledWagesInfoQueryVo extends BasePageEntity {

    /**
     * 店员ID
     */
    @ApiModelProperty(value = "店员ID", example = "1", notes = "特定店员的ID")
    private String clerkId;

    /**
     * 店员上架状态
     */
    @ApiModelProperty(value = "店员上架状态", example = "1", notes = "1：上架，0：下架")
    private String listingState;

}


--- File: play-admin/src/main/java/com/starry/admin/modules/clerk/module/vo/PlayClerkUnsettledWagesInfoReturnVo.java ---
package com.starry.admin.modules.clerk.module.vo;

import com.fasterxml.jackson.annotation.JsonIgnore;
import com.starry.admin.modules.order.module.entity.PlayOrderInfoEntity;
import java.math.BigDecimal;
import java.util.List;
import lombok.Data;

/**
 * @author admin
 * @since 2024/6/3 下午10:08
 **/
@Data
public class PlayClerkUnsettledWagesInfoReturnVo {

    private String clerkId;

    private String clerkNickname;

    @JsonIgnore
    private List<PlayOrderInfoEntity> orderInfoEntities;

    /**
     * 待开始订单-数量
     */
    private Integer orderState1Number;

    /**
     * 待开始订单-金额
     */
    private BigDecimal orderState1Money;

    /**
     * 服务中订单-数量
     */
    private Integer orderState2Number;

    /**
     * 服务中订单-金额
     */
    private BigDecimal orderState2Money;

    /**
     * 服务中订单-店员收入
     */
    private BigDecimal orderState2Revenue;

    /**
     * 已完成未结算订单-数量
     */
    private Integer orderState3Number;

    /**
     * 已完成未结算订单-金额
     */
    private BigDecimal orderState3Money;

    /**
     * 已完成未结算订单-店员收入
     */
    private BigDecimal orderState3Revenue;

}


--- File: play-admin/src/main/java/com/starry/admin/modules/clerk/module/vo/PlayClerkUserAddToWxVo.java ---
package com.starry.admin.modules.clerk.module.vo;

import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
import lombok.Data;

/**
 * @author admin
 */
@Data
public class PlayClerkUserAddToWxVo {

    /**
     * 店员昵称
     */
    @NotBlank(message = "昵称不能为空")
    private String nickname;

    /**
     * 性别[0:未知;1:男;2:女]
     */
    @NotNull(message = "性别不能为空")
    private Integer sex;

    /**
     * 年龄
     */
    @NotNull(message = "年龄不能为空")
    private int age;

    /**
     * 微信号
     */
    @NotBlank(message = "微信号不能为空")
    private String weChat;

    /**
     * 手机号码区号
     */
    @NotBlank(message = "微信号不能为空")
    private String areaCode;

    /**
     * 手机号码
     */
    @NotBlank(message = "微信号不能为空")
    private String phone;

    /**
     * 音频
     */
    @NotBlank(message = "音频不能为空")
    private String audioFrequency;

    /**
     * 所在国家
     */
    private String country = "中国";

    /**
     * 所在省份
     */
    @NotBlank(message = "省份不能为空")
    private String province;

    /**
     * 所在城市
     */
    @NotBlank(message = "城市不能为空")
    private String city;

    /**
     * 备注
     */
    private String remark;

    /**
     * 账户状态（1：审批通过，0：注册未审批）
     */
    private String accountState = "0";

}


--- File: play-admin/src/main/java/com/starry/admin/modules/clerk/module/vo/PlayClerkUserAddVo.java ---
package com.starry.admin.modules.clerk.module.vo;

import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
import lombok.Data;

/**
 * @author admin
 */
@Data
public class PlayClerkUserAddVo {

    /**
     * 账号ID
     */
    @NotBlank(message = "账号不能为空")
    private String playUserId;

    /**
     * 用户的标识，对当前公众号唯一
     */
    private String openid;

    /**
     * 店员昵称
     */
    @NotBlank(message = "昵称不能为空")
    private String nickname;

    /**
     * 店员等级
     */
    @NotBlank(message = "等级不能为空")
    private String levelId;

    /**
     * 性别[0:未知;1:男;2:女]
     */
    @NotNull(message = "性别不能为空")
    private Integer sex;

    /**
     * 头像
     */
    @NotBlank(message = "头像不能为空")
    private String avatar;

    /**
     * 音频
     */
    @NotBlank(message = "音频不能为空")
    private String audioFrequency;

    /**
     * 星座
     */
    @NotBlank(message = "星座不能为空")
    private String constellation;

    /**
     * 标签
     */
    private String label;

    /**
     * 个性签名
     */
    @NotBlank(message = "签名不能为空")
    private String signature;

    /**
     * 年龄
     */
    @NotNull(message = "年龄不能为空")
    private Long age;

    /**
     * 所在国家
     */
    @NotBlank(message = "国家不能为空")
    private String country;

    /**
     * 所在省份
     */
    @NotBlank(message = "省份不能为空")
    private String province;

    /**
     * 所在城市
     */
    @NotBlank(message = "城市不能为空")
    private String city;

    /**
     * 备注
     */
    private String remark;

}


--- File: play-admin/src/main/java/com/starry/admin/modules/clerk/module/vo/PlayClerkUserEditVo.java ---
package com.starry.admin.modules.clerk.module.vo;

import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
import lombok.Data;

/**
 * @author admin
 */
@Data
@ApiModel(value = "店员编辑对象", description = "修改店员基本信息的参数对象")
public class PlayClerkUserEditVo {

    /**
     * id
     */
    @NotBlank(message = "id不能为空")
    @ApiModelProperty(value = "店员ID", required = true, notes = "唯一标识")
    private String id;

    /**
     * 账号ID
     */
    @NotBlank(message = "账号不能为空")
    @ApiModelProperty(value = "账号ID", required = true, notes = "关联的用户账号ID")
    private String playUserId;

    /**
     * 用户的标识，对当前公众号唯一
     */
    @ApiModelProperty(value = "微信OpenID", notes = "用户在微信公众号中的唯一标识")
    private String openid;

    /**
     * 店员昵称
     */
    @NotBlank(message = "昵称不能为空")
    @ApiModelProperty(value = "店员昵称", required = true)
    private String nickname;

    /**
     * 店员等级
     */
    @NotBlank(message = "等级不能为空")
    @ApiModelProperty(value = "店员等级ID", required = true)
    private String levelId;

    /**
     * 性别[0:未知;1:男;2:女]
     */
    @NotNull(message = "性别不能为空")
    @ApiModelProperty(value = "性别", required = true, notes = "0:未知;1:男;2:女")
    private String sex;

    /**
     * 头像
     */
    @NotBlank(message = "头像不能为空")
    @ApiModelProperty(value = "头像", required = true, notes = "头像图片URL")
    private String avatar;

    /**
     * 音频
     */
    @NotBlank(message = "音频不能为空")
    @ApiModelProperty(value = "音频", required = true, notes = "音频文件URL")
    private String audioFrequency;

    /**
     * 星座
     */
    @NotBlank(message = "星座不能为空")
    @ApiModelProperty(value = "星座", required = true, example = "天蝎座")
    private String constellation;

    /**
     * 标签
     */
    @ApiModelProperty(value = "标签", notes = "店员个人标签，多个标签用逗号分隔")
    private String label;

    /**
     * 个性签名
     */
    @NotBlank(message = "签名不能为空")
    @ApiModelProperty(value = "个性签名", required = true)
    private String signature;

    /**
     * 年龄
     */
    @NotNull(message = "年龄不能为空")
    @ApiModelProperty(value = "年龄", required = true, example = "25")
    private Long age;

    /**
     * 所在国家
     */
    @NotBlank(message = "国家不能为空")
    @ApiModelProperty(value = "所在国家", required = true, example = "中国")
    private String country;

    /**
     * 所在省份
     */
    @NotBlank(message = "省份不能为空")
    @ApiModelProperty(value = "所在省份", required = true, example = "广东省")
    private String province;

    /**
     * 所在城市
     */
    @NotBlank(message = "城市不能为空")
    @ApiModelProperty(value = "所在城市", required = true, example = "深圳市")
    private String city;

    /**
     * 备注
     */
    @ApiModelProperty(value = "备注", notes = "店员的备注信息")
    private String remark;

}


--- File: play-admin/src/main/java/com/starry/admin/modules/clerk/module/vo/PlayClerkUserReviewQueryVo.java ---
package com.starry.admin.modules.clerk.module.vo;

import com.starry.common.domain.BasePageEntity;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import java.util.List;
import lombok.Data;
import lombok.EqualsAndHashCode;

/**
 * 店员申请审核
 *
 * @author admin
 * @since 2024/5/19 下午4:21
 **/
@EqualsAndHashCode(callSuper = true)
@Data
@ApiModel(value = "店员资料审核查询参数", description = "查询店员资料审核信息的条件参数")
public class PlayClerkUserReviewQueryVo extends BasePageEntity {

    /**
     * 店员ID
     */
    @ApiModelProperty(value = "店员ID", example = "1", notes = "特定店员的ID")
    private String clerkId;

    /**
     * 店员昵称
     */
    @ApiModelProperty(value = "店员昵称", example = "小明", notes = "店员的昵称，支持模糊查询")
    private String nickname;

    /**
     * 性别[0:未知;1:男;2:女]
     */
    @ApiModelProperty(value = "性别", example = "1", notes = "0:未知;1:男;2:女")
    private String sex;

    /**
     * 手机号码
     */
    @ApiModelProperty(value = "手机号码", example = "13800138000", notes = "店员的手机号码")
    private String phone;

    /**
     * 微信号
     */
    @ApiModelProperty(value = "微信号", example = "wx123456", notes = "店员的微信号")
    private String weiChatCode;

    /**
     * 审核状态（0：未审核:1：审核通过，2：审核不通过）
     */
    @ApiModelProperty(value = "审核状态", example = "0", notes = "0：未审核:1：审核通过，2：审核不通过")
    private String reviewState;

    /**
     * 申请时间
     */
    @ApiModelProperty(value = "申请时间范围", example = "['2024-01-01 00:00:00','2024-12-31 23:59:59']", notes = "申请时间范围，包含开始和结束时间")
    private List<String> addTime;
}


--- File: play-admin/src/main/java/com/starry/admin/modules/clerk/module/vo/PlayClerkUserReviewReturnVo.java ---
package com.starry.admin.modules.clerk.module.vo;

import com.baomidou.mybatisplus.annotation.TableField;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.starry.admin.common.conf.StringTypeHandler;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
import lombok.Data;
import org.springframework.format.annotation.DateTimeFormat;

/**
 * 店员申请审核
 *
 * @author admin
 * @since 2024/5/19 下午4:21
 **/
@Data
@ApiModel(value = "店员资料审核返回数据", description = "店员资料审核信息的返回数据")
public class PlayClerkUserReviewReturnVo {

    @ApiModelProperty(value = "审核记录ID", example = "1")
    private String id;

    /**
     * 店员ID
     */
    @ApiModelProperty(value = "店员ID", example = "1")
    private String clerkId;

    /**
     * 店员昵称
     */
    @ApiModelProperty(value = "店员昵称", example = "小明")
    private String clerkNickname;

    /**
     * 店员头像
     */
    @ApiModelProperty(value = "店员头像", example = "https://example.com/avatar.jpg")
    private String clerkAvatar;

    /**
     * 微信号码
     */
    @ApiModelProperty(value = "微信号", example = "wx123456")
    private String weiChatCode;

    /**
     * 手机号码
     */
    @ApiModelProperty(value = "手机号码", example = "13800138000")
    private String phone;

    /**
     * 昵称
     */
    @ApiModelProperty(value = "昵称", example = "小明")
    private String nickname;

    /**
     * 性别[0:未知;1:男;2:女]
     */
    @ApiModelProperty(value = "性别", example = "1", notes = "0:未知;1:男;2:女")
    private String sex;

    /**
     * 年龄
     */
    @ApiModelProperty(value = "年龄", example = "25")
    private Integer age;

    /**
     * 所在省份
     */
    @ApiModelProperty(value = "所在省份", example = "广东省")
    private String province;

    /**
     * 所在城市
     */
    @ApiModelProperty(value = "所在城市", example = "深圳市")
    private String city;

    /**
     * 备注（相关经验）
     */
    @ApiModelProperty(value = "备注", example = "有3年相关经验")
    private String remark;

    /**
     * 音频
     */
    @ApiModelProperty(value = "音频", example = "https://example.com/audio.mp3")
    private String audio;

    /**
     * 照片
     */
    @TableField(typeHandler = StringTypeHandler.class)
    @ApiModelProperty(value = "照片", example = "[\"https://example.com/photo1.jpg\",\"https://example.com/photo2.jpg\"]")
    private List<String> album = new ArrayList<>();

    /**
     * 申请时间
     */
    @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
    @ApiModelProperty(value = "申请时间", example = "2024-01-01 12:00:00")
    private LocalDateTime addTime;

    /**
     * 审核状态（0：未审核:1：审核通过，2：审核不通过）
     */
    @ApiModelProperty(value = "审核状态", example = "0", notes = "0：未审核:1：审核通过，2：审核不通过")
    private String reviewState;

    /**
     * 审核时间
     */
    @ApiModelProperty(value = "审核时间", example = "2024-01-02 12:00:00")
    private LocalDateTime reviewTime;

    /**
     * 审核人
     */
    @ApiModelProperty(value = "审核人", example = "admin")
    private String reviewBy;

    /**
     * 审核内容
     */
    @ApiModelProperty(value = "审核内容", example = "资料完整，通过审核")
    private String reviewCon;

    /**
     * 个性签名
     */
    @ApiModelProperty(value = "个性签名", example = "用心服务每一位客户")
    private String signature;

}


--- File: play-admin/src/main/java/com/starry/admin/modules/clerk/module/vo/PlayClerkUserReviewStateEditVo.java ---
package com.starry.admin.modules.clerk.module.vo;

import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import java.time.LocalDateTime;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.Pattern;
import lombok.Data;

/**
 * @author admin 动态审核
 */
@Data
@ApiModel(value = "店员资料审核状态修改参数", description = "修改店员资料审核状态的请求参数")
public class PlayClerkUserReviewStateEditVo {

    @NotBlank(message = "ID不能为空")
    @ApiModelProperty(value = "审核记录ID", required = true, example = "1", notes = "店员资料审核记录的ID")
    private String id;

    /**
     * 审核状态（0：未审核:1：审核通过，2：审核不通过）
     */
    @NotBlank(message = "审核状态不能为空")
    @Pattern(regexp = "[12]", message = "审核状态必须为1或2")
    @ApiModelProperty(value = "审核状态", required = true, example = "1", notes = "1：审核通过，2：审核不通过")
    private String reviewState;

    /**
     * 发布时间
     */
    @ApiModelProperty(value = "审核内容", example = "资料完整，通过审核", notes = "审核的备注内容或拒绝原因")
    private String reviewCon;

    /**
     * 审核时间
     */
    @ApiModelProperty(value = "审核时间", hidden = true, notes = "系统自动生成，无需传入")
    private LocalDateTime reviewTime = LocalDateTime.now();
}


--- File: play-admin/src/main/java/com/starry/admin/modules/clerk/module/vo/PlayClerkUserStateEditVo.java ---
package com.starry.admin.modules.clerk.module.vo;

import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import javax.validation.constraints.NotBlank;
import lombok.Data;

/**
 * @author admin
 */
@Data
@ApiModel(value = "店员状态编辑对象", description = "修改店员状态信息的参数对象")
public class PlayClerkUserStateEditVo {

    /**
     * id
     */
    @NotBlank(message = "id不能为空")
    @ApiModelProperty(value = "店员ID", required = true, notes = "唯一标识")
    private String id;

    /**
     * 是否推荐状态（1：已推荐，0：未推荐）
     */
    @ApiModelProperty(value = "是否推荐", notes = "1：已推荐，0：未推荐")
    private String recommendationState;

    private String avatar;

    /**
     * 是否置顶状态（1：已置顶，0：未置顶）
     */
    @ApiModelProperty(value = "是否置顶", notes = "1：已置顶，0：未置顶")
    private String pinToTopState;

    /**
     * 在线状态【1：在线，0：离线】
     */
    @ApiModelProperty(value = "在线状态", notes = "1：在线，0：离线")
    private String onlineState;

    /**
     * 上架状态【1：上架，0：下架】
     */
    @ApiModelProperty(value = "上架状态", notes = "1：上架，0：下架")
    private String listingState;

    /**
     * 显示状态【1：显示，0：隐藏】
     */
    @ApiModelProperty(value = "显示状态", notes = "1：显示，0：隐藏")
    private String displayState;

    /**
     * 实名状态【1：已实名，0：未实名】
     */
    @ApiModelProperty(value = "实名状态", notes = "1：已实名，0：未实名")
    private String realState;

    /**
     * 是否必须实名【0:非必须实名;1:必须实名;2:跟随店铺设置】
     */
    @ApiModelProperty(value = "是否必须实名", notes = "0:非必须实名;1:必须实名;2:跟随店铺设置")
    private String mandatoryRealState;

    /**
     * 随机接单状态【1：允许，0：禁止】
     */
    @ApiModelProperty(value = "随机接单状态", notes = "1：允许，0：禁止")
    private String randomOrderState;

    /**
     * 是否固定等级(0:固定等级，1:不固定)
     */
    @ApiModelProperty(value = "是否固定等级", notes = "0:固定等级，1:不固定")
    private String fixingLevel;

    /**
     * 在职状态（1：在职，0：离职）
     */
    @ApiModelProperty(value = "在职状态", notes = "1：在职，0：离职", example = "1")
    private String onboardingState = "1";

    /**
     * 在职状态（1：在职，0：离职）
     */
    @ApiModelProperty(value = "店员等级ID")
    private String levelId;

    @ApiModelProperty(value = "备注", notes = "店员状态的备注信息")
    private String remark;

    @ApiModelProperty(value = "性别", notes = "0:未知;1:男;2:女")
    private String sex;

}


--- File: play-admin/src/main/java/com/starry/admin/modules/clerk/module/vo/PlayClerkWagesInfoQueryVo.java ---
package com.starry.admin.modules.clerk.module.vo;

import com.starry.common.domain.BasePageEntity;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import lombok.EqualsAndHashCode;

/**
 * @author admin
 * @since 2024/6/3 下午10:08
 **/
@EqualsAndHashCode(callSuper = true)
@Data
@ApiModel(value = "工资历史查询参数", description = "查询店员历史工资结算信息的条件参数")
public class PlayClerkWagesInfoQueryVo extends BasePageEntity {

    /**
     * 店员ID
     */
    @ApiModelProperty(value = "店员ID", example = "1", notes = "特定店员的ID")
    private String clerkId;

    /**
     * 店员分组
     */
    @ApiModelProperty(value = "店员分组ID", example = "1", notes = "店员所属分组的ID")
    private String groupId;
    /**
     * 店员上架状态
     */
    @ApiModelProperty(value = "店员上架状态", example = "1", notes = "1：上架，0：下架")
    private String listingState;

    /**
     * 统计时间
     */
    @ApiModelProperty(value = "统计时间", example = "2024-06", notes = "工资结算的月份，格式为yyyy-MM")
    private String settlementDate;
}


--- File: play-admin/src/main/java/com/starry/admin/modules/clerk/module/vo/PlayClerkWagesInfoReturnVo.java ---
package com.starry.admin.modules.clerk.module.vo;

import com.fasterxml.jackson.annotation.JsonFormat;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.time.LocalDateTime;
import lombok.Data;
import org.springframework.format.annotation.DateTimeFormat;

/**
 * @author admin
 * @since 2024/6/3 下午10:08
 **/
@Data
public class PlayClerkWagesInfoReturnVo {

    /**
     * UUID
     */
    private String id;

    /**
     * 租户ID
     */
    private String tenantId;

    /**
     * 店员ID
     */
    private String clerkId;

    /**
     * 店员昵称
     */
    private String clerkNickname;

    /**
     * 是否为往期统计(1:是;0:不是)
     **/
    private String historicalStatistics;

    /**
     * 统计序号
     */
    private Long serialNumber;

    /**
     * 排行序号
     */
    private Long rankingIndex;

    /**
     * 开始统计时间
     */
    @DateTimeFormat(pattern = "yyyy-MM-dd")
    @JsonFormat(pattern = "yyyy-MM-dd", timezone = "GMT+8")
    private LocalDate startCountDate;

    /**
     * 结束统计时间
     */
    @DateTimeFormat(pattern = "yyyy-MM-dd")
    @JsonFormat(pattern = "yyyy-MM-dd", timezone = "GMT+8")
    private LocalDate endCountDate;

    /**
     * 结算时间
     */
    @DateTimeFormat(pattern = "yyyy-MM-dd")
    @JsonFormat(pattern = "yyyy-MM-dd", timezone = "GMT+8")
    private LocalDateTime settlementDate;

    /**
     * 订单总数
     */
    private Integer orderNumber;

    /**
     * 订单金额
     */
    private BigDecimal finalAmount;

    /**
     * 续单数
     */
    private Integer orderContinueNumber;

    /**
     * 续单金额
     */
    private BigDecimal orderContinueMoney;

    /**
     * 超时未接单数
     */
    private Integer ordersExpiredNumber;

    /**
     * 店员收入
     */
    private BigDecimal estimatedRevenue = BigDecimal.ZERO;

}


--- File: play-admin/src/main/java/com/starry/admin/modules/clerk/service/IPlayAvatarFrameInfoService.java ---
package com.starry.admin.modules.clerk.service;

import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.service.IService;
import com.starry.admin.modules.clerk.module.entity.PlayAvatarFrameInfoEntity;
import com.starry.admin.modules.clerk.module.vo.PlayAvatarFrameInfoQueryVo;
import java.util.List;

/**
 * 店员头像框Service接口
 *
 * @author admin
 * @since 2024-06-06
 */
public interface IPlayAvatarFrameInfoService extends IService<PlayAvatarFrameInfoEntity> {
    /**
     * 查询店员头像框
     *
     * @param id
     *            店员头像框主键
     * @return 店员头像框
     */
    PlayAvatarFrameInfoEntity selectPlayAvatarFrameInfoById(String id);

    /**
     * 查询店员头像框列表
     *
     * @return 店员头像框集合
     */
    List<PlayAvatarFrameInfoEntity> selectAll();

    /**
     * 查询店员头像框列表
     *
     * @param vo
     *            店员头像框
     * @return 店员头像框集合
     */
    IPage<PlayAvatarFrameInfoEntity> selectByPage(PlayAvatarFrameInfoQueryVo vo);

    /**
     * 查询店员头像框列表
     *
     * @param playAvatarFrameInfo
     *            店员头像框
     * @return 店员头像框集合
     */
    IPage<PlayAvatarFrameInfoEntity> selectPlayAvatarFrameInfoByPage(PlayAvatarFrameInfoEntity playAvatarFrameInfo);

    /**
     * 新增店员头像框
     *
     * @param playAvatarFrameInfo
     *            店员头像框
     * @return 结果
     */
    boolean create(PlayAvatarFrameInfoEntity playAvatarFrameInfo);

    /**
     * 修改店员头像框
     *
     * @param playAvatarFrameInfo
     *            店员头像框
     * @return 结果
     */
    boolean update(PlayAvatarFrameInfoEntity playAvatarFrameInfo);

    /**
     * 批量删除店员头像框
     *
     * @param ids
     *            需要删除的店员头像框主键集合
     * @return 结果
     */
    int deletePlayAvatarFrameInfoByIds(String[] ids);

    /**
     * 删除店员头像框信息
     *
     * @param id
     *            店员头像框主键
     * @return 结果
     */
    int deletePlayAvatarFrameInfoById(String id);
}


--- File: play-admin/src/main/java/com/starry/admin/modules/clerk/service/IPlayClerkArticleInfoService.java ---
package com.starry.admin.modules.clerk.service;

import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.service.IService;
import com.starry.admin.modules.clerk.module.entity.PlayClerkArticleInfoEntity;
import com.starry.admin.modules.clerk.module.vo.PlayClerkArticleQueryVo;
import com.starry.admin.modules.clerk.module.vo.PlayClerkArticleReturnVo;
import com.starry.admin.modules.weichat.entity.article.PlayClerkArticleCustomQueryVo;
import com.starry.admin.modules.weichat.entity.article.PlayClerkArticleCustomReturnVo;

/**
 * 店员动态信息Service接口
 *
 * @author admin
 * @since 2024-05-04
 */
public interface IPlayClerkArticleInfoService extends IService<PlayClerkArticleInfoEntity> {
    /**
     * 查询店员动态信息
     *
     * @param id
     *            店员动态信息主键
     * @return 店员动态信息
     */
    PlayClerkArticleInfoEntity selectPlayClerkArticleInfoById(String id);

    /**
     * 查询店员动态信息列表
     *
     * @param vo
     *            店员动态信息查询对象
     * @param searchByYourself
     *            查询人是否是自己
     *
     * @return 店员动态信息集合
     */
    IPage<PlayClerkArticleReturnVo> selectByPage(PlayClerkArticleQueryVo vo, boolean searchByYourself);

    /**
     * 顾客查询动态列表
     *
     * @param customId
     *            顾客ID
     * @param customId
     *            动态查询对象
     * @return PlayClerkArticleCustomReturnVo
     */
    IPage<PlayClerkArticleCustomReturnVo> customSelectByPage(PlayClerkArticleCustomQueryVo vo, String customId);

    /**
     * 新增店员动态信息
     *
     * @param playClerkArticleInfo
     *            店员动态信息
     * @return 结果
     */
    boolean create(PlayClerkArticleInfoEntity playClerkArticleInfo);

    /**
     * 修改店员动态信息
     *
     * @param playClerkArticleInfo
     *            店员动态信息
     * @return 结果
     */
    boolean update(PlayClerkArticleInfoEntity playClerkArticleInfo);

    /**
     * 批量删除店员动态信息
     *
     * @param ids
     *            需要删除的店员动态信息主键集合
     * @return 结果
     */
    int deletePlayClerkArticleInfoByIds(String[] ids);

    /**
     * 删除店员动态信息信息
     *
     * @param id
     *            店员动态信息主键
     * @return 结果
     */
    int deletePlayClerkArticleInfoById(String id);
}


--- File: play-admin/src/main/java/com/starry/admin/modules/clerk/service/IPlayClerkClassificationInfoService.java ---
package com.starry.admin.modules.clerk.service;

import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.service.IService;
import com.starry.admin.modules.clerk.module.entity.PlayClerkClassificationInfoEntity;
import java.util.List;

/**
 * 店员分类Service接口
 *
 * @author admin
 * @since 2024-04-06
 */
public interface IPlayClerkClassificationInfoService extends IService<PlayClerkClassificationInfoEntity> {
    /**
     * 查询店员分类
     *
     * @param id
     *            店员分类主键
     * @return 店员分类
     */
    PlayClerkClassificationInfoEntity selectPlayClerkClassificationInfoById(String id);

    /**
     * 查询店员分类列表
     *
     * @return 店员分类集合
     */
    List<PlayClerkClassificationInfoEntity> selectAll();

    /**
     * 查询店员分类列表
     *
     * @param playClerkClassificationInfo
     *            店员分类
     * @return 店员分类集合
     */
    IPage<PlayClerkClassificationInfoEntity> selectPlayClerkClassificationInfoByPage(
            PlayClerkClassificationInfoEntity playClerkClassificationInfo);

    /**
     * 新增店员分类
     *
     * @param playClerkClassificationInfo
     *            店员分类
     * @return 结果
     */
    boolean create(PlayClerkClassificationInfoEntity playClerkClassificationInfo);

    /**
     * 修改店员分类
     *
     * @param playClerkClassificationInfo
     *            店员分类
     * @return 结果
     */
    boolean update(PlayClerkClassificationInfoEntity playClerkClassificationInfo);

    /**
     * 批量删除店员分类
     *
     * @param ids
     *            需要删除的店员分类主键集合
     * @return 结果
     */
    int deletePlayClerkClassificationInfoByIds(String[] ids);

    /**
     * 删除店员分类信息
     *
     * @param id
     *            店员分类主键
     * @return 结果
     */
    int deletePlayClerkClassificationInfoById(String id);
}


--- File: play-admin/src/main/java/com/starry/admin/modules/clerk/service/IPlayClerkCommodityService.java ---
package com.starry.admin.modules.clerk.service;

import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.service.IService;
import com.starry.admin.modules.clerk.module.entity.PlayClerkCommodityEntity;
import java.util.List;

/**
 * 陪聊服务项目Service接口
 *
 * @author admin
 * @since 2024-03-31
 */
public interface IPlayClerkCommodityService extends IService<PlayClerkCommodityEntity> {

    /**
     * 初始化当前陪聊服务项目
     *
     * @param playUserId
     *            账号ID
     */
    void initClerkCommodity(String playUserId);

    /**
     * 根据用户ID，查询当前用户的服务项目类型
     *
     * @param userId
     *            用户ID
     * @param enablingState
     *            服务启动状态[0:停用;1:启用]
     * @return 服务项目类型
     */
    List<String> getClerkCommodityList(String userId, String enablingState);

    /**
     * 根据用户ID，查询当前店员所有服务项目
     *
     * @param userId
     *            用户ID
     * @param enablingState
     *            服务启动状态[0:停用;1:启用]
     * @return List<PlayClerkCommodityEntity>
     */
    List<PlayClerkCommodityEntity> selectCommodityTypeByUser(String userId, String enablingState);

    /**
     * 根据用户ID，查询当前用户的服务项目
     *
     * @param playUserId
     *            用户ID
     * @return List<PlayClerkCommodityEntity>
     */
    List<PlayClerkCommodityEntity> selectByUser(String playUserId);

    /**
     * 查询当前陪聊所有服务项目
     *
     * @return List<PlayClerkCommodityEntity>
     */
    List<PlayClerkCommodityEntity> selectAll();

    /**
     * 启停当前陪聊服务项目
     *
     * @param type
     *            项目名称
     * @param enablingState
     *            启停状态
     * @param clerkUserId
     *            陪聊ID
     */
    void startStopClerkItem(String type, String enablingState, String clerkUserId);

    /**
     * 查询陪聊服务项目
     *
     * @param id
     *            陪聊服务项目主键
     * @return 陪聊服务项目
     */
    PlayClerkCommodityEntity selectPlayClerkCommodityById(String id);

    /**
     * 查询陪聊服务项目列表
     *
     * @param playClerkCommodity
     *            陪聊服务项目
     * @return 陪聊服务项目集合
     */
    IPage<PlayClerkCommodityEntity> selectPlayClerkCommodityByPage(PlayClerkCommodityEntity playClerkCommodity);

    /**
     * 新增陪聊服务项目
     *
     * @param playClerkCommodity
     *            陪聊服务项目
     * @return 结果
     */
    boolean create(PlayClerkCommodityEntity playClerkCommodity);

    /**
     * 修改陪聊服务项目
     *
     * @param playClerkCommodity
     *            陪聊服务项目
     * @return 结果
     */
    boolean update(PlayClerkCommodityEntity playClerkCommodity);

    /**
     * 批量删除陪聊服务项目
     *
     * @param ids
     *            需要删除的陪聊服务项目主键集合
     * @return 结果
     */
    int deletePlayClerkCommodityByIds(String[] ids);

    /**
     * 删除陪聊服务项目信息
     *
     * @param id
     *            陪聊服务项目主键
     * @return 结果
     */
    int deletePlayClerkCommodityById(String id);
}


--- File: play-admin/src/main/java/com/starry/admin/modules/clerk/service/IPlayClerkDataReviewInfoService.java ---
package com.starry.admin.modules.clerk.service;

import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.service.IService;
import com.starry.admin.modules.clerk.module.entity.PlayClerkDataReviewInfoEntity;
import com.starry.admin.modules.clerk.module.vo.PlayClerkDataReviewQueryVo;
import com.starry.admin.modules.clerk.module.vo.PlayClerkDataReviewReturnVo;
import com.starry.admin.modules.clerk.module.vo.PlayClerkDataReviewStateEditVo;
import java.util.List;

/**
 * 店员资料审核Service接口
 *
 * @author admin
 * @since 2024-05-19
 */
public interface IPlayClerkDataReviewInfoService extends IService<PlayClerkDataReviewInfoEntity> {
    /**
     * 查询店员资料审核
     *
     * @param id
     *            店员资料审核主键
     * @return 店员资料审核
     */
    PlayClerkDataReviewInfoEntity selectPlayClerkDataReviewInfoById(String id);

    /**
     * 查询店员资料申请
     *
     * @param clerkId
     *            店员ID
     * @param reviewState
     *            审核状态（0：未审核:1：审核通过，2：审核不通过）
     * @return PlayClerkDataReviewInfoEntity
     */
    List<PlayClerkDataReviewInfoEntity> queryByClerkId(String clerkId, String reviewState);

    /**
     * 查询店员资料申请
     *
     * @param clerkId
     *            店员ID
     * @param dataType
     *            资料类型[0:昵称;1:头像;2:相册;3:录音]
     * @param reviewState
     *            审核状态（0：未审核:1：审核通过，2：审核不通过）
     * @return PlayClerkDataReviewInfoEntity
     */
    PlayClerkDataReviewInfoEntity queryByClerkId(String clerkId, String dataType, String reviewState);

    /**
     * 查询店员资料审核列表
     *
     * @param vo
     *            店员资料审核
     * @return 店员资料审核集合
     */
    IPage<PlayClerkDataReviewReturnVo> selectByPage(PlayClerkDataReviewQueryVo vo);

    /**
     * 新增店员资料审核
     *
     * @param playClerkDataReviewInfo
     *            店员资料审核
     * @return 结果
     */
    boolean create(PlayClerkDataReviewInfoEntity playClerkDataReviewInfo);

    /**
     * 修改店员资料审核状态
     *
     * @param vo
     *            PlayClerkDataReviewStateEditVo
     */
    void updateDataReviewState(PlayClerkDataReviewStateEditVo vo);
    /**
     * 修改店员资料审核
     *
     * @param playClerkDataReviewInfo
     *            店员资料审核
     * @return 结果
     */
    boolean update(PlayClerkDataReviewInfoEntity playClerkDataReviewInfo);

    /**
     * 批量删除店员资料审核
     *
     * @param ids
     *            需要删除的店员资料审核主键集合
     * @return 结果
     */
    int deletePlayClerkDataReviewInfoByIds(String[] ids);

    /**
     * 删除店员资料审核信息
     *
     * @param id
     *            店员资料审核主键
     * @return 结果
     */
    int deletePlayClerkDataReviewInfoById(String id);
}


--- File: play-admin/src/main/java/com/starry/admin/modules/clerk/service/IPlayClerkLevelInfoService.java ---
package com.starry.admin.modules.clerk.service;

import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.service.IService;
import com.starry.admin.modules.clerk.module.entity.PlayClerkLevelInfoEntity;
import com.starry.admin.modules.system.module.entity.SysTenantEntity;
import java.util.List;

/**
 * 店员等级Service接口
 *
 * @author admin
 * @since 2024-03-30
 */
public interface IPlayClerkLevelInfoService extends IService<PlayClerkLevelInfoEntity> {

    /**
     * 初始化店员等级
     *
     * @param sysTenantEntity
     *            租户信息
     * @author admin
     * @since 2024/7/19 15:13
     **/
    void initDefaultLevel(SysTenantEntity sysTenantEntity);

    /**
     * 获取新增陪聊时，默认最低等级ID
     *
     * @return PlayClerkLevelInfoEntity
     */
    PlayClerkLevelInfoEntity getDefaultLevel();

    /**
     * 查询店员等级
     *
     * @param id
     *            店员等级主键
     * @return 店员等级
     */
    PlayClerkLevelInfoEntity selectPlayClerkLevelInfoById(String id);

    /**
     * 查询店员等级列表
     *
     * @return 店员等级集合
     */
    List<PlayClerkLevelInfoEntity> selectAll();

    /**
     * 分页查询店员等级列表
     *
     * @param playClerkLevelInfo
     *            店员等级
     * @return 店员等级集合
     */
    IPage<PlayClerkLevelInfoEntity> selectPlayClerkLevelInfoByPage(PlayClerkLevelInfoEntity playClerkLevelInfo);

    /**
     * 新增店员等级
     *
     * @param playClerkLevelInfo
     *            店员等级
     * @return 结果
     */
    boolean create(PlayClerkLevelInfoEntity playClerkLevelInfo);

    /**
     * 修改店员等级
     *
     * @param playClerkLevelInfo
     *            店员等级
     * @return 结果
     */
    boolean update(PlayClerkLevelInfoEntity playClerkLevelInfo);

    /**
     * 批量删除店员等级
     *
     * @param ids
     *            需要删除的店员等级主键集合
     * @return 结果
     */
    int deletePlayClerkLevelInfoByIds(String[] ids);

    /**
     * 删除店员等级信息
     *
     * @param id
     *            店员等级主键
     * @return 结果
     */
    int deletePlayClerkLevelInfoById(String id);

    /**
     * 查询最大等级
     *
     * @return 最大等级
     */
    int selectMaxLevel();

    /**
     * 删除最大等级
     */
    void delMaxLevelByLevel(Integer level);
}


--- File: play-admin/src/main/java/com/starry/admin/modules/clerk/service/IPlayClerkPkService.java ---
package com.starry.admin.modules.clerk.service;

import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.service.IService;
import com.starry.admin.modules.clerk.module.entity.PlayClerkPkEntity;

/**
 * 店员pkService接口
 *
 * @author admin
 * @since 2024-08-02
 */
public interface IPlayClerkPkService extends IService<PlayClerkPkEntity> {
    /**
     * 查询店员pk
     *
     * @param id
     *            店员pk主键
     * @return 店员pk
     */
    PlayClerkPkEntity selectPlayClerkPkById(String id);

    /**
     * 查询店员pk列表
     *
     * @param playClerkPk
     *            店员pk
     * @return 店员pk集合
     */
    IPage<PlayClerkPkEntity> selectPlayClerkPkByPage(PlayClerkPkEntity playClerkPk);

    /**
     * 新增店员pk
     *
     * @param playClerkPk
     *            店员pk
     * @return 结果
     */
    boolean create(PlayClerkPkEntity playClerkPk);

    /**
     * 修改店员pk
     *
     * @param playClerkPk
     *            店员pk
     * @return 结果
     */
    boolean update(PlayClerkPkEntity playClerkPk);

    /**
     * 批量删除店员pk
     *
     * @param ids
     *            需要删除的店员pk主键集合
     * @return 结果
     */
    int deletePlayClerkPkByIds(String[] ids);

    /**
     * 删除店员pk信息
     *
     * @param id
     *            店员pk主键
     * @return 结果
     */
    int deletePlayClerkPkById(String id);
}


--- File: play-admin/src/main/java/com/starry/admin/modules/clerk/service/IPlayClerkRankingInfoService.java ---
package com.starry.admin.modules.clerk.service;

import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.service.IService;
import com.starry.admin.modules.clerk.module.entity.IPlayClerkRankingInfoQueryVo;
import com.starry.admin.modules.clerk.module.entity.IPlayClerkRankingInfoReturnVo;
import com.starry.admin.modules.clerk.module.entity.PlayClerkRankingInfoEntity;
import com.starry.admin.modules.weichat.entity.order.PlayOrderHistoryRankingReturnVo;
import com.starry.admin.modules.weichat.entity.order.PlayOrderRankingReturnVo;
import java.time.LocalDate;
import java.util.List;

/**
 * 店员排行Service接口
 *
 * @author admin
 * @since 2024-05-25
 */
public interface IPlayClerkRankingInfoService extends IService<PlayClerkRankingInfoEntity> {

    /**
     * 分页查询店员排行
     *
     * @param vo
     *            店员排行查询对象
     * @return 店员排查询对象
     */
    IPage<IPlayClerkRankingInfoReturnVo> selectByPage(IPlayClerkRankingInfoQueryVo vo);

    /**
     * 查询店员排行
     *
     * @param id
     *            店员排行主键
     * @return 店员排行
     */
    PlayClerkRankingInfoEntity selectPlayClerkRankingInfoById(String id);

    /**
     * 查询店员排行列表
     *
     * @param playClerkRankingInfo
     *            店员排行
     * @return 店员排行集合
     */
    IPage<PlayClerkRankingInfoEntity> selectPlayClerkRankingInfoByPage(PlayClerkRankingInfoEntity playClerkRankingInfo);

    /**
     * 根据统计批次更新排名数据
     *
     * @param weeklyRanking
     *            日排名还是周排名（0:每日排名;1:每周排名）
     * @param serialNumber
     *            统计批次
     * @author admin
     * @since 2024/6/12 15:47
     **/
    void updateClerkRankingInfo(String weeklyRanking, Integer serialNumber);

    /**
     * 查询当前排行榜最后一个批次值
     *
     * @param weeklyRanking
     *            日排名还是周排名（0:每日排名;1:每周排名）
     * @return Integer 当前排行榜最后一个批次值
     * @author admin
     * @since 2024/6/7 11:53
     **/
    Integer selectSerialNumber(String weeklyRanking);

    /**
     * 查询当前排行榜最大批次
     *
     * @param weeklyRanking
     *            日排名还是周排名（0:每日排名;1:每周排名）
     * @return List<com.starry.admin.modules.clerk.module.entity.PlayClerkRankingInfoEntity>
     * @author admin
     * @since 2024/6/7 11:55
     **/
    List<PlayClerkRankingInfoEntity> selectMaxSerialNumber(String weeklyRanking);

    /**
     * 根据时间查询店员排行
     *
     * @param clerkId
     *            店员ID
     * @param startTime
     *            开始时间
     * @param endTime
     *            结束时间
     * @return 店员排行集合
     */
    PlayClerkRankingInfoEntity selectByTime(String clerkId, LocalDate startTime, LocalDate endTime);

    /**
     * 店员分页查询本期排行信息
     *
     * @param clerkId
     *            店员ID
     * @return 店员排行集合
     */
    PlayOrderRankingReturnVo selectCurrentRanking(String clerkId);

    /**
     * 店员分页查询本期历史排行
     *
     * @param clerkId
     *            店员ID
     * @return 店员排行集合
     */
    List<PlayOrderHistoryRankingReturnVo> selectHistoryRanking(String clerkId);

    /**
     * 新增店员排行
     *
     * @param playClerkRankingInfo
     *            店员排行
     * @return 结果
     */
    boolean create(PlayClerkRankingInfoEntity playClerkRankingInfo);

    /**
     * 修改店员排行
     *
     * @param playClerkRankingInfo
     *            店员排行
     * @return 结果
     */
    boolean update(PlayClerkRankingInfoEntity playClerkRankingInfo);

    /**
     * 批量删除店员排行
     *
     * @param ids
     *            需要删除的店员排行主键集合
     * @return 结果
     */
    int deletePlayClerkRankingInfoByIds(String[] ids);

    /**
     * 删除店员排行信息
     *
     * @param id
     *            店员排行主键
     * @return 结果
     */
    int deletePlayClerkRankingInfoById(String id);
}


--- File: play-admin/src/main/java/com/starry/admin/modules/clerk/service/IPlayClerkTypeInfoService.java ---
package com.starry.admin.modules.clerk.service;

import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.service.IService;
import com.starry.admin.modules.clerk.module.entity.PlayClerkTypeInfoEntity;
import com.starry.admin.modules.clerk.module.vo.PlayClerkTypeInfoQueryVo;
import com.starry.admin.modules.clerk.module.vo.PlayClerkTypeInfoReturnVo;
import java.util.List;

/**
 * 店员分类信息Service接口
 *
 * @author admin
 * @since 2024-05-31
 */
public interface IPlayClerkTypeInfoService extends IService<PlayClerkTypeInfoEntity> {
    /**
     * 查询店员分类信息
     *
     * @param id
     *            店员分类信息主键
     * @return 店员分类信息
     */
    PlayClerkTypeInfoEntity selectPlayClerkTypeInfoById(String id);

    /**
     * 查询店员分类信息列表
     *
     * @return 店员分类信息集合
     */
    List<PlayClerkTypeInfoEntity> selectByAll();

    /**
     * 查询店员分类信息列表
     *
     * @param vo
     *            店员分类信息
     * @return 店员分类信息集合
     */
    IPage<PlayClerkTypeInfoReturnVo> selectByPage(PlayClerkTypeInfoQueryVo vo);

    /**
     * 新增店员分类信息
     *
     * @param playClerkTypeInfo
     *            店员分类信息
     * @return 结果
     */
    boolean create(PlayClerkTypeInfoEntity playClerkTypeInfo);

    /**
     * 修改店员分类信息
     *
     * @param playClerkTypeInfo
     *            店员分类信息
     * @return 结果
     */
    boolean update(PlayClerkTypeInfoEntity playClerkTypeInfo);

    /**
     * 批量删除店员分类信息
     *
     * @param ids
     *            需要删除的店员分类信息主键集合
     * @return 结果
     */
    int deletePlayClerkTypeInfoByIds(String[] ids);

    /**
     * 删除店员分类信息信息
     *
     * @param id
     *            店员分类信息主键
     * @return 结果
     */
    int deletePlayClerkTypeInfoById(String id);
}


--- File: play-admin/src/main/java/com/starry/admin/modules/clerk/service/IPlayClerkTypeUserInfoService.java ---
package com.starry.admin.modules.clerk.service;

import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.service.IService;
import com.starry.admin.modules.clerk.module.entity.PlayClerkTypeUserInfoEntity;

/**
 * 店员和分类关系Service接口
 *
 * @author admin
 * @since 2024-05-31
 */
public interface IPlayClerkTypeUserInfoService extends IService<PlayClerkTypeUserInfoEntity> {
    /**
     * 查询店员和分类关系
     *
     * @param id
     *            店员和分类关系主键
     * @return 店员和分类关系
     */
    PlayClerkTypeUserInfoEntity selectPlayClerkTypeUserInfoById(String id);

    /**
     * 查询店员和分类关系列表
     *
     * @param playClerkTypeUserInfo
     *            店员和分类关系
     * @return 店员和分类关系集合
     */
    IPage<PlayClerkTypeUserInfoEntity> selectPlayClerkTypeUserInfoByPage(
            PlayClerkTypeUserInfoEntity playClerkTypeUserInfo);

    /**
     * 新增店员和分类关系
     *
     * @param playClerkTypeUserInfo
     *            店员和分类关系
     * @return 结果
     */
    boolean create(PlayClerkTypeUserInfoEntity playClerkTypeUserInfo);

    /**
     * 修改店员和分类关系
     *
     * @param playClerkTypeUserInfo
     *            店员和分类关系
     * @return 结果
     */
    boolean update(PlayClerkTypeUserInfoEntity playClerkTypeUserInfo);

    /**
     * 批量删除店员和分类关系
     *
     * @param ids
     *            需要删除的店员和分类关系主键集合
     * @return 结果
     */
    int deletePlayClerkTypeUserInfoByIds(String[] ids);

    /**
     * 删除店员和分类关系信息
     *
     * @param id
     *            店员和分类关系主键
     * @return 结果
     */
    int deletePlayClerkTypeUserInfoById(String id);
}


--- File: play-admin/src/main/java/com/starry/admin/modules/clerk/service/IPlayClerkUserInfoService.java ---
package com.starry.admin.modules.clerk.service;

import com.alibaba.fastjson2.JSONObject;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.service.IService;
import com.starry.admin.modules.clerk.module.entity.PlayClerkLevelInfoEntity;
import com.starry.admin.modules.clerk.module.entity.PlayClerkUserInfoEntity;
import com.starry.admin.modules.clerk.module.entity.PlayClerkUserQueryVo;
import com.starry.admin.modules.clerk.module.entity.PlayClerkUserReturnVo;
import com.starry.admin.modules.clerk.module.vo.PlayClerkUnsettledWagesInfoQueryVo;
import com.starry.admin.modules.clerk.module.vo.PlayClerkUnsettledWagesInfoReturnVo;
import com.starry.admin.modules.statistics.module.vo.PlayClerkPerformanceInfoQueryVo;
import com.starry.admin.modules.weichat.entity.PlayClerkUserLoginResponseVo;
import com.starry.admin.modules.weichat.entity.clerk.PlayClerkUserInfoQueryVo;
import com.starry.admin.modules.weichat.entity.clerk.PlayClerkUserInfoResultVo;
import java.math.BigDecimal;
import java.util.List;

/**
 * 店员Service接口
 *
 * @author admin
 * @since 2024-03-30
 */
public interface IPlayClerkUserInfoService extends IService<PlayClerkUserInfoEntity> {

    /**
     * 根据分组ID查询店员列表
     *
     * @param typeId
     *            分类ID
     * @return 店员列表
     */
    List<PlayClerkUserInfoEntity> listAllByTypeId(String typeId);

    /**
     * 根据分组ID查询店员列表
     *
     * @param groupId
     *            分组ID
     * @return 店员列表
     */
    List<PlayClerkUserInfoEntity> listAllByGroupId(String groupId);

    /**
     * 查询当前租户店员总数
     *
     * @param tenantId
     *            租户ID
     * @return 店员总数
     */
    Long getTotalClerkUser(String tenantId);

    /**
     * 获取当前店员抽成信息
     *
     * @param clerkId
     *            店员ID
     * @return com.starry.admin.modules.clerk.module.entity.PlayClerkLevelInfoEntity
     * @author admin
     * @since 2024/6/3 11:29
     **/
    PlayClerkLevelInfoEntity queryLevelCommission(String clerkId);

    /**
     * 查询店员
     *
     * @param openId
     *            微信ID
     * @return 店员
     */
    PlayClerkUserInfoEntity selectByOpenid(String openId);

    /**
     * 查询店员
     *
     * @param id
     *            店员主键
     * @return 店员
     */
    PlayClerkUserInfoEntity selectById(String id);

    /**
     * 获得陪聊用户登录返回信息
     *
     * @param vo
     *            PlayClerkUserInfoEntity
     * @return PlayClerkUserLoginResponseVo
     */
    PlayClerkUserLoginResponseVo getVo(PlayClerkUserInfoEntity vo);

    /**
     * 更新token
     *
     * @param id
     *            用户ID
     * @param token
     *            token
     * @author admin
     * @since 2024/4/9 14:30
     **/
    void updateTokenById(String id, String token);

    /**
     * 更新账号余额
     *
     * @param id
     *            用户ID
     * @param accountBalance
     *            账户余额
     * @author admin
     * @since 2024/4/9 14:33
     **/
    void updateAccountBalanceById(String id, BigDecimal accountBalance);

    /**
     * 更新店员账户余额信息
     *
     * @param userId
     *            用户ID
     * @param balanceBeforeOperation
     *            操作前余额
     * @param balanceAfterOperation
     *            操作后余额
     * @param operationType
     *            操作类型（0:充值;1:消费;2:服务）
     * @param operationAction
     *            操作动作
     * @param balanceMoney
     *            操作金额
     * @param orderId
     *            订单ID
     */
    void updateAccountBalanceById(String userId, BigDecimal balanceBeforeOperation, BigDecimal balanceAfterOperation,
            String operationType, String operationAction, BigDecimal balanceMoney, String orderId);

    /**
     * 查询当前租户下所有店员
     *
     * @return 店员列表
     */
    List<PlayClerkUserInfoEntity> listAll();

    /**
     * 查询店员未结算订单
     *
     * @param vo
     *            未结算订单查询对象
     * @return 未结算订单
     */
    IPage<PlayClerkUnsettledWagesInfoReturnVo> listUnsettledWagesByPage(PlayClerkUnsettledWagesInfoQueryVo vo);

    /**
     * 管理端分页查询店员信息
     *
     * @param vo
     *            店员查询实体
     * @return 店员列表
     */

    IPage<PlayClerkUserInfoEntity> selectByPage(PlayClerkPerformanceInfoQueryVo vo);

    /**
     * 管理端分页查询店员信息
     *
     * @param vo
     *            店员查询实体
     * @return 店员列表
     */

    IPage<PlayClerkUserReturnVo> selectByPage(PlayClerkUserQueryVo vo);

    /**
     * 查询店员列表
     *
     * @param vo
     *            店员查询对象
     * @param customUserId
     *            顾客ID
     * @return 店员列表
     */
    IPage<PlayClerkUserInfoResultVo> selectByPage(PlayClerkUserInfoQueryVo vo, String customUserId);

    /**
     * 查询店员列表
     *
     * @param vo
     *            店员查询对象
     * @return 店员集合
     */
    IPage<PlayClerkUserInfoResultVo> selectPlayClerkUserInfoByPage(PlayClerkUserInfoQueryVo vo);

    /**
     * 新增店员
     *
     * @param playClerkUserInfo
     *            店员
     * @return 结果
     */
    boolean create(PlayClerkUserInfoEntity playClerkUserInfo);

    /**
     * 修改店员
     *
     * @param playClerkUserInfo
     *            店员
     * @return 结果
     */
    boolean update(PlayClerkUserInfoEntity playClerkUserInfo);

    /**
     * 批量删除店员
     *
     * @param ids
     *            需要删除的店员主键集合
     * @return 结果
     */
    int deletePlayClerkUserInfoByIds(String[] ids);

    /**
     * 删除店员信息
     *
     * @param id
     *            店员主键
     * @return 结果
     */
    int deletePlayClerkUserInfoById(String id);

    List<PlayClerkUserInfoEntity> simpleList();

    JSONObject getPcData(PlayClerkUserInfoEntity entity);
}


--- File: play-admin/src/main/java/com/starry/admin/modules/clerk/service/IPlayClerkUserReviewInfoService.java ---
package com.starry.admin.modules.clerk.service;

import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.service.IService;
import com.starry.admin.modules.clerk.module.entity.PlayClerkUserReviewInfoEntity;
import com.starry.admin.modules.clerk.module.vo.PlayClerkUserReviewQueryVo;
import com.starry.admin.modules.clerk.module.vo.PlayClerkUserReviewReturnVo;
import com.starry.admin.modules.clerk.module.vo.PlayClerkUserReviewStateEditVo;

/**
 * 店员资料审核Service接口
 *
 * @author admin
 * @since 2024-05-19
 */
public interface IPlayClerkUserReviewInfoService extends IService<PlayClerkUserReviewInfoEntity> {
    /**
     * 查询店员资料审核
     *
     * @param id
     *            店员资料审核主键
     * @return 店员资料审核
     */
    PlayClerkUserReviewInfoEntity selectPlayClerkUserReviewInfoById(String id);

    /**
     * 根据店员ID，查询未审核的申请
     *
     * @param clerkId
     *            店员ID
     * @param reviewState
     *            审核状态（0：未审核:1：审核通过，2：审核不通过）
     * @return 店员申请审核对象
     */
    PlayClerkUserReviewInfoEntity queryByClerkId(String clerkId, String reviewState);

    /**
     * 分页查询店员申请
     *
     * @param vo
     *            店员申请对象
     * @return 店员申请对象
     */
    IPage<PlayClerkUserReviewReturnVo> selectByPage(PlayClerkUserReviewQueryVo vo);

    /**
     * 查询店员资料审核列表
     *
     * @param playClerkUserReviewInfo
     *            店员资料审核
     * @return 店员资料审核集合
     */
    IPage<PlayClerkUserReviewInfoEntity> selectPlayClerkUserReviewInfoByPage(
            PlayClerkUserReviewInfoEntity playClerkUserReviewInfo);

    /**
     * 新增店员资料审核
     *
     * @param playClerkUserReviewInfo
     *            店员资料审核
     * @return 结果
     */
    boolean create(PlayClerkUserReviewInfoEntity playClerkUserReviewInfo);

    boolean update(PlayClerkUserReviewInfoEntity entity);

    /**
     * 修改店员资料审核
     *
     * @param vo
     *            店员资料审核对象
     */
    void updateDataReviewState(PlayClerkUserReviewStateEditVo vo);

    /**
     * 批量删除店员资料审核
     *
     * @param ids
     *            需要删除的店员资料审核主键集合
     * @return 结果
     */
    int deletePlayClerkUserReviewInfoByIds(String[] ids);

    /**
     * 删除店员资料审核信息
     *
     * @param id
     *            店员资料审核主键
     * @return 结果
     */
    int deletePlayClerkUserReviewInfoById(String id);
}


--- File: play-admin/src/main/java/com/starry/admin/modules/clerk/service/IPlayClerkWagesDetailsInfoService.java ---
package com.starry.admin.modules.clerk.service;

import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.service.IService;
import com.starry.admin.modules.clerk.module.entity.PlayClerkWagesDetailsInfoEntity;
import java.util.List;

/**
 * 店员工资明细信息Service接口
 *
 * @author admin
 * @since 2024-05-31
 */
public interface IPlayClerkWagesDetailsInfoService extends IService<PlayClerkWagesDetailsInfoEntity> {

    /**
     * 查询店员工资明细信息
     *
     * @param wagesId
     *            店员工资统计ID
     * @return 店员工资明细信息
     */
    List<PlayClerkWagesDetailsInfoEntity> selectByWagesId(String wagesId);

    /**
     * 根据订单ID
     *
     * @param orderId
     *            订单ID
     * @return 工资详情
     */
    PlayClerkWagesDetailsInfoEntity selectByOrderId(String orderId);

    /**
     * 根据工资结算ID和店员ID查询工资详情
     *
     * @param wagesId
     *            结算ID
     * @param clerkId
     *            店员ID
     * @return 工资详情
     */
    PlayClerkWagesDetailsInfoEntity selectByClerkIdAndWagesId(String wagesId, String clerkId);
    /**
     * 查询店员工资明细信息
     *
     * @param id
     *            店员工资明细信息主键
     * @return 店员工资明细信息
     */
    PlayClerkWagesDetailsInfoEntity selectPlayClerkWagesDetailsInfoById(String id);

    /**
     * 查询店员工资明细信息列表
     *
     * @param playClerkWagesDetailsInfo
     *            店员工资明细信息
     * @return 店员工资明细信息集合
     */
    IPage<PlayClerkWagesDetailsInfoEntity> selectPlayClerkWagesDetailsInfoByPage(
            PlayClerkWagesDetailsInfoEntity playClerkWagesDetailsInfo);

    /**
     * 新增店员工资明细信息
     *
     * @param playClerkWagesDetailsInfo
     *            店员工资明细信息
     * @return 结果
     */
    boolean create(PlayClerkWagesDetailsInfoEntity playClerkWagesDetailsInfo);

    /**
     * 修改店员工资明细信息
     *
     * @param playClerkWagesDetailsInfo
     *            店员工资明细信息
     * @return 结果
     */
    boolean update(PlayClerkWagesDetailsInfoEntity playClerkWagesDetailsInfo);

    /**
     * 批量删除店员工资明细信息
     *
     * @param ids
     *            需要删除的店员工资明细信息主键集合
     * @return 结果
     */
    int deletePlayClerkWagesDetailsInfoByIds(String[] ids);

    /**
     * 删除店员工资明细信息信息
     *
     * @param id
     *            店员工资明细信息主键
     * @return 结果
     */
    int deletePlayClerkWagesDetailsInfoById(String id);
}


--- File: play-admin/src/main/java/com/starry/admin/modules/clerk/service/IPlayClerkWagesInfoService.java ---
package com.starry.admin.modules.clerk.service;

import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.service.IService;
import com.starry.admin.modules.clerk.module.entity.PlayClerkWagesInfoEntity;
import com.starry.admin.modules.clerk.module.vo.PlayClerkWagesInfoQueryVo;
import com.starry.admin.modules.clerk.module.vo.PlayClerkWagesInfoReturnVo;
import java.time.LocalDate;
import java.util.List;

/**
 * 店员工资结算信息Service接口
 *
 * @author admin
 * @since 2024-05-31
 */
public interface IPlayClerkWagesInfoService extends IService<PlayClerkWagesInfoEntity> {

    /**
     * 根据店员ID查询店员工资结算信息
     *
     * @param clerkId
     *            店员ID
     * @return 店员工资结算信息
     */
    PlayClerkWagesInfoEntity selectCurrentPeriodWagesByClerkId(String clerkId);

    /**
     * 根据店员ID查询店员往期工资
     *
     * @param clerkId
     *            店员ID
     * @return 店员工资结算信息
     */
    List<PlayClerkWagesInfoEntity> selectHistoricalWagesByClerkId(String clerkId);

    /**
     * 获取最后一次统计
     *
     * @param clerkId
     *            店员ID
     * @return 上次结算时间
     */
    PlayClerkWagesInfoEntity getLastSettlement(String clerkId);

    /**
     * 获取最后一次统计
     *
     * @param clerkId
     *            店员ID
     * @return 上次结算时间
     */
    LocalDate getLastSettlementTime(String clerkId);

    /**
     * 查询店员工资结算信息
     *
     * @param id
     *            店员工资结算信息主键
     * @return 店员工资结算信息
     */
    PlayClerkWagesInfoEntity selectPlayClerkWagesInfoById(String id);

    /**
     * 分页查询工资统计信息
     *
     * @param vo
     *            统计统计查询对象
     * @return 查询工资统计信息
     */
    IPage<PlayClerkWagesInfoReturnVo> selectHistoricalByPage(PlayClerkWagesInfoQueryVo vo);

    /**
     * 查询店员工资结算信息列表
     *
     * @param playClerkWagesInfo
     *            店员工资结算信息
     * @return 店员工资结算信息集合
     */
    IPage<PlayClerkWagesInfoEntity> selectPlayClerkWagesInfoByPage(PlayClerkWagesInfoEntity playClerkWagesInfo);

    /**
     * 新增店员工资结算信息
     *
     * @param playClerkWagesInfo
     *            店员工资结算信息
     * @return 结果
     */
    boolean create(PlayClerkWagesInfoEntity playClerkWagesInfo);

    /**
     * 修改店员工资结算信息
     *
     * @param playClerkWagesInfo
     *            店员工资结算信息
     * @return 结果
     */
    boolean update(PlayClerkWagesInfoEntity playClerkWagesInfo);

    /**
     * 批量删除店员工资结算信息
     *
     * @param ids
     *            需要删除的店员工资结算信息主键集合
     * @return 结果
     */
    int deletePlayClerkWagesInfoByIds(String[] ids);

    /**
     * 删除店员工资结算信息信息
     *
     * @param id
     *            店员工资结算信息主键
     * @return 结果
     */
    int deletePlayClerkWagesInfoById(String id);
}


--- File: play-admin/src/main/java/com/starry/admin/modules/clerk/service/IPlayCustomArticleInfoService.java ---
package com.starry.admin.modules.clerk.service;

import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.service.IService;
import com.starry.admin.modules.clerk.module.entity.PlayCustomArticleInfoEntity;

/**
 * 陪聊点赞动态信息Service接口
 *
 * @author admin
 * @since 2024-05-04
 */
public interface IPlayCustomArticleInfoService extends IService<PlayCustomArticleInfoEntity> {

    /**
     * 查询顾客操作动态数据
     *
     * @param articleId
     *            动态ID
     * @param customId
     *            客户ID
     * @param endorseType
     *            操作类型(1:点赞;0:收藏)
     * @return PlayCustomArticleInfoEntity
     */
    PlayCustomArticleInfoEntity selectByArticleId(String articleId, String customId, String endorseType);

    /**
     * 查询陪聊点赞动态信息
     *
     * @param id
     *            陪聊点赞动态信息主键
     * @return 陪聊点赞动态信息
     */
    PlayCustomArticleInfoEntity selectPlayCustomArticleInfoById(String id);

    /**
     * 查询陪聊点赞动态信息列表
     *
     * @param playCustomArticleInfo
     *            陪聊点赞动态信息
     * @return 陪聊点赞动态信息集合
     */
    IPage<PlayCustomArticleInfoEntity> selectPlayCustomArticleInfoByPage(
            PlayCustomArticleInfoEntity playCustomArticleInfo);

    /**
     * 新增陪聊点赞动态信息
     *
     * @param playCustomArticleInfo
     *            陪聊点赞动态信息
     * @return 结果
     */
    boolean create(PlayCustomArticleInfoEntity playCustomArticleInfo);

    /**
     * 修改陪聊点赞动态信息
     *
     * @param playCustomArticleInfo
     *            陪聊点赞动态信息
     * @return 结果
     */
    boolean update(PlayCustomArticleInfoEntity playCustomArticleInfo);

    /**
     * 批量删除陪聊点赞动态信息
     *
     * @param ids
     *            需要删除的陪聊点赞动态信息主键集合
     * @return 结果
     */
    int deletePlayCustomArticleInfoByIds(String[] ids);

    /**
     * 删除陪聊点赞动态信息信息
     *
     * @param id
     *            陪聊点赞动态信息主键
     * @return 结果
     */
    int deletePlayCustomArticleInfoById(String id);

    /**
     * 根据动态ID删除点赞信息
     *
     * @param articleId
     *            动态ID
     */
    void deleteByArticleId(String articleId);
}


--- File: play-admin/src/main/java/com/starry/admin/modules/clerk/service/impl/PlayAvatarFrameInfoServiceImpl.java ---
package com.starry.admin.modules.clerk.service.impl;

import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.starry.admin.modules.clerk.mapper.PlayAvatarFrameInfoMapper;
import com.starry.admin.modules.clerk.module.entity.PlayAvatarFrameInfoEntity;
import com.starry.admin.modules.clerk.module.vo.PlayAvatarFrameInfoQueryVo;
import com.starry.admin.modules.clerk.service.IPlayAvatarFrameInfoService;
import com.starry.common.utils.IdUtils;
import java.util.Arrays;
import java.util.List;
import javax.annotation.Resource;
import org.springframework.stereotype.Service;

/**
 * 店员头像框Service业务层处理
 *
 * @author admin
 * @since 2024-06-06
 */
@Service
public class PlayAvatarFrameInfoServiceImpl extends ServiceImpl<PlayAvatarFrameInfoMapper, PlayAvatarFrameInfoEntity>
        implements
            IPlayAvatarFrameInfoService {
    @Resource
    private PlayAvatarFrameInfoMapper playAvatarFrameInfoMapper;

    /**
     * 查询店员头像框
     *
     * @param id
     *            店员头像框主键
     * @return 店员头像框
     */
    @Override
    public PlayAvatarFrameInfoEntity selectPlayAvatarFrameInfoById(String id) {
        return this.baseMapper.selectById(id);
    }

    @Override
    public List<PlayAvatarFrameInfoEntity> selectAll() {
        return this.baseMapper.selectList(new LambdaQueryWrapper<>());
    }

    @Override
    public IPage<PlayAvatarFrameInfoEntity> selectByPage(PlayAvatarFrameInfoQueryVo vo) {
        return this.baseMapper.selectPage(new Page<>(vo.getPageNum(), vo.getPageSize()), new LambdaQueryWrapper<>());
    }

    /**
     * 查询店员头像框列表
     *
     * @param playAvatarFrameInfo
     *            店员头像框
     * @return 店员头像框
     */
    @Override
    public IPage<PlayAvatarFrameInfoEntity> selectPlayAvatarFrameInfoByPage(
            PlayAvatarFrameInfoEntity playAvatarFrameInfo) {
        Page<PlayAvatarFrameInfoEntity> page = new Page<>(1, 10);
        return this.baseMapper.selectPage(page, new LambdaQueryWrapper<>());
    }

    /**
     * 新增店员头像框
     *
     * @param playAvatarFrameInfo
     *            店员头像框
     * @return 结果
     */
    @Override
    public boolean create(PlayAvatarFrameInfoEntity playAvatarFrameInfo) {
        if (StrUtil.isBlankIfStr(playAvatarFrameInfo.getId())) {
            playAvatarFrameInfo.setId(IdUtils.getUuid());
        }
        return save(playAvatarFrameInfo);
    }

    /**
     * 修改店员头像框
     *
     * @param playAvatarFrameInfo
     *            店员头像框
     * @return 结果
     */
    @Override
    public boolean update(PlayAvatarFrameInfoEntity playAvatarFrameInfo) {
        return updateById(playAvatarFrameInfo);
    }

    /**
     * 批量删除店员头像框
     *
     * @param ids
     *            需要删除的店员头像框主键
     * @return 结果
     */
    @Override
    public int deletePlayAvatarFrameInfoByIds(String[] ids) {
        return playAvatarFrameInfoMapper.deleteBatchIds(Arrays.asList(ids));
    }

    /**
     * 删除店员头像框信息
     *
     * @param id
     *            店员头像框主键
     * @return 结果
     */
    @Override
    public int deletePlayAvatarFrameInfoById(String id) {
        return playAvatarFrameInfoMapper.deleteById(id);
    }
}


--- File: play-admin/src/main/java/com/starry/admin/modules/clerk/service/impl/PlayClerkArticleInfoServiceImpl.java ---
package com.starry.admin.modules.clerk.service.impl;

import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.github.yulichang.wrapper.MPJLambdaWrapper;
import com.starry.admin.common.exception.CustomException;
import com.starry.admin.modules.clerk.mapper.PlayClerkArticleInfoMapper;
import com.starry.admin.modules.clerk.module.entity.PlayClerkArticleInfoEntity;
import com.starry.admin.modules.clerk.module.entity.PlayClerkUserInfoEntity;
import com.starry.admin.modules.clerk.module.entity.PlayCustomArticleInfoEntity;
import com.starry.admin.modules.clerk.module.vo.PlayClerkArticleQueryVo;
import com.starry.admin.modules.clerk.module.vo.PlayClerkArticleReturnVo;
import com.starry.admin.modules.clerk.service.IPlayClerkArticleInfoService;
import com.starry.admin.modules.personnel.service.IPlayPersonnelGroupInfoService;
import com.starry.admin.modules.weichat.entity.article.PlayClerkArticleCustomQueryVo;
import com.starry.admin.modules.weichat.entity.article.PlayClerkArticleCustomReturnVo;
import com.starry.admin.utils.SecurityUtils;
import com.starry.common.utils.IdUtils;
import java.util.Arrays;
import java.util.List;
import javax.annotation.Resource;
import org.springframework.stereotype.Service;

/**
 * 店员动态信息Service业务层处理
 *
 * @author admin
 * @since 2024-05-04
 */
@Service
public class PlayClerkArticleInfoServiceImpl extends ServiceImpl<PlayClerkArticleInfoMapper, PlayClerkArticleInfoEntity>
        implements
            IPlayClerkArticleInfoService {
    @Resource
    private PlayClerkArticleInfoMapper playClerkArticleInfoMapper;

    @Resource
    private IPlayPersonnelGroupInfoService playClerkGroupInfoService;

    /**
     * 查询店员动态信息
     *
     * @param id
     *            店员动态信息主键
     * @return 店员动态信息
     */
    @Override
    public PlayClerkArticleInfoEntity selectPlayClerkArticleInfoById(String id) {
        PlayClerkArticleInfoEntity entity = this.baseMapper.selectById(id);
        if (entity == null) {
            throw new CustomException("动态不存在");
        }
        return this.baseMapper.selectById(id);
    }

    /**
     * 查询店员动态信息列表
     *
     * @param vo
     *            店员动态信息查询对象
     * @return 店员动态信息
     */
    @Override
    public IPage<PlayClerkArticleReturnVo> selectByPage(PlayClerkArticleQueryVo vo, boolean searchByYourself) {
        MPJLambdaWrapper<PlayClerkArticleInfoEntity> lambdaQueryWrapper = new MPJLambdaWrapper<>();
        // 查询主表全部字段
        lambdaQueryWrapper.selectAll(PlayClerkArticleInfoEntity.class);
        // 陪聊用户表全部字段
        lambdaQueryWrapper.selectAs(PlayClerkUserInfoEntity::getId, "clerkId")
                .selectAs(PlayClerkUserInfoEntity::getNickname, "clerkNickname")
                .selectAs(PlayClerkUserInfoEntity::getAvatar, "clerkAvatar");
        // 陪聊动态表
        lambdaQueryWrapper.leftJoin(PlayClerkUserInfoEntity.class, PlayClerkUserInfoEntity::getId,
                PlayClerkArticleInfoEntity::getClerkId);
        // 动态点赞表
        lambdaQueryWrapper
                .selectCollection(PlayCustomArticleInfoEntity.class, PlayClerkArticleReturnVo::getArticleInfoEntities)
                .leftJoin(PlayCustomArticleInfoEntity.class, PlayCustomArticleInfoEntity::getArticleId,
                        PlayCustomArticleInfoEntity::getId);

        if (StrUtil.isNotBlank(vo.getClerkId())) {
            lambdaQueryWrapper.eq(PlayClerkArticleInfoEntity::getClerkId, vo.getClerkId());
        }
        if (StrUtil.isNotBlank(vo.getReviewState())) {
            lambdaQueryWrapper.eq(PlayClerkArticleInfoEntity::getReviewState, vo.getReviewState());
        }
        if (vo.getReleaseTime() != null && vo.getReleaseTime().size() == 2) {
            lambdaQueryWrapper.between(PlayClerkArticleInfoEntity::getReleaseTime, vo.getReleaseTime().get(0),
                    vo.getReleaseTime().get(1));
        }
        if (!searchByYourself) {
            // 后台查询时，加入组员的筛选
            List<String> clerkIdList = playClerkGroupInfoService.getValidClerkIdList(SecurityUtils.getLoginUser(),
                    null);
            lambdaQueryWrapper.in(PlayClerkArticleInfoEntity::getClerkId, clerkIdList);
        }
        IPage<PlayClerkArticleReturnVo> page = this.baseMapper.selectJoinPage(
                new Page<>(vo.getPageNum(), vo.getPageSize()), PlayClerkArticleReturnVo.class, lambdaQueryWrapper);
        for (PlayClerkArticleReturnVo record : page.getRecords()) {
            int index = 0;
            for (PlayCustomArticleInfoEntity articleInfoEntity : record.getArticleInfoEntities()) {
                if (articleInfoEntity.getId() == null) {
                    continue;
                }
                index++;
            }
            record.setAgreedQuantity(index);
        }
        return this.baseMapper.selectJoinPage(new Page<>(vo.getPageNum(), vo.getPageSize()),
                PlayClerkArticleReturnVo.class, lambdaQueryWrapper);
    }

    @Override
    public IPage<PlayClerkArticleCustomReturnVo> customSelectByPage(PlayClerkArticleCustomQueryVo vo, String customId) {
        MPJLambdaWrapper<PlayClerkArticleInfoEntity> lambdaQueryWrapper = new MPJLambdaWrapper<>();
        // 查询主表全部字段
        lambdaQueryWrapper.selectAll(PlayClerkArticleInfoEntity.class);
        // 陪聊用户表全部字段
        lambdaQueryWrapper.selectAs(PlayClerkUserInfoEntity::getId, "clerkId")
                .selectAs(PlayClerkUserInfoEntity::getNickname, "clerkNickname")
                .selectAs(PlayClerkUserInfoEntity::getAvatar, "clerkAvatar")
                .selectAs(PlayClerkUserInfoEntity::getSex, "clerkSex");
        // 陪聊用户表全部字段
        lambdaQueryWrapper.selectAs(PlayCustomArticleInfoEntity::getId, "id");
        // 陪聊动态表
        lambdaQueryWrapper.leftJoin(PlayClerkUserInfoEntity.class, PlayClerkUserInfoEntity::getId,
                PlayClerkArticleInfoEntity::getClerkId);
        if (StrUtil.isNotBlank(vo.getFollowState()) && "1".equals(vo.getFollowState())) {
            // 查询本人收藏的动态列表
            lambdaQueryWrapper.innerJoin(PlayCustomArticleInfoEntity.class, PlayCustomArticleInfoEntity::getArticleId,
                    PlayClerkArticleInfoEntity::getId);
            lambdaQueryWrapper.eq(PlayCustomArticleInfoEntity::getCustomId, customId);
            lambdaQueryWrapper.eq(PlayCustomArticleInfoEntity::getEndorseType, "0");
            lambdaQueryWrapper.eq(PlayCustomArticleInfoEntity::getEndorseState, "1");
        }
        // 查询指定店员动态
        if (StrUtil.isNotBlank(vo.getClerkId())) {
            lambdaQueryWrapper.eq(PlayClerkArticleInfoEntity::getClerkId, vo.getClerkId());
        }
        // 动态点赞表
        lambdaQueryWrapper
                .selectCollection(PlayCustomArticleInfoEntity.class, PlayClerkArticleReturnVo::getArticleInfoEntities)
                .leftJoin(PlayCustomArticleInfoEntity.class, PlayCustomArticleInfoEntity::getArticleId,
                        PlayCustomArticleInfoEntity::getId);
        lambdaQueryWrapper.eq(PlayClerkArticleInfoEntity::getReviewState, "1");

        IPage<PlayClerkArticleCustomReturnVo> page = this.baseMapper.selectJoinPage(
                new Page<>(vo.getPageNum(), vo.getPageSize()), PlayClerkArticleCustomReturnVo.class,
                lambdaQueryWrapper);
        for (PlayClerkArticleCustomReturnVo record : page.getRecords()) {
            int index = 0;
            for (PlayCustomArticleInfoEntity articleInfoEntity : record.getArticleInfoEntities()) {
                // 动态操作信息为空
                if (articleInfoEntity.getId() == null) {
                    continue;
                }
                // 点赞或者动态处于激活状态
                if ("1".equals(articleInfoEntity.getEndorseState())) {
                    if ("1".equals(articleInfoEntity.getEndorseType())) {
                        index++;
                        record.setGreedState("1");
                    } else {
                        record.setFollowState("1");
                    }
                }
            }
            record.setAgreedQuantity(index);
        }
        return page;
    }
    /**
     * 新增店员动态信息
     *
     * @param entity
     *            店员动态信息
     * @return 结果
     */
    @Override
    public boolean create(PlayClerkArticleInfoEntity entity) {
        if (!queryClerkUnauditedArticleInfo(entity.getClerkId()).isEmpty()) {
            throw new CustomException("存在未审核动态,无法新增");
        }

        if (StrUtil.isBlankIfStr(entity.getId())) {
            entity.setId(IdUtils.getUuid());
        }
        return save(entity);
    }

    /**
     * 查询店员未审核的动态
     *
     * @param clerkId
     *            店员ID
     * @return 店员未审核的动态列表
     */
    public List<PlayClerkArticleInfoEntity> queryClerkUnauditedArticleInfo(String clerkId) {
        LambdaQueryWrapper<PlayClerkArticleInfoEntity> lambdaQueryWrapper = new LambdaQueryWrapper<>();
        lambdaQueryWrapper.eq(PlayClerkArticleInfoEntity::getClerkId, clerkId);
        lambdaQueryWrapper.eq(PlayClerkArticleInfoEntity::getReviewState, "0");
        return this.baseMapper.selectList(lambdaQueryWrapper);
    }

    /**
     * 修改店员动态信息
     *
     * @param playClerkArticleInfo
     *            店员动态信息
     * @return 结果
     */
    @Override
    public boolean update(PlayClerkArticleInfoEntity playClerkArticleInfo) {
        return updateById(playClerkArticleInfo);
    }

    /**
     * 批量删除店员动态信息
     *
     * @param ids
     *            需要删除的店员动态信息主键
     * @return 结果
     */
    @Override
    public int deletePlayClerkArticleInfoByIds(String[] ids) {
        return playClerkArticleInfoMapper.deleteBatchIds(Arrays.asList(ids));
    }

    /**
     * 删除店员动态信息信息
     *
     * @param id
     *            店员动态信息主键
     * @return 结果
     */
    @Override
    public int deletePlayClerkArticleInfoById(String id) {
        return playClerkArticleInfoMapper.deleteById(id);
    }
}


--- File: play-admin/src/main/java/com/starry/admin/modules/clerk/service/impl/PlayClerkClassificationInfoServiceImpl.java ---
package com.starry.admin.modules.clerk.service.impl;

import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.starry.admin.modules.clerk.mapper.PlayClerkClassificationInfoMapper;
import com.starry.admin.modules.clerk.module.entity.PlayClerkClassificationInfoEntity;
import com.starry.admin.modules.clerk.service.IPlayClerkClassificationInfoService;
import com.starry.common.utils.IdUtils;
import java.util.Arrays;
import java.util.List;
import javax.annotation.Resource;
import org.springframework.stereotype.Service;

/**
 * 店员分类Service业务层处理
 *
 * @author admin
 * @since 2024-04-06
 */
@Service
public class PlayClerkClassificationInfoServiceImpl
        extends
            ServiceImpl<PlayClerkClassificationInfoMapper, PlayClerkClassificationInfoEntity>
        implements
            IPlayClerkClassificationInfoService {
    @Resource
    private PlayClerkClassificationInfoMapper playClerkClassificationInfoMapper;

    /**
     * 查询店员分类
     *
     * @param id
     *            店员分类主键
     * @return 店员分类
     */
    @Override
    public PlayClerkClassificationInfoEntity selectPlayClerkClassificationInfoById(String id) {
        return this.baseMapper.selectById(id);
    }

    /**
     * 查询店员分类列表
     *
     * @return 店员分类
     */
    @Override
    public List<PlayClerkClassificationInfoEntity> selectAll() {
        return this.baseMapper.selectList(new LambdaQueryWrapper<>());
    }

    /**
     * 查询店员分类列表
     *
     * @param playClerkClassificationInfo
     *            店员分类
     * @return 店员分类
     */
    @Override
    public IPage<PlayClerkClassificationInfoEntity> selectPlayClerkClassificationInfoByPage(
            PlayClerkClassificationInfoEntity playClerkClassificationInfo) {
        Page<PlayClerkClassificationInfoEntity> page = new Page<>(1, 10);
        return this.baseMapper.selectPage(page, new LambdaQueryWrapper<>());
    }

    /**
     * 新增店员分类
     *
     * @param playClerkClassificationInfo
     *            店员分类
     * @return 结果
     */
    @Override
    public boolean create(PlayClerkClassificationInfoEntity playClerkClassificationInfo) {
        if (StrUtil.isBlankIfStr(playClerkClassificationInfo.getId())) {
            playClerkClassificationInfo.setId(IdUtils.getUuid());
        }
        return save(playClerkClassificationInfo);
    }

    /**
     * 修改店员分类
     *
     * @param playClerkClassificationInfo
     *            店员分类
     * @return 结果
     */
    @Override
    public boolean update(PlayClerkClassificationInfoEntity playClerkClassificationInfo) {
        return updateById(playClerkClassificationInfo);
    }

    /**
     * 批量删除店员分类
     *
     * @param ids
     *            需要删除的店员分类主键
     * @return 结果
     */
    @Override
    public int deletePlayClerkClassificationInfoByIds(String[] ids) {
        return playClerkClassificationInfoMapper.deleteBatchIds(Arrays.asList(ids));
    }

    /**
     * 删除店员分类信息
     *
     * @param id
     *            店员分类主键
     * @return 结果
     */
    @Override
    public int deletePlayClerkClassificationInfoById(String id) {
        return playClerkClassificationInfoMapper.deleteById(id);
    }
}


--- File: play-admin/src/main/java/com/starry/admin/modules/clerk/service/impl/PlayClerkCommodityServiceImpl.java ---
package com.starry.admin.modules.clerk.service.impl;

import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.starry.admin.common.exception.CustomException;
import com.starry.admin.modules.clerk.mapper.PlayClerkCommodityMapper;
import com.starry.admin.modules.clerk.module.entity.PlayClerkCommodityEntity;
import com.starry.admin.modules.clerk.service.IPlayClerkCommodityService;
import com.starry.admin.modules.shop.module.entity.PlayCommodityInfoEntity;
import com.starry.admin.modules.shop.service.IPlayCommodityInfoService;
import com.starry.common.utils.IdUtils;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
import javax.annotation.Resource;
import org.springframework.stereotype.Service;

/**
 * 陪聊服务项目Service业务层处理
 *
 * @author admin
 * @since 2024-03-31
 */
@Service
public class PlayClerkCommodityServiceImpl extends ServiceImpl<PlayClerkCommodityMapper, PlayClerkCommodityEntity>
        implements
            IPlayClerkCommodityService {
    @Resource
    private PlayClerkCommodityMapper playClerkCommodityMapper;

    @Resource
    private IPlayCommodityInfoService playCommodityInfoService;

    @Override
    public void initClerkCommodity(String userId) {
        // 查询当前陪聊的所有服务项目
        LambdaQueryWrapper<PlayClerkCommodityEntity> lambdaQueryWrapper = new LambdaQueryWrapper<>();
        lambdaQueryWrapper.eq(PlayClerkCommodityEntity::getClerkId, userId);
        this.baseMapper.delete(lambdaQueryWrapper);
        // 根据当前租户的服务项目，生成陪聊项目数据
        for (PlayCommodityInfoEntity commodityInfo : playCommodityInfoService.selectByType()) {
            PlayClerkCommodityEntity entity = new PlayClerkCommodityEntity();
            entity.setClerkId(userId);
            entity.setCommodityId(commodityInfo.getId());
            entity.setCommodityName(commodityInfo.getItemName());
            entity.setEnablingState("1");
            entity.setSort(commodityInfo.getSort());
            this.create(entity);
        }

    }

    @Override
    public List<String> getClerkCommodityList(String userId, String enablingState) {
        List<PlayClerkCommodityEntity> list = this.selectCommodityTypeByUser(userId, enablingState);
        return list.stream().map(PlayClerkCommodityEntity::getCommodityName).collect(Collectors.toList());
    }

    @Override
    public List<PlayClerkCommodityEntity> selectCommodityTypeByUser(String userId, String enablingState) {
        LambdaQueryWrapper<PlayClerkCommodityEntity> lambdaQueryWrapper = new LambdaQueryWrapper<>();
        lambdaQueryWrapper.orderByDesc(PlayClerkCommodityEntity::getSort);
        lambdaQueryWrapper.eq(PlayClerkCommodityEntity::getClerkId, userId);
        if (StrUtil.isNotBlank(enablingState)) {
            lambdaQueryWrapper.eq(PlayClerkCommodityEntity::getEnablingState, enablingState);
        }
        return this.baseMapper.selectList(lambdaQueryWrapper);
    }

    @Override
    public List<PlayClerkCommodityEntity> selectByUser(String userId) {
        LambdaQueryWrapper<PlayClerkCommodityEntity> lambdaQueryWrapper = new LambdaQueryWrapper<>();
        lambdaQueryWrapper.eq(PlayClerkCommodityEntity::getClerkId, userId);
        return this.baseMapper.selectList(lambdaQueryWrapper);
    }

    @Override
    public List<PlayClerkCommodityEntity> selectAll() {
        return this.baseMapper.selectList(new LambdaQueryWrapper<>());
    }

    @Override
    public void startStopClerkItem(String commodityName, String enablingState, String clerkId) {
        PlayClerkCommodityEntity entity = new PlayClerkCommodityEntity();
        entity.setEnablingState(enablingState);
        LambdaQueryWrapper<PlayClerkCommodityEntity> lambdaQueryWrapper = new LambdaQueryWrapper<>();
        lambdaQueryWrapper.eq(PlayClerkCommodityEntity::getClerkId, clerkId);
        lambdaQueryWrapper.eq(PlayClerkCommodityEntity::getCommodityName, commodityName);
        this.baseMapper.update(entity, lambdaQueryWrapper);
    }

    /**
     * 查询陪聊服务项目
     *
     * @param id
     *            陪聊服务项目主键
     * @return 陪聊服务项目
     */
    @Override
    public PlayClerkCommodityEntity selectPlayClerkCommodityById(String id) {
        PlayClerkCommodityEntity entity = this.baseMapper.selectById(id);
        if (entity == null) {
            throw new CustomException("服务项目不存在");
        }
        return entity;
    }

    /**
     * 查询陪聊服务项目列表
     *
     * @param playClerkCommodity
     *            陪聊服务项目
     * @return 陪聊服务项目
     */
    @Override
    public IPage<PlayClerkCommodityEntity> selectPlayClerkCommodityByPage(PlayClerkCommodityEntity playClerkCommodity) {
        Page<PlayClerkCommodityEntity> page = new Page<>(1, 10);
        return this.baseMapper.selectPage(page, new LambdaQueryWrapper<>());
    }

    /**
     * 新增陪聊服务项目
     *
     * @param playClerkCommodity
     *            陪聊服务项目
     * @return 结果
     */
    @Override
    public boolean create(PlayClerkCommodityEntity playClerkCommodity) {
        if (StrUtil.isBlankIfStr(playClerkCommodity.getId())) {
            playClerkCommodity.setId(IdUtils.getUuid());
        }
        return save(playClerkCommodity);
    }

    /**
     * 修改陪聊服务项目
     *
     * @param playClerkCommodity
     *            陪聊服务项目
     * @return 结果
     */
    @Override
    public boolean update(PlayClerkCommodityEntity playClerkCommodity) {
        return updateById(playClerkCommodity);
    }

    /**
     * 批量删除陪聊服务项目
     *
     * @param ids
     *            需要删除的陪聊服务项目主键
     * @return 结果
     */
    @Override
    public int deletePlayClerkCommodityByIds(String[] ids) {
        return playClerkCommodityMapper.deleteBatchIds(Arrays.asList(ids));
    }

    /**
     * 删除陪聊服务项目信息
     *
     * @param id
     *            陪聊服务项目主键
     * @return 结果
     */
    @Override
    public int deletePlayClerkCommodityById(String id) {
        return playClerkCommodityMapper.deleteById(id);
    }
}


--- File: play-admin/src/main/java/com/starry/admin/modules/clerk/service/impl/PlayClerkDataReviewInfoServiceImpl.java ---
package com.starry.admin.modules.clerk.service.impl;

import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.github.yulichang.wrapper.MPJLambdaWrapper;
import com.starry.admin.common.exception.CustomException;
import com.starry.admin.modules.clerk.mapper.PlayClerkDataReviewInfoMapper;
import com.starry.admin.modules.clerk.module.entity.PlayClerkDataReviewInfoEntity;
import com.starry.admin.modules.clerk.module.entity.PlayClerkUserInfoEntity;
import com.starry.admin.modules.clerk.module.entity.PlayClerkUserReviewInfoEntity;
import com.starry.admin.modules.clerk.module.vo.PlayClerkDataReviewQueryVo;
import com.starry.admin.modules.clerk.module.vo.PlayClerkDataReviewReturnVo;
import com.starry.admin.modules.clerk.module.vo.PlayClerkDataReviewStateEditVo;
import com.starry.admin.modules.clerk.service.IPlayClerkDataReviewInfoService;
import com.starry.admin.modules.clerk.service.IPlayClerkUserInfoService;
import com.starry.common.utils.IdUtils;
import java.time.LocalDateTime;
import java.util.Arrays;
import java.util.List;
import javax.annotation.Resource;
import org.springframework.beans.BeanUtils;
import org.springframework.stereotype.Service;

/**
 * 店员资料审核Service业务层处理
 *
 * @author admin
 * @since 2024-05-19
 */
@Service
public class PlayClerkDataReviewInfoServiceImpl
        extends
            ServiceImpl<PlayClerkDataReviewInfoMapper, PlayClerkDataReviewInfoEntity>
        implements
            IPlayClerkDataReviewInfoService {
    @Resource
    private PlayClerkDataReviewInfoMapper playClerkDataReviewInfoMapper;

    @Resource
    private IPlayClerkUserInfoService playClerkUserInfoService;

    /**
     * 查询店员资料审核
     *
     * @param id
     *            店员资料审核主键
     * @return 店员资料审核
     */
    @Override
    public PlayClerkDataReviewInfoEntity selectPlayClerkDataReviewInfoById(String id) {
        LambdaQueryWrapper<PlayClerkDataReviewInfoEntity> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(PlayClerkDataReviewInfoEntity::getId, id);
        PlayClerkDataReviewInfoEntity entity = this.baseMapper.selectOne(queryWrapper);
        if (entity == null) {
            throw new CustomException("对象不存在");
        }
        return entity;
    }

    @Override
    public List<PlayClerkDataReviewInfoEntity> queryByClerkId(String clerkId, String reviewState) {
        LambdaQueryWrapper<PlayClerkDataReviewInfoEntity> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(PlayClerkDataReviewInfoEntity::getClerkId, clerkId);
        queryWrapper.eq(PlayClerkDataReviewInfoEntity::getReviewState, reviewState);
        return this.baseMapper.selectList(queryWrapper);
    }

    @Override
    public PlayClerkDataReviewInfoEntity queryByClerkId(String clerkId, String dataType, String reviewState) {
        LambdaQueryWrapper<PlayClerkDataReviewInfoEntity> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(PlayClerkDataReviewInfoEntity::getClerkId, clerkId);
        queryWrapper.eq(PlayClerkDataReviewInfoEntity::getReviewState, reviewState);
        queryWrapper.eq(PlayClerkDataReviewInfoEntity::getDataType, dataType);
        return this.baseMapper.selectOne(queryWrapper);
    }

    /**
     * 查询店员资料审核列表
     *
     * @param vo
     *            店员资料审核
     * @return 店员资料审核
     */
    @Override
    public IPage<PlayClerkDataReviewReturnVo> selectByPage(PlayClerkDataReviewQueryVo vo) {
        MPJLambdaWrapper<PlayClerkDataReviewInfoEntity> lambdaQueryWrapper = new MPJLambdaWrapper<>();
        lambdaQueryWrapper.orderByDesc(PlayClerkDataReviewInfoEntity::getAddTime);
        lambdaQueryWrapper.selectAll(PlayClerkDataReviewInfoEntity.class);
        // 店员表
        lambdaQueryWrapper.selectAs(PlayClerkUserInfoEntity::getAvatar, "clerkAvatar")
                .selectAs(PlayClerkUserInfoEntity::getNickname, "clerkNickname");
        lambdaQueryWrapper.leftJoin(PlayClerkUserInfoEntity.class, PlayClerkUserInfoEntity::getId,
                PlayClerkUserReviewInfoEntity::getClerkId);
        if (StrUtil.isNotBlank(vo.getClerkId())) {
            lambdaQueryWrapper.eq(PlayClerkDataReviewInfoEntity::getClerkId, vo.getClerkId());
        }
        if (StrUtil.isNotBlank(vo.getDataType())) {
            lambdaQueryWrapper.eq(PlayClerkDataReviewInfoEntity::getDataType, vo.getDataType());
        }
        if (StrUtil.isNotBlank(vo.getReviewState())) {
            lambdaQueryWrapper.like(PlayClerkDataReviewInfoEntity::getReviewState, vo.getReviewState());
        }
        if (vo.getAddTime() != null && vo.getAddTime().size() == 2) {
            lambdaQueryWrapper.between(PlayClerkDataReviewInfoEntity::getAddTime, vo.getAddTime().get(0),
                    vo.getAddTime().get(1));
        }
        return this.baseMapper.selectJoinPage(new Page<>(vo.getPageNum(), vo.getPageSize()),
                PlayClerkDataReviewReturnVo.class, lambdaQueryWrapper);
    }

    /**
     * 新增店员资料审核
     *
     * @param playClerkDataReviewInfo
     *            店员资料审核
     * @return 结果
     */
    @Override
    public boolean create(PlayClerkDataReviewInfoEntity playClerkDataReviewInfo) {
        if (StrUtil.isBlankIfStr(playClerkDataReviewInfo.getId())) {
            playClerkDataReviewInfo.setId(IdUtils.getUuid());
        }
        if (StrUtil.isBlankIfStr(playClerkDataReviewInfo.getAddTime())) {
            playClerkDataReviewInfo.setAddTime(LocalDateTime.now());
        }
        return save(playClerkDataReviewInfo);
    }

    @Override
    public void updateDataReviewState(PlayClerkDataReviewStateEditVo vo) {
        PlayClerkDataReviewInfoEntity entity = this.selectPlayClerkDataReviewInfoById(vo.getId());
        BeanUtils.copyProperties(vo, entity);
        this.update(entity);
        if ("1".equals(vo.getReviewState())) {
            PlayClerkUserInfoEntity userInfo = new PlayClerkUserInfoEntity();
            userInfo.setId(entity.getClerkId());
            if ("1".equals(entity.getDataType())) {
                userInfo.setAvatar(entity.getDataContent().get(0));
            }
            if ("2".equals(entity.getDataType())) {
                userInfo.setAlbum(entity.getDataContent());
            }
            if ("3".equals(entity.getDataType())) {
                userInfo.setAudio(entity.getDataContent().get(0));
            }
            playClerkUserInfoService.update(userInfo);
        }
    }

    /**
     * 修改店员资料审核
     *
     * @param playClerkDataReviewInfo
     *            店员资料审核
     * @return 结果
     */
    @Override
    public boolean update(PlayClerkDataReviewInfoEntity playClerkDataReviewInfo) {
        return updateById(playClerkDataReviewInfo);
    }

    /**
     * 批量删除店员资料审核
     *
     * @param ids
     *            需要删除的店员资料审核主键
     * @return 结果
     */
    @Override
    public int deletePlayClerkDataReviewInfoByIds(String[] ids) {
        return playClerkDataReviewInfoMapper.deleteBatchIds(Arrays.asList(ids));
    }

    /**
     * 删除店员资料审核信息
     *
     * @param id
     *            店员资料审核主键
     * @return 结果
     */
    @Override
    public int deletePlayClerkDataReviewInfoById(String id) {
        return playClerkDataReviewInfoMapper.deleteById(id);
    }
}


--- File: play-admin/src/main/java/com/starry/admin/modules/clerk/service/impl/PlayClerkLevelInfoServiceImpl.java ---
package com.starry.admin.modules.clerk.service.impl;

import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.starry.admin.modules.clerk.mapper.PlayClerkLevelInfoMapper;
import com.starry.admin.modules.clerk.module.entity.PlayClerkLevelInfoEntity;
import com.starry.admin.modules.clerk.service.IPlayClerkLevelInfoService;
import com.starry.admin.modules.system.module.entity.SysTenantEntity;
import com.starry.common.utils.IdUtils;
import java.util.Arrays;
import java.util.Date;
import java.util.List;
import javax.annotation.Resource;
import org.springframework.stereotype.Service;

/**
 * 店员等级Service业务层处理
 *
 * @author admin
 * @since 2024-03-30
 */
@Service
public class PlayClerkLevelInfoServiceImpl extends ServiceImpl<PlayClerkLevelInfoMapper, PlayClerkLevelInfoEntity>
        implements
            IPlayClerkLevelInfoService {
    @Resource
    private PlayClerkLevelInfoMapper playClerkLevelInfoMapper;

    @Override
    public void initDefaultLevel(SysTenantEntity sysTenantEntity) {
        List<PlayClerkLevelInfoEntity> list = this.selectAll();
        if (list == null || list.isEmpty()) {
            PlayClerkLevelInfoEntity entity = new PlayClerkLevelInfoEntity();
            entity.setName("普通");
            entity.setFirstRandomRadio(45);
            entity.setNotFirstRandomRadio(50);
            entity.setFirstRewardRatio(45);
            entity.setNotFirstRewardRatio(50);
            entity.setFirstRegularRatio(45);
            entity.setNotFirstRegularRatio(50);
            entity.setLevel(1);
            entity.setStyleType(entity.getLevel());
            entity.setTenantId(sysTenantEntity.getTenantId());
            this.baseMapper.insert(entity);
        }
    }

    @Override
    public PlayClerkLevelInfoEntity getDefaultLevel() {
        List<PlayClerkLevelInfoEntity> list = this.selectAll();
        if (list != null && !list.isEmpty()) {
            return list.get(0);
        } else {
            PlayClerkLevelInfoEntity entity = new PlayClerkLevelInfoEntity();
            entity.setName("普通");
            entity.setFirstRandomRadio(45);
            entity.setNotFirstRandomRadio(50);
            entity.setFirstRewardRatio(45);
            entity.setNotFirstRewardRatio(50);
            entity.setFirstRegularRatio(45);
            entity.setNotFirstRegularRatio(50);
            entity.setLevel(1);
            entity.setStyleType(1);
            this.baseMapper.insert(entity);
            return entity;
        }
    }

    /**
     * 查询店员等级
     *
     * @param id
     *            店员等级主键
     * @return 店员等级
     */
    @Override
    public PlayClerkLevelInfoEntity selectPlayClerkLevelInfoById(String id) {
        return this.baseMapper.selectById(id);
    }

    @Override
    public List<PlayClerkLevelInfoEntity> selectAll() {
        LambdaQueryWrapper<PlayClerkLevelInfoEntity> lambdaQueryWrapper = new LambdaQueryWrapper<>();
        lambdaQueryWrapper.orderByAsc(PlayClerkLevelInfoEntity::getLevel);
        return this.baseMapper.selectList(lambdaQueryWrapper);
    }

    /**
     * 查询店员等级列表
     *
     * @param playClerkLevelInfo
     *            店员等级
     * @return 店员等级
     */
    @Override
    public IPage<PlayClerkLevelInfoEntity> selectPlayClerkLevelInfoByPage(PlayClerkLevelInfoEntity playClerkLevelInfo) {
        Page<PlayClerkLevelInfoEntity> page = new Page<>(1, 9999);
        return this.baseMapper.selectPage(page, new LambdaQueryWrapper<>());
    }

    /**
     * 新增店员等级
     *
     * @param playClerkLevelInfo
     *            店员等级
     * @return 结果
     */
    @Override
    public boolean create(PlayClerkLevelInfoEntity playClerkLevelInfo) {
        if (StrUtil.isBlankIfStr(playClerkLevelInfo.getId())) {
            playClerkLevelInfo.setId(IdUtils.getUuid());
        }
        playClerkLevelInfo.setCreatedTime(new Date());
        playClerkLevelInfo.setStyleType(playClerkLevelInfo.getLevel());
        return save(playClerkLevelInfo);
    }

    /**
     * 修改店员等级
     *
     * @param playClerkLevelInfo
     *            店员等级
     * @return 结果
     */
    @Override
    public boolean update(PlayClerkLevelInfoEntity playClerkLevelInfo) {
        return updateById(playClerkLevelInfo);
    }

    /**
     * 批量删除店员等级
     *
     * @param ids
     *            需要删除的店员等级主键
     * @return 结果
     */
    @Override
    public int deletePlayClerkLevelInfoByIds(String[] ids) {
        return playClerkLevelInfoMapper.deleteBatchIds(Arrays.asList(ids));
    }

    /**
     * 删除店员等级信息
     *
     * @param id
     *            店员等级主键
     * @return 结果
     */
    @Override
    public int deletePlayClerkLevelInfoById(String id) {
        return playClerkLevelInfoMapper.deleteById(id);
    }

    @Override
    public int selectMaxLevel() {
        QueryWrapper<PlayClerkLevelInfoEntity> queryWrapper = new QueryWrapper<>();
        queryWrapper.select("max(level) as level ");
        PlayClerkLevelInfoEntity entity = this.baseMapper.selectOne(queryWrapper);
        return entity == null ? 0 : entity.getLevel();
    }

    @Override
    public void delMaxLevelByLevel(Integer level) {
        LambdaQueryWrapper<PlayClerkLevelInfoEntity> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(PlayClerkLevelInfoEntity::getLevel, level);
        playClerkLevelInfoMapper.delete(queryWrapper);
    }
}


--- File: play-admin/src/main/java/com/starry/admin/modules/clerk/service/impl/PlayClerkPkServiceImpl.java ---
package com.starry.admin.modules.clerk.service.impl;

import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.starry.admin.modules.clerk.mapper.PlayClerkPkMapper;
import com.starry.admin.modules.clerk.module.entity.ClerkPkEnum;
import com.starry.admin.modules.clerk.module.entity.PlayClerkPkEntity;
import com.starry.admin.modules.clerk.module.entity.PlayClerkUserInfoEntity;
import com.starry.admin.modules.clerk.service.IPlayClerkPkService;
import com.starry.admin.modules.clerk.service.IPlayClerkUserInfoService;
import com.starry.common.utils.IdUtils;
import java.util.Arrays;
import java.util.Date;
import javax.annotation.Resource;
import org.springframework.stereotype.Service;

/**
 * 店员pkService业务层处理
 *
 * @author admin
 * @since 2024-08-02
 */
@Service
public class PlayClerkPkServiceImpl extends ServiceImpl<PlayClerkPkMapper, PlayClerkPkEntity>
        implements
            IPlayClerkPkService {
    @Resource
    private PlayClerkPkMapper playClerkPkMapper;
    @Resource
    private IPlayClerkUserInfoService clerkUserInfoService;

    /**
     * 查询店员pk
     *
     * @param id
     *            店员pk主键
     * @return 店员pk
     */
    @Override
    public PlayClerkPkEntity selectPlayClerkPkById(String id) {
        return this.baseMapper.selectById(id);
    }

    /**
     * 查询店员pk列表
     *
     * @param playClerkPk
     *            店员pk
     * @return 店员pk
     */
    @Override
    public IPage<PlayClerkPkEntity> selectPlayClerkPkByPage(PlayClerkPkEntity playClerkPk) {
        Page<PlayClerkPkEntity> page = new Page<>(1, 10);
        return this.baseMapper.selectPage(page, new LambdaQueryWrapper<>());
    }

    /**
     * 新增店员pk
     *
     * @param playClerkPk
     *            店员pk
     * @return 结果
     */
    @Override
    public boolean create(PlayClerkPkEntity playClerkPk) {
        if (StrUtil.isBlankIfStr(playClerkPk.getId())) {
            playClerkPk.setId(IdUtils.getUuid());
        }
        if (DateUtil.compare(playClerkPk.getPkBeginTime(), new Date()) < 1) {
            throw new RuntimeException("开始时间必须大于当前时间");
        }
        if (DateUtil.compare(playClerkPk.getPkEndTime(), new Date()) < 1) {
            throw new RuntimeException("结束时间必须大于当前时间");
        }
        LambdaQueryWrapper<PlayClerkPkEntity> wrapper = Wrappers.lambdaQuery(PlayClerkPkEntity.class)
                .in(PlayClerkPkEntity::getClerkA, Arrays.asList(playClerkPk.getClerkA(), playClerkPk.getClerkB()))
                .in(PlayClerkPkEntity::getStatus,
                        Arrays.asList(ClerkPkEnum.TO_BE_STARTED.name(), ClerkPkEnum.IN_PROGRESS.name()));
        long count = this.count(wrapper);
        if (count > 0) {
            PlayClerkUserInfoEntity clerkUserInfo = clerkUserInfoService.selectById(playClerkPk.getClerkA());
            throw new RuntimeException("店员【" + clerkUserInfo.getNickname() + "】还有未完成的PK，无法继续添加");
        }
        wrapper.clear();
        wrapper = Wrappers.lambdaQuery(PlayClerkPkEntity.class)
                .in(PlayClerkPkEntity::getClerkB, Arrays.asList(playClerkPk.getClerkA(), playClerkPk.getClerkB()))
                .in(PlayClerkPkEntity::getStatus,
                        Arrays.asList(ClerkPkEnum.TO_BE_STARTED.name(), ClerkPkEnum.IN_PROGRESS.name()));
        count = this.count(wrapper);
        if (count > 0) {
            PlayClerkUserInfoEntity clerkUserInfo = clerkUserInfoService.selectById(playClerkPk.getClerkB());
            throw new RuntimeException("店员【" + clerkUserInfo.getNickname() + "】还有未完成的PK，无法继续添加");
        }

        playClerkPk.setStatus(ClerkPkEnum.TO_BE_STARTED.name());
        return save(playClerkPk);
    }

    /**
     * 修改店员pk
     *
     * @param playClerkPk
     *            店员pk
     * @return 结果
     */
    @Override
    public boolean update(PlayClerkPkEntity playClerkPk) {
        return updateById(playClerkPk);
    }

    /**
     * 批量删除店员pk
     *
     * @param ids
     *            需要删除的店员pk主键
     * @return 结果
     */
    @Override
    public int deletePlayClerkPkByIds(String[] ids) {
        return playClerkPkMapper.deleteBatchIds(Arrays.asList(ids));
    }

    /**
     * 删除店员pk信息
     *
     * @param id
     *            店员pk主键
     * @return 结果
     */
    @Override
    public int deletePlayClerkPkById(String id) {
        return playClerkPkMapper.deleteById(id);
    }
}


--- File: play-admin/src/main/java/com/starry/admin/modules/clerk/service/impl/PlayClerkRankingInfoServiceImpl.java ---
package com.starry.admin.modules.clerk.service.impl;

import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.github.yulichang.wrapper.MPJLambdaWrapper;
import com.starry.admin.modules.clerk.mapper.PlayClerkRankingInfoMapper;
import com.starry.admin.modules.clerk.module.entity.*;
import com.starry.admin.modules.clerk.service.IPlayClerkRankingInfoService;
import com.starry.admin.modules.personnel.service.IPlayPersonnelGroupInfoService;
import com.starry.admin.modules.weichat.entity.order.PlayOrderHistoryRankingReturnVo;
import com.starry.admin.modules.weichat.entity.order.PlayOrderRankingListVo;
import com.starry.admin.modules.weichat.entity.order.PlayOrderRankingReturnVo;
import com.starry.admin.utils.SecurityUtils;
import com.starry.common.utils.IdUtils;
import java.time.LocalDate;
import java.util.Arrays;
import java.util.List;
import javax.annotation.Resource;
import org.springframework.stereotype.Service;

/**
 * 店员排行Service业务层处理
 *
 * @author admin
 * @since 2024-05-25
 */
@Service
public class PlayClerkRankingInfoServiceImpl extends ServiceImpl<PlayClerkRankingInfoMapper, PlayClerkRankingInfoEntity>
        implements
            IPlayClerkRankingInfoService {
    @Resource
    private PlayClerkRankingInfoMapper playClerkRankingInfoMapper;

    @Resource
    private IPlayPersonnelGroupInfoService playClerkGroupInfoService;

    @Override
    public IPage<IPlayClerkRankingInfoReturnVo> selectByPage(IPlayClerkRankingInfoQueryVo vo) {
        MPJLambdaWrapper<PlayClerkRankingInfoEntity> lambdaQueryWrapper = new MPJLambdaWrapper<>();
        lambdaQueryWrapper.selectAll(PlayClerkRankingInfoEntity.class);
        // 拼接店员表
        lambdaQueryWrapper.selectAs(PlayClerkUserInfoEntity::getNickname, "clerkNickname")
                .selectAs(PlayClerkUserInfoEntity::getSex, "clerkSex");
        lambdaQueryWrapper.leftJoin(PlayClerkUserInfoEntity.class, PlayClerkUserInfoEntity::getId,
                PlayClerkRankingInfoEntity::getClerkId);
        if (StrUtil.isNotBlank(vo.getClerkId())) {
            lambdaQueryWrapper.eq(PlayClerkRankingInfoEntity::getClerkId, vo.getClerkId());
        }
        if (StrUtil.isNotBlank(vo.getWeeklyRanking())) {
            lambdaQueryWrapper.eq(PlayClerkRankingInfoEntity::getWeeklyRanking, vo.getWeeklyRanking());
        }
        if (StrUtil.isNotBlank(vo.getHistoricalStatistics())) {
            lambdaQueryWrapper.eq(PlayClerkRankingInfoEntity::getHistoricalStatistics, vo.getHistoricalStatistics());
        }
        if (StrUtil.isNotBlank(vo.getSex())) {
            lambdaQueryWrapper.eq(PlayClerkUserInfoEntity::getSex, vo.getSex());
        }
        if (vo.getSettlementDate() != null) {
            lambdaQueryWrapper.eq(PlayClerkRankingInfoEntity::getSettlementDate, vo.getSettlementDate());
        }
        // 加入组员的筛选
        List<String> clerkIdList = playClerkGroupInfoService.getValidClerkIdList(SecurityUtils.getLoginUser(), null);
        lambdaQueryWrapper.in(PlayClerkUserInfoEntity::getId, clerkIdList);

        lambdaQueryWrapper.orderByAsc(PlayClerkRankingInfoEntity::getRankingIndex);
        return this.baseMapper.selectJoinPage(new Page<>(vo.getPageNum(), vo.getPageSize()),
                IPlayClerkRankingInfoReturnVo.class, lambdaQueryWrapper);
    }

    /**
     * 查询店员排行
     *
     * @param id
     *            店员排行主键
     * @return 店员排行
     */
    @Override
    public PlayClerkRankingInfoEntity selectPlayClerkRankingInfoById(String id) {
        return this.baseMapper.selectById(id);
    }

    @Override
    public List<PlayOrderHistoryRankingReturnVo> selectHistoryRanking(String clerkId) {
        long serialNumber = selectSerialNumber("1");
        MPJLambdaWrapper<PlayClerkRankingInfoEntity> lambdaQueryWrapper = new MPJLambdaWrapper<>();
        // 根据排行序号和用户ID查询
        lambdaQueryWrapper.ne(PlayClerkRankingInfoEntity::getSerialNumber, serialNumber);
        lambdaQueryWrapper.eq(PlayClerkRankingInfoEntity::getClerkId, clerkId);
        return this.baseMapper.selectJoinList(PlayOrderHistoryRankingReturnVo.class, lambdaQueryWrapper);
    }

    @Override
    public PlayOrderRankingReturnVo selectCurrentRanking(String clerkId) {
        long serialNumber = selectSerialNumber("1");
        MPJLambdaWrapper<PlayClerkRankingInfoEntity> lambdaQueryWrapper = new MPJLambdaWrapper<>();
        lambdaQueryWrapper.selectAll(PlayClerkRankingInfoEntity.class);
        // 店员信息
        lambdaQueryWrapper.selectAs(PlayClerkUserInfoEntity::getId, "clerkId")
                .selectAs(PlayClerkUserInfoEntity::getNickname, "clerkNickname")
                .selectAs(PlayClerkUserInfoEntity::getAvatar, "clerkAvatar")
                .selectAs(PlayClerkUserInfoEntity::getSex, "clerkSex");
        lambdaQueryWrapper.leftJoin(PlayClerkUserInfoEntity.class, PlayClerkUserInfoEntity::getId,
                PlayClerkRankingInfoEntity::getClerkId);
        // 店员等级
        lambdaQueryWrapper.selectAs(PlayClerkLevelInfoEntity::getId, "levelId")
                .selectAs(PlayClerkLevelInfoEntity::getName, "levelName");
        lambdaQueryWrapper.leftJoin(PlayClerkLevelInfoEntity.class, PlayClerkLevelInfoEntity::getId,
                PlayClerkUserInfoEntity::getLevelId);
        // 根据排行序号和用户ID查询
        lambdaQueryWrapper.eq(PlayClerkRankingInfoEntity::getSerialNumber, serialNumber);
        lambdaQueryWrapper.eq(PlayClerkRankingInfoEntity::getClerkId, clerkId);
        lambdaQueryWrapper.eq(PlayClerkRankingInfoEntity::getHistoricalStatistics, "0");
        PlayOrderRankingReturnVo returnVo = this.baseMapper.selectJoinOne(PlayOrderRankingReturnVo.class,
                lambdaQueryWrapper);
        if (returnVo == null) {
            returnVo = new PlayOrderRankingReturnVo();
        }
        returnVo.setRankings(this.selectBySerialNumber(serialNumber));
        return returnVo;
    }

    @Override
    public List<PlayClerkRankingInfoEntity> selectMaxSerialNumber(String weeklyRanking) {
        MPJLambdaWrapper<PlayClerkRankingInfoEntity> lambdaQueryWrapper = new MPJLambdaWrapper<>();
        lambdaQueryWrapper.eq(PlayClerkRankingInfoEntity::getSerialNumber, this.selectSerialNumber(weeklyRanking));
        return this.baseMapper.selectList(lambdaQueryWrapper);
    }

    @Override
    public void updateClerkRankingInfo(String weeklyRanking, Integer serialNumber) {
        LambdaUpdateWrapper<PlayClerkRankingInfoEntity> lambdaUpdateWrapper = new LambdaUpdateWrapper<>();
        lambdaUpdateWrapper.eq(PlayClerkRankingInfoEntity::getWeeklyRanking, weeklyRanking);
        lambdaUpdateWrapper.eq(PlayClerkRankingInfoEntity::getSerialNumber, serialNumber);
        lambdaUpdateWrapper.set(PlayClerkRankingInfoEntity::getHistoricalStatistics, "1");
        this.baseMapper.update(null, lambdaUpdateWrapper);
    }

    /**
     * 获取当前期排名序号
     *
     * @return PlayClerkRankingInfoEntity
     */
    @Override
    public Integer selectSerialNumber(String weeklyRanking) {
        LambdaQueryWrapper<PlayClerkRankingInfoEntity> lambdaQueryWrapper = new LambdaQueryWrapper<>();
        lambdaQueryWrapper.eq(PlayClerkRankingInfoEntity::getWeeklyRanking, weeklyRanking);
        lambdaQueryWrapper.orderByDesc(PlayClerkRankingInfoEntity::getSerialNumber).last("limit 1");
        PlayClerkRankingInfoEntity entity = this.baseMapper.selectOne(lambdaQueryWrapper);
        if (entity == null || entity.getSerialNumber() == null) {
            return 0;
        }
        return entity.getSerialNumber();
    }

    List<PlayOrderRankingListVo> selectBySerialNumber(long serialNumber) {
        MPJLambdaWrapper<PlayClerkRankingInfoEntity> lambdaQueryWrapper = new MPJLambdaWrapper<>();
        lambdaQueryWrapper.eq(PlayClerkRankingInfoEntity::getSerialNumber, serialNumber);
        lambdaQueryWrapper.orderByDesc(PlayClerkRankingInfoEntity::getRankingIndex);
        lambdaQueryWrapper.selectAll(PlayClerkRankingInfoEntity.class);
        // 店员信息
        lambdaQueryWrapper.selectAs(PlayClerkUserInfoEntity::getId, "clerkId")
                .selectAs(PlayClerkUserInfoEntity::getNickname, "clerkNickname")
                .selectAs(PlayClerkUserInfoEntity::getAvatar, "clerkAvatar")
                .selectAs(PlayClerkUserInfoEntity::getSex, "clerkSex");
        lambdaQueryWrapper.leftJoin(PlayClerkUserInfoEntity.class, PlayClerkUserInfoEntity::getId,
                PlayClerkRankingInfoEntity::getClerkId);
        // 店员等级
        lambdaQueryWrapper.selectAs(PlayClerkLevelInfoEntity::getId, "levelId")
                .selectAs(PlayClerkLevelInfoEntity::getName, "levelName");
        lambdaQueryWrapper.leftJoin(PlayClerkLevelInfoEntity.class, PlayClerkLevelInfoEntity::getId,
                PlayClerkUserInfoEntity::getLevelId);
        return this.baseMapper.selectJoinList(PlayOrderRankingListVo.class, lambdaQueryWrapper);
    }

    @Override
    public PlayClerkRankingInfoEntity selectByTime(String clerkId, LocalDate startTime, LocalDate endTime) {
        MPJLambdaWrapper<PlayClerkRankingInfoEntity> lambdaQueryWrapper = new MPJLambdaWrapper<>();
        lambdaQueryWrapper.eq(PlayClerkRankingInfoEntity::getStartCountDate, startTime);
        lambdaQueryWrapper.eq(PlayClerkRankingInfoEntity::getEndCountDate, endTime);
        lambdaQueryWrapper.eq(PlayClerkRankingInfoEntity::getClerkId, clerkId);
        return this.baseMapper.selectOne(lambdaQueryWrapper);
    }

    /**
     * 查询店员排行列表
     *
     * @param playClerkRankingInfo
     *            店员排行
     * @return 店员排行
     */
    @Override
    public IPage<PlayClerkRankingInfoEntity> selectPlayClerkRankingInfoByPage(
            PlayClerkRankingInfoEntity playClerkRankingInfo) {
        Page<PlayClerkRankingInfoEntity> page = new Page<>(1, 10);
        return this.baseMapper.selectPage(page, new LambdaQueryWrapper<>());
    }

    /**
     * 新增店员排行
     *
     * @param playClerkRankingInfo
     *            店员排行
     * @return 结果
     */
    @Override
    public boolean create(PlayClerkRankingInfoEntity playClerkRankingInfo) {
        if (StrUtil.isBlankIfStr(playClerkRankingInfo.getId())) {
            playClerkRankingInfo.setId(IdUtils.getUuid());
        }
        return save(playClerkRankingInfo);
    }

    /**
     * 修改店员排行
     *
     * @param playClerkRankingInfo
     *            店员排行
     * @return 结果
     */
    @Override
    public boolean update(PlayClerkRankingInfoEntity playClerkRankingInfo) {
        return updateById(playClerkRankingInfo);
    }

    /**
     * 批量删除店员排行
     *
     * @param ids
     *            需要删除的店员排行主键
     * @return 结果
     */
    @Override
    public int deletePlayClerkRankingInfoByIds(String[] ids) {
        return playClerkRankingInfoMapper.deleteBatchIds(Arrays.asList(ids));
    }

    /**
     * 删除店员排行信息
     *
     * @param id
     *            店员排行主键
     * @return 结果
     */
    @Override
    public int deletePlayClerkRankingInfoById(String id) {
        return playClerkRankingInfoMapper.deleteById(id);
    }
}


--- File: play-admin/src/main/java/com/starry/admin/modules/clerk/service/impl/PlayClerkTypeInfoServiceImpl.java ---
package com.starry.admin.modules.clerk.service.impl;

import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.github.yulichang.wrapper.MPJLambdaWrapper;
import com.starry.admin.modules.clerk.mapper.PlayClerkTypeInfoMapper;
import com.starry.admin.modules.clerk.module.entity.PlayClerkTypeInfoEntity;
import com.starry.admin.modules.clerk.module.vo.PlayClerkTypeInfoQueryVo;
import com.starry.admin.modules.clerk.module.vo.PlayClerkTypeInfoReturnVo;
import com.starry.admin.modules.clerk.service.IPlayClerkTypeInfoService;
import com.starry.admin.modules.clerk.service.IPlayClerkUserInfoService;
import com.starry.common.utils.IdUtils;
import java.util.Arrays;
import java.util.List;
import javax.annotation.Resource;
import org.springframework.stereotype.Service;

/**
 * 店员分类信息Service业务层处理
 *
 * @author admin
 * @since 2024-05-31
 */
@Service
public class PlayClerkTypeInfoServiceImpl extends ServiceImpl<PlayClerkTypeInfoMapper, PlayClerkTypeInfoEntity>
        implements
            IPlayClerkTypeInfoService {

    @Resource
    private PlayClerkTypeInfoMapper playClerkTypeInfoMapper;

    @Resource
    private IPlayClerkUserInfoService playClerkUserInfoService;

    /**
     * 查询店员分类信息
     *
     * @param id
     *            店员分类信息主键
     * @return 店员分类信息
     */
    @Override
    public PlayClerkTypeInfoEntity selectPlayClerkTypeInfoById(String id) {
        return this.baseMapper.selectById(id);
    }

    @Override
    public List<PlayClerkTypeInfoEntity> selectByAll() {
        return this.baseMapper.selectList(new LambdaQueryWrapper<>());
    }

    /**
     * 查询店员分类信息列表
     *
     * @param vo
     *            店员分类信息
     * @return 店员分类信息
     */
    @Override
    public IPage<PlayClerkTypeInfoReturnVo> selectByPage(PlayClerkTypeInfoQueryVo vo) {
        MPJLambdaWrapper<PlayClerkTypeInfoEntity> lambdaWrapper = new MPJLambdaWrapper<>();
        lambdaWrapper.orderByAsc(PlayClerkTypeInfoEntity::getSort);
        IPage<PlayClerkTypeInfoReturnVo> page = this.baseMapper.selectJoinPage(
                new Page<>(vo.getPageNum(), vo.getPageSize()), PlayClerkTypeInfoReturnVo.class, lambdaWrapper);
        for (PlayClerkTypeInfoReturnVo record : page.getRecords()) {
            record.setClerkNumber(playClerkUserInfoService.listAllByTypeId(record.getId()).size());
        }
        return page;
    }

    /**
     * 新增店员分类信息
     *
     * @param playClerkTypeInfo
     *            店员分类信息
     * @return 结果
     */
    @Override
    public boolean create(PlayClerkTypeInfoEntity playClerkTypeInfo) {
        if (StrUtil.isBlankIfStr(playClerkTypeInfo.getId())) {
            playClerkTypeInfo.setId(IdUtils.getUuid());
        }
        return save(playClerkTypeInfo);
    }

    /**
     * 修改店员分类信息
     *
     * @param playClerkTypeInfo
     *            店员分类信息
     * @return 结果
     */
    @Override
    public boolean update(PlayClerkTypeInfoEntity playClerkTypeInfo) {
        return updateById(playClerkTypeInfo);
    }

    /**
     * 批量删除店员分类信息
     *
     * @param ids
     *            需要删除的店员分类信息主键
     * @return 结果
     */
    @Override
    public int deletePlayClerkTypeInfoByIds(String[] ids) {
        return playClerkTypeInfoMapper.deleteBatchIds(Arrays.asList(ids));
    }

    /**
     * 删除店员分类信息信息
     *
     * @param id
     *            店员分类信息主键
     * @return 结果
     */
    @Override
    public int deletePlayClerkTypeInfoById(String id) {
        return playClerkTypeInfoMapper.deleteById(id);
    }
}


--- File: play-admin/src/main/java/com/starry/admin/modules/clerk/service/impl/PlayClerkTypeUserInfoServiceImpl.java ---
package com.starry.admin.modules.clerk.service.impl;

import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.starry.admin.modules.clerk.mapper.PlayClerkTypeUserInfoMapper;
import com.starry.admin.modules.clerk.module.entity.PlayClerkTypeUserInfoEntity;
import com.starry.admin.modules.clerk.service.IPlayClerkTypeUserInfoService;
import com.starry.common.utils.IdUtils;
import java.util.Arrays;
import javax.annotation.Resource;
import org.springframework.stereotype.Service;

/**
 * 店员和分类关系Service业务层处理
 *
 * @author admin
 * @since 2024-05-31
 */
@Service
public class PlayClerkTypeUserInfoServiceImpl
        extends
            ServiceImpl<PlayClerkTypeUserInfoMapper, PlayClerkTypeUserInfoEntity>
        implements
            IPlayClerkTypeUserInfoService {
    @Resource
    private PlayClerkTypeUserInfoMapper playClerkTypeUserInfoMapper;

    /**
     * 查询店员和分类关系
     *
     * @param id
     *            店员和分类关系主键
     * @return 店员和分类关系
     */
    @Override
    public PlayClerkTypeUserInfoEntity selectPlayClerkTypeUserInfoById(String id) {
        return this.baseMapper.selectById(id);
    }

    /**
     * 查询店员和分类关系列表
     *
     * @param playClerkTypeUserInfo
     *            店员和分类关系
     * @return 店员和分类关系
     */
    @Override
    public IPage<PlayClerkTypeUserInfoEntity> selectPlayClerkTypeUserInfoByPage(
            PlayClerkTypeUserInfoEntity playClerkTypeUserInfo) {
        Page<PlayClerkTypeUserInfoEntity> page = new Page<>(1, 10);
        return this.baseMapper.selectPage(page, new LambdaQueryWrapper<PlayClerkTypeUserInfoEntity>());
    }

    /**
     * 新增店员和分类关系
     *
     * @param playClerkTypeUserInfo
     *            店员和分类关系
     * @return 结果
     */
    @Override
    public boolean create(PlayClerkTypeUserInfoEntity playClerkTypeUserInfo) {
        if (StrUtil.isBlankIfStr(playClerkTypeUserInfo.getId())) {
            playClerkTypeUserInfo.setId(IdUtils.getUuid());
        }
        return save(playClerkTypeUserInfo);
    }

    /**
     * 修改店员和分类关系
     *
     * @param playClerkTypeUserInfo
     *            店员和分类关系
     * @return 结果
     */
    @Override
    public boolean update(PlayClerkTypeUserInfoEntity playClerkTypeUserInfo) {
        return updateById(playClerkTypeUserInfo);
    }

    /**
     * 批量删除店员和分类关系
     *
     * @param ids
     *            需要删除的店员和分类关系主键
     * @return 结果
     */
    @Override
    public int deletePlayClerkTypeUserInfoByIds(String[] ids) {
        return playClerkTypeUserInfoMapper.deleteBatchIds(Arrays.asList(ids));
    }

    /**
     * 删除店员和分类关系信息
     *
     * @param id
     *            店员和分类关系主键
     * @return 结果
     */
    @Override
    public int deletePlayClerkTypeUserInfoById(String id) {
        return playClerkTypeUserInfoMapper.deleteById(id);
    }
}


--- File: play-admin/src/main/java/com/starry/admin/modules/clerk/service/impl/PlayClerkUserInfoServiceImpl.java ---
package com.starry.admin.modules.clerk.service.impl;

import cn.hutool.core.util.StrUtil;
import com.alibaba.fastjson2.JSONObject;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.github.yulichang.wrapper.MPJLambdaWrapper;
import com.starry.admin.common.component.JwtToken;
import com.starry.admin.common.domain.LoginUser;
import com.starry.admin.common.exception.CustomException;
import com.starry.admin.modules.clerk.mapper.PlayClerkUserInfoMapper;
import com.starry.admin.modules.clerk.module.entity.*;
import com.starry.admin.modules.clerk.module.vo.PlayClerkCommodityQueryVo;
import com.starry.admin.modules.clerk.module.vo.PlayClerkUnsettledWagesInfoQueryVo;
import com.starry.admin.modules.clerk.module.vo.PlayClerkUnsettledWagesInfoReturnVo;
import com.starry.admin.modules.clerk.service.IPlayClerkCommodityService;
import com.starry.admin.modules.clerk.service.IPlayClerkLevelInfoService;
import com.starry.admin.modules.clerk.service.IPlayClerkUserInfoService;
import com.starry.admin.modules.custom.entity.PlayCustomFollowInfoEntity;
import com.starry.admin.modules.custom.service.IPlayCustomFollowInfoService;
import com.starry.admin.modules.order.module.entity.PlayOrderInfoEntity;
import com.starry.admin.modules.order.service.IPlayOrderInfoService;
import com.starry.admin.modules.personnel.module.entity.PlayPersonnelAdminInfoEntity;
import com.starry.admin.modules.personnel.module.entity.PlayPersonnelGroupInfoEntity;
import com.starry.admin.modules.personnel.module.entity.PlayPersonnelWaiterInfoEntity;
import com.starry.admin.modules.personnel.service.IPlayBalanceDetailsInfoService;
import com.starry.admin.modules.personnel.service.IPlayPersonnelAdminInfoService;
import com.starry.admin.modules.personnel.service.IPlayPersonnelGroupInfoService;
import com.starry.admin.modules.personnel.service.IPlayPersonnelWaiterInfoService;
import com.starry.admin.modules.statistics.module.vo.PlayClerkPerformanceInfoQueryVo;
import com.starry.admin.modules.system.service.LoginService;
import com.starry.admin.modules.weichat.entity.PlayClerkUserLoginResponseVo;
import com.starry.admin.modules.weichat.entity.clerk.PlayClerkUserInfoQueryVo;
import com.starry.admin.modules.weichat.entity.clerk.PlayClerkUserInfoResultVo;
import com.starry.admin.utils.SecurityUtils;
import com.starry.common.utils.ConvertUtil;
import com.starry.common.utils.IdUtils;
import com.starry.common.utils.StringUtils;
import java.math.BigDecimal;
import java.util.*;
import java.util.stream.Collectors;
import javax.annotation.Resource;
import org.springframework.stereotype.Service;

/**
 * 店员Service业务层处理
 *
 * @author admin
 * @since 2024-03-30
 */
@Service
public class PlayClerkUserInfoServiceImpl extends ServiceImpl<PlayClerkUserInfoMapper, PlayClerkUserInfoEntity>
        implements
            IPlayClerkUserInfoService {
    @Resource
    private PlayClerkUserInfoMapper playClerkUserInfoMapper;
    @Resource
    private PlayClerkUserReviewInfoServiceImpl playClerkUserReviewInfoService;
    @Resource
    private IPlayClerkCommodityService playClerkCommodityService;
    @Resource
    private IPlayCustomFollowInfoService customFollowInfoService;
    @Resource
    private IPlayBalanceDetailsInfoService playBalanceDetailsInfoService;
    @Resource
    private IPlayOrderInfoService playOrderInfoService;
    @Resource
    private IPlayPersonnelGroupInfoService playClerkGroupInfoService;
    @Resource
    private IPlayPersonnelAdminInfoService playPersonnelAdminInfoService;
    @Resource
    private IPlayPersonnelWaiterInfoService playClerkWaiterInfoService;
    @Resource
    private LoginService loginService;
    @Resource
    private JwtToken jwtToken;
    @Resource
    private IPlayClerkLevelInfoService playClerkLevelInfoService;

    @Override
    public List<PlayClerkUserInfoEntity> listAllByTypeId(String typeId) {
        MPJLambdaWrapper<PlayClerkUserInfoEntity> lambdaWrapper = new MPJLambdaWrapper<>();
        lambdaWrapper.select(PlayClerkUserInfoEntity::getId);
        lambdaWrapper.eq(PlayClerkUserInfoEntity::getTypeId, typeId);
        return this.baseMapper.selectList(lambdaWrapper);
    }

    @Override
    public List<PlayClerkUserInfoEntity> listAllByGroupId(String groupId) {
        MPJLambdaWrapper<PlayClerkUserInfoEntity> lambdaWrapper = new MPJLambdaWrapper<>();
        lambdaWrapper.select(PlayClerkUserInfoEntity::getId, PlayClerkUserInfoEntity::getListingState);
        lambdaWrapper.eq(PlayClerkUserInfoEntity::getGroupId, groupId);
        return this.baseMapper.selectList(lambdaWrapper);
    }

    @Override
    public Long getTotalClerkUser(String tenantId) {
        MPJLambdaWrapper<PlayClerkUserInfoEntity> lambdaWrapper = new MPJLambdaWrapper<>();
        lambdaWrapper.eq(PlayClerkUserInfoEntity::getTenantId, tenantId);
        return this.baseMapper.selectCount(lambdaWrapper);
    }

    @Override
    public PlayClerkLevelInfoEntity queryLevelCommission(String clerkId) {
        MPJLambdaWrapper<PlayClerkUserInfoEntity> lambdaWrapper = new MPJLambdaWrapper<>();
        lambdaWrapper.selectAll(PlayClerkLevelInfoEntity.class);
        lambdaWrapper.selectAs(PlayClerkUserInfoEntity::getLevelId, "levelId");
        lambdaWrapper.leftJoin(PlayClerkLevelInfoEntity.class, PlayClerkLevelInfoEntity::getId,
                PlayClerkUserInfoEntity::getLevelId);
        lambdaWrapper.eq(PlayClerkUserInfoEntity::getId, clerkId);
        return this.baseMapper.selectJoinOne(PlayClerkLevelInfoEntity.class, lambdaWrapper);

    }

    @Override
    public PlayClerkUserInfoEntity selectByOpenid(String openId) {
        LambdaQueryWrapper<PlayClerkUserInfoEntity> lambdaQueryWrapper = new LambdaQueryWrapper<>();
        lambdaQueryWrapper.eq(PlayClerkUserInfoEntity::getOpenid, openId);
        return this.baseMapper.selectOne(lambdaQueryWrapper);
    }

    /**
     * 查询店员
     *
     * @param id
     *            店员主键
     * @return 店员
     */
    @Override
    public PlayClerkUserInfoEntity selectById(String id) {
        PlayClerkUserInfoEntity entity = this.baseMapper.selectById(id);
        if (entity == null) {
            throw new CustomException("店员不存在");
        }
        return entity;
    }

    @Override
    public PlayClerkUserLoginResponseVo getVo(PlayClerkUserInfoEntity userInfo) {
        PlayClerkUserLoginResponseVo result = ConvertUtil.entityToVo(userInfo, PlayClerkUserLoginResponseVo.class);
        // List<PlayClerkDataReviewInfoEntity> list =
        // playClerkDataReviewInfoService.queryByClerkId(userInfo.getId(),"0");
        // // 判断头像、音频、相册是否可以编辑，如果存在未审核的数据，则不允许编辑
        // Map<String, PlayClerkDataReviewInfoEntity> map =
        // list.stream().collect(Collectors.toMap(PlayClerkDataReviewInfoEntity::getDataType,
        // account -> account, (entity1, entity2) -> entity1));
        // if (map.containsKey("1")) {
        // result.setAvatarAllowEdit(false);
        // }
        // if (map.containsKey("2")) {
        // result.setAlbumAllowEdit(false);
        // }
        // if (map.containsKey("3")) {
        // result.setAudioAllowEdit(false);
        // }
        // 是店员之后，判断是否可以登录
        if ("1".equals(result.getClerkState())) {
            // 设置店员是否运行登录
            if ("0".equals(userInfo.getOnboardingState())) {
                result.setAllowLogin("1");
                result.setDisableLoginReason("你已离职，需要复职请联系店铺管理员");
            }
            if ("0".equals(userInfo.getListingState())) {
                result.setAllowLogin("1");
                result.setDisableLoginReason("你已被下架，没有权限访问");
            }

        }

        // 如果存在未审批的申请，或者当前已经是店员-可以申请陪聊
        PlayClerkUserReviewInfoEntity entity = playClerkUserReviewInfoService.queryByClerkId(userInfo.getId(), "0");
        if (entity != null || "1".equals(result.getClerkState())) {
            result.setClerkAllowEdit(false);
        }

        // 查询店员服务项目
        List<PlayClerkCommodityEntity> clerkCommodityEntities = playClerkCommodityService
                .selectCommodityTypeByUser(userInfo.getId(), "");
        List<PlayClerkCommodityQueryVo> playClerkCommodityQueryVos = new ArrayList<>();
        for (PlayClerkCommodityEntity clerkCommodityEntity : clerkCommodityEntities) {
            playClerkCommodityQueryVos.add(new PlayClerkCommodityQueryVo(clerkCommodityEntity.getCommodityName(),
                    clerkCommodityEntity.getEnablingState()));
        }
        result.setCommodity(playClerkCommodityQueryVos);
        result.setArea(userInfo.getProvince() + "-" + userInfo.getCity());

        result.setPcData(this.getPcData(userInfo));
        result.setLevelInfo(playClerkLevelInfoService.selectPlayClerkLevelInfoById(userInfo.getLevelId()));
        return result;
    }

    @Override
    public void updateTokenById(String id, String token) {
        PlayClerkUserInfoEntity entity = new PlayClerkUserInfoEntity();
        entity.setToken(token);
        entity.setId(id);
        entity.setOnlineState("1");
        this.baseMapper.updateById(entity);
    }

    @Override
    public void updateAccountBalanceById(String id, BigDecimal accountBalance) {

    }

    @Override
    public void updateAccountBalanceById(String userId, BigDecimal balanceBeforeOperation,
            BigDecimal balanceAfterOperation, String operationType, String operationAction, BigDecimal balanceMoney,
            String orderId) {
        // 修改用户余额
        this.baseMapper.updateById(new PlayClerkUserInfoEntity(userId, balanceAfterOperation));
        // 记录余额变更记录
        playBalanceDetailsInfoService.insertBalanceDetailsInfo("0", userId, balanceBeforeOperation,
                balanceAfterOperation, operationType, operationAction, balanceMoney, BigDecimal.ZERO, orderId);
    }

    /**
     * 查询店员列表
     *
     * @param vo
     *            店员查询对象
     * @return 店员
     */
    @Override
    public IPage<PlayClerkUserInfoResultVo> selectPlayClerkUserInfoByPage(PlayClerkUserInfoQueryVo vo) {
        Page<PlayClerkUserInfoResultVo> page = new Page<>(vo.getPageNum(), vo.getPageSize());

        MPJLambdaWrapper<PlayClerkUserInfoEntity> lambdaQueryWrapper = new MPJLambdaWrapper<>();
        // 查询不隐藏的
        lambdaQueryWrapper.eq(PlayClerkUserInfoEntity::getDisplayState, "1");
        // 查询主表全部字段
        lambdaQueryWrapper.selectAll(PlayClerkUserInfoEntity.class).selectAs(PlayClerkUserInfoEntity::getCity,
                "address");
        // 等级表
        lambdaQueryWrapper.selectAs(PlayClerkLevelInfoEntity::getName, "levelName");
        lambdaQueryWrapper.leftJoin(PlayClerkLevelInfoEntity.class, PlayClerkLevelInfoEntity::getId,
                PlayClerkUserInfoEntity::getLevelId);

        // 服务项目表
        if (StrUtil.isNotBlank(vo.getNickname())) {
            lambdaQueryWrapper.like(PlayClerkUserInfoEntity::getNickname, vo.getNickname());
        }
        if (StrUtil.isNotBlank(vo.getTypeId())) {
            lambdaQueryWrapper.eq(PlayClerkUserInfoEntity::getTypeId, vo.getTypeId());
        }
        if (StrUtil.isNotBlank(vo.getLevelId())) {
            lambdaQueryWrapper.eq(PlayClerkUserInfoEntity::getLevelId, vo.getLevelId());
        }
        if (StrUtil.isNotBlank(vo.getSex())) {
            lambdaQueryWrapper.eq(PlayClerkUserInfoEntity::getSex, vo.getSex());
        }
        if (StrUtil.isNotBlank(vo.getProvince())) {
            lambdaQueryWrapper.eq(PlayClerkUserInfoEntity::getProvince, vo.getProvince());
        }
        if (StrUtil.isNotBlank(vo.getClerkState())) {
            lambdaQueryWrapper.eq(PlayClerkUserInfoEntity::getClerkState, vo.getClerkState());
        }
        if (StrUtil.isNotBlank(vo.getRecommendationState())) {
            lambdaQueryWrapper.eq(PlayClerkUserInfoEntity::getRecommendationState, vo.getRecommendationState());
        }
        if (StrUtil.isNotBlank(vo.getOnboardingState())) {
            lambdaQueryWrapper.eq(PlayClerkUserInfoEntity::getOnboardingState, vo.getOnboardingState());
        }
        return this.baseMapper.selectJoinPage(page, PlayClerkUserInfoResultVo.class, lambdaQueryWrapper);
    }

    @Override
    public List<PlayClerkUserInfoEntity> listAll() {
        LambdaQueryWrapper<PlayClerkUserInfoEntity> lambdaQueryWrapper = new LambdaQueryWrapper<>();
        lambdaQueryWrapper.eq(PlayClerkUserInfoEntity::getClerkState, "1");
        return this.baseMapper.selectList(lambdaQueryWrapper);

    }

    @Override
    public IPage<PlayClerkUnsettledWagesInfoReturnVo> listUnsettledWagesByPage(PlayClerkUnsettledWagesInfoQueryVo vo) {
        MPJLambdaWrapper<PlayClerkUserInfoEntity> lambdaQueryWrapper = new MPJLambdaWrapper<>();
        // 查询所有店员
        lambdaQueryWrapper.selectAs(PlayClerkUserInfoEntity::getNickname, "clerkNickname")
                .selectAs(PlayClerkUserInfoEntity::getId, "clerkId");
        lambdaQueryWrapper.eq(PlayClerkUserInfoEntity::getClerkState, "1");
        // 加入组员的筛选
        List<String> clerkIdList = playClerkGroupInfoService.getValidClerkIdList(SecurityUtils.getLoginUser(), null);
        lambdaQueryWrapper.in(PlayClerkUserInfoEntity::getId, clerkIdList);
        if (StrUtil.isNotBlank(vo.getClerkId())) {
            lambdaQueryWrapper.eq(PlayClerkUserInfoEntity::getId, vo.getClerkId());
        }
        if (StrUtil.isNotBlank(vo.getListingState())) {
            lambdaQueryWrapper.eq(PlayClerkUserInfoEntity::getListingState, vo.getListingState());
        }
        // 查询店员订单信息
        lambdaQueryWrapper.selectCollection(PlayOrderInfoEntity.class,
                PlayClerkUnsettledWagesInfoReturnVo::getOrderInfoEntities);
        lambdaQueryWrapper.leftJoin(PlayOrderInfoEntity.class, PlayOrderInfoEntity::getAcceptBy,
                PlayClerkUserInfoEntity::getId);
        lambdaQueryWrapper.eq(PlayOrderInfoEntity::getOrderSettlementState, "0");

        return this.baseMapper.selectJoinPage(new Page<>(vo.getPageNum(), vo.getPageSize()),
                PlayClerkUnsettledWagesInfoReturnVo.class, lambdaQueryWrapper);
    }

    @Override
    public IPage<PlayClerkUserInfoEntity> selectByPage(PlayClerkPerformanceInfoQueryVo vo) {
        LambdaQueryWrapper<PlayClerkUserInfoEntity> lambdaQueryWrapper = new LambdaQueryWrapper<>();
        if (StrUtil.isNotBlank(vo.getClerkId())) {
            lambdaQueryWrapper.eq(PlayClerkUserInfoEntity::getId, vo.getClerkId());
        }
        // if (StrUtil.isNotBlank(vo.getGroupId())) {
        // lambdaQueryWrapper.eq(PlayClerkUserInfoEntity::getGroupId, vo.getGroupId());
        // }
        if (StrUtil.isNotBlank(vo.getSex())) {
            lambdaQueryWrapper.eq(PlayClerkUserInfoEntity::getSex, vo.getSex());
        }
        if (StrUtil.isNotBlank(vo.getListingState())) {
            lambdaQueryWrapper.eq(PlayClerkUserInfoEntity::getListingState, vo.getListingState());
        }
        lambdaQueryWrapper.eq(PlayClerkUserInfoEntity::getClerkState, "1");
        // 加入组员的筛选
        List<String> clerkIdList = playClerkGroupInfoService.getValidClerkIdList(SecurityUtils.getLoginUser(), null);
        lambdaQueryWrapper.in(PlayClerkUserInfoEntity::getId, clerkIdList);
        return this.baseMapper.selectPage(new Page<>(vo.getPageNum(), vo.getPageSize()), lambdaQueryWrapper);
    }

    @Override
    public IPage<PlayClerkUserReturnVo> selectByPage(PlayClerkUserQueryVo vo) {
        MPJLambdaWrapper<PlayClerkUserInfoEntity> lambdaQueryWrapper = new MPJLambdaWrapper<>();

        LoginUser loginUser = SecurityUtils.getLoginUser();
        // 主表（店员表全部字段）
        lambdaQueryWrapper.selectAll(PlayClerkUserInfoEntity.class);

        if (StrUtil.isNotBlank(vo.getId())) {
            lambdaQueryWrapper.eq(PlayClerkUserInfoEntity::getId, vo.getId());
        }
        if (StrUtil.isNotBlank(vo.getNickname())) {
            lambdaQueryWrapper.eq(PlayClerkUserInfoEntity::getNickname, vo.getNickname());
        }
        if (StrUtil.isNotBlank(vo.getPhone())) {
            lambdaQueryWrapper.eq(PlayClerkUserInfoEntity::getPhone, vo.getPhone());
        }
        if (StrUtil.isNotBlank(vo.getGroupId())) {
            lambdaQueryWrapper.eq(PlayClerkUserInfoEntity::getGroupId, vo.getGroupId());
        }
        if (StrUtil.isNotBlank(vo.getSex())) {
            lambdaQueryWrapper.eq(PlayClerkUserInfoEntity::getSex, vo.getSex());
        }
        if (StrUtil.isNotBlank(vo.getFixingLevel())) {
            lambdaQueryWrapper.eq(PlayClerkUserInfoEntity::getFixingLevel, vo.getFixingLevel());
        }
        if (StrUtil.isNotBlank(vo.getLevelId())) {
            lambdaQueryWrapper.eq(PlayClerkUserInfoEntity::getLevelId, vo.getLevelId());
        }
        if (StrUtil.isNotBlank(vo.getProvince())) {
            lambdaQueryWrapper.eq(PlayClerkUserInfoEntity::getProvince, vo.getProvince());
        }
        if (StrUtil.isNotBlank(vo.getCity())) {
            lambdaQueryWrapper.eq(PlayClerkUserInfoEntity::getCity, vo.getCity());
        }
        if (StrUtil.isNotBlank(vo.getOnboardingState())) {
            lambdaQueryWrapper.eq(PlayClerkUserInfoEntity::getOnboardingState, vo.getOnboardingState());
        }
        if (StrUtil.isNotBlank(vo.getRecommendationState())) {
            lambdaQueryWrapper.eq(PlayClerkUserInfoEntity::getRecommendationState, vo.getRecommendationState());
        }
        if (StrUtil.isNotBlank(vo.getPinToTopState())) {
            lambdaQueryWrapper.eq(PlayClerkUserInfoEntity::getPinToTopState, vo.getPinToTopState());
        }
        if (StrUtil.isNotBlank(vo.getOnlineState())) {
            lambdaQueryWrapper.eq(PlayClerkUserInfoEntity::getOnlineState, vo.getOnlineState());
        }
        if (StrUtil.isNotBlank(vo.getDisplayState())) {
            lambdaQueryWrapper.eq(PlayClerkUserInfoEntity::getDisplayState, vo.getDisplayState());
        }
        if (StrUtil.isNotBlank(vo.getRealState())) {
            lambdaQueryWrapper.eq(PlayClerkUserInfoEntity::getRealState, vo.getRealState());
        }
        if (StrUtil.isNotBlank(vo.getMandatoryRealState())) {
            lambdaQueryWrapper.eq(PlayClerkUserInfoEntity::getMandatoryRealState, vo.getMandatoryRealState());
        }
        if (StrUtil.isNotBlank(vo.getClerkState())) {
            lambdaQueryWrapper.eq(PlayClerkUserInfoEntity::getClerkState, vo.getClerkState());
        }
        if (StrUtil.isNotBlank(vo.getCode())) {
            lambdaQueryWrapper.eq(PlayClerkUserInfoEntity::getCode, vo.getCode());
        }

        // 加入组员的筛选
        List<String> clerkIdList = playClerkGroupInfoService.getValidClerkIdList(SecurityUtils.getLoginUser(), null);
        lambdaQueryWrapper.in(PlayClerkUserInfoEntity::getId, clerkIdList);
        lambdaQueryWrapper.orderByDesc(PlayClerkUserInfoEntity::getCreatedTime);
        IPage<PlayClerkUserReturnVo> page = this.baseMapper.selectJoinPage(
                new Page<>(vo.getPageNum(), vo.getPageSize()), PlayClerkUserReturnVo.class, lambdaQueryWrapper);

        for (PlayClerkUserReturnVo record : page.getRecords()) {
            BigDecimal orderTotalAmount = new BigDecimal("0");
            int orderContinueNumber = 0;
            int orderNumber = 0;
            for (PlayOrderInfoEntity orderInfo : playOrderInfoService.queryBySettlementOrder(record.getId(), "")) {
                if ("0".equals(orderInfo.getFirstOrder())) {
                    orderContinueNumber++;
                }
                orderNumber++;
                if (!"4".equals(orderInfo.getOrderStatus())) {
                    orderTotalAmount = orderTotalAmount.add(orderInfo.getFinalAmount());
                }
            }
            record.setOrderTotalAmount(orderTotalAmount);
            record.setOrderNumber(String.valueOf(orderNumber));
            record.setOrderContinueNumber(String.valueOf(orderContinueNumber));
        }

        return page;
    }

    @Override
    public IPage<PlayClerkUserInfoResultVo> selectByPage(PlayClerkUserInfoQueryVo vo, String customUserId) {
        IPage<PlayClerkUserInfoResultVo> voPage = this.selectPlayClerkUserInfoByPage(vo);
        Map<String, String> customFollows = new HashMap<>(16);
        // 如果当前顾客已登录，查询用户关注状态
        if (StrUtil.isNotBlank(customUserId)) {
            LambdaQueryWrapper<PlayCustomFollowInfoEntity> lambdaQueryWrapper = new LambdaQueryWrapper<>();
            lambdaQueryWrapper.eq(PlayCustomFollowInfoEntity::getCustomId, customUserId);
            List<PlayCustomFollowInfoEntity> customFollowInfoEntities = customFollowInfoService
                    .list(lambdaQueryWrapper);
            customFollows = customFollowInfoEntities.stream().collect(Collectors
                    .toMap(PlayCustomFollowInfoEntity::getClerkId, PlayCustomFollowInfoEntity::getFollowState));
        }
        for (PlayClerkUserInfoResultVo record : voPage.getRecords()) {
            record.setFollowState(customFollows.containsKey(record.getId()) ? "1" : "0");
            record.setCommodity(playClerkCommodityService.getClerkCommodityList(record.getId(), "1"));
        }
        voPage.getRecords().parallelStream().forEach(ca -> {
            ca.setLevelInfo(playClerkLevelInfoService.selectPlayClerkLevelInfoById(ca.getLevelId()));
        });
        return voPage;
    }

    /**
     * 新增店员
     *
     * @param playClerkUserInfo
     *            店员
     * @return 结果
     */
    @Override
    public boolean create(PlayClerkUserInfoEntity playClerkUserInfo) {
        if (StrUtil.isBlankIfStr(playClerkUserInfo.getId())) {
            playClerkUserInfo.setId(IdUtils.getUuid());
        }
        return save(playClerkUserInfo);
    }

    /**
     * 修改店员
     *
     * @param playClerkUserInfo
     *            店员
     * @return 结果
     */
    @Override
    public boolean update(PlayClerkUserInfoEntity playClerkUserInfo) {
        return updateById(playClerkUserInfo);
    }

    /**
     * 批量删除店员
     *
     * @param ids
     *            需要删除的店员主键
     * @return 结果
     */
    @Override
    public int deletePlayClerkUserInfoByIds(String[] ids) {
        return playClerkUserInfoMapper.deleteBatchIds(Arrays.asList(ids));
    }

    /**
     * 删除店员信息
     *
     * @param id
     *            店员主键
     * @return 结果
     */
    @Override
    public int deletePlayClerkUserInfoById(String id) {
        return playClerkUserInfoMapper.deleteById(id);
    }

    @Override
    public List<PlayClerkUserInfoEntity> simpleList() {
        List<String> clerkIdList = playClerkGroupInfoService.getValidClerkIdList(SecurityUtils.getLoginUser(), null);
        LambdaQueryWrapper<PlayClerkUserInfoEntity> lambdaQueryWrapper = new LambdaQueryWrapper<>();
        lambdaQueryWrapper.eq(PlayClerkUserInfoEntity::getDeleted, 0);
        lambdaQueryWrapper.in(PlayClerkUserInfoEntity::getId, clerkIdList);
        lambdaQueryWrapper.select(PlayClerkUserInfoEntity::getId, PlayClerkUserInfoEntity::getNickname,
                PlayClerkUserInfoEntity::getAvatar, PlayClerkUserInfoEntity::getTypeId,
                PlayClerkUserInfoEntity::getGroupId, PlayClerkUserInfoEntity::getPhone);
        lambdaQueryWrapper.orderByDesc(PlayClerkUserInfoEntity::getId);
        return this.baseMapper.selectList(lambdaQueryWrapper);
    }

    @Override
    public JSONObject getPcData(PlayClerkUserInfoEntity entity) {
        JSONObject data = new JSONObject();
        data.fluentPut("token", "");
        data.fluentPut("role", "");
        if (StringUtils.isEmpty(entity.getSysUserId())) {
            return data;
        }
        LoginUser loginUserInfo = loginService.getLoginUserInfo(entity.getSysUserId());
        Map<String, Object> tokenMap = jwtToken.createToken(loginUserInfo);
        data.fluentPut("token", tokenMap.get("token"));
        PlayPersonnelAdminInfoEntity adminInfoEntity = playPersonnelAdminInfoService
                .selectByUserId(entity.getSysUserId());
        if (Objects.nonNull(adminInfoEntity)) {
            data.fluentPut("role", "operator");
            return data;
        }
        PlayPersonnelGroupInfoEntity groupInfoEntity = playClerkGroupInfoService.selectByUserId(entity.getSysUserId());
        if (Objects.nonNull(groupInfoEntity)) {
            data.fluentPut("role", "leader");
            return data;
        }
        PlayPersonnelWaiterInfoEntity waiterInfoEntity = playClerkWaiterInfoService
                .selectByUserId(entity.getSysUserId());
        if (Objects.nonNull(waiterInfoEntity)) {
            data.fluentPut("role", "waiter");
            return data;
        }
        return data;
    }
}


--- File: play-admin/src/main/java/com/starry/admin/modules/clerk/service/impl/PlayClerkUserReviewInfoServiceImpl.java ---
package com.starry.admin.modules.clerk.service.impl;

import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.github.yulichang.wrapper.MPJLambdaWrapper;
import com.starry.admin.common.exception.CustomException;
import com.starry.admin.modules.clerk.mapper.PlayClerkUserReviewInfoMapper;
import com.starry.admin.modules.clerk.module.entity.PlayClerkUserInfoEntity;
import com.starry.admin.modules.clerk.module.entity.PlayClerkUserReviewInfoEntity;
import com.starry.admin.modules.clerk.module.vo.PlayClerkUserReviewQueryVo;
import com.starry.admin.modules.clerk.module.vo.PlayClerkUserReviewReturnVo;
import com.starry.admin.modules.clerk.module.vo.PlayClerkUserReviewStateEditVo;
import com.starry.admin.modules.clerk.service.IPlayClerkCommodityService;
import com.starry.admin.modules.clerk.service.IPlayClerkUserInfoService;
import com.starry.admin.modules.clerk.service.IPlayClerkUserReviewInfoService;
import com.starry.admin.modules.weichat.service.WxCustomMpService;
import com.starry.common.utils.IdUtils;
import java.util.Arrays;
import javax.annotation.Resource;
import org.springframework.beans.BeanUtils;
import org.springframework.stereotype.Service;

/**
 * 店员资料审核Service业务层处理
 *
 * @author admin
 * @since 2024-05-19
 */
@Service
public class PlayClerkUserReviewInfoServiceImpl
        extends
            ServiceImpl<PlayClerkUserReviewInfoMapper, PlayClerkUserReviewInfoEntity>
        implements
            IPlayClerkUserReviewInfoService {

    @Resource
    private PlayClerkUserReviewInfoMapper playClerkUserReviewInfoMapper;
    @Resource
    private IPlayClerkUserInfoService playClerkUserInfoService;
    @Resource
    private IPlayClerkCommodityService clerkCommodityService;
    @Resource
    private WxCustomMpService wxCustomMpService;

    @Override
    public PlayClerkUserReviewInfoEntity queryByClerkId(String clerkId, String reviewState) {
        LambdaQueryWrapper<PlayClerkUserReviewInfoEntity> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(PlayClerkUserReviewInfoEntity::getClerkId, clerkId);
        queryWrapper.eq(PlayClerkUserReviewInfoEntity::getReviewState, reviewState);
        return this.baseMapper.selectOne(queryWrapper);
    }

    /**
     * 查询店员资料审核
     *
     * @param id
     *            店员资料审核主键
     * @return 店员资料审核
     */
    @Override
    public PlayClerkUserReviewInfoEntity selectPlayClerkUserReviewInfoById(String id) {
        PlayClerkUserReviewInfoEntity entity = this.baseMapper.selectById(id);
        if (entity == null) {
            throw new CustomException("对象不存在");
        }
        return entity;
    }

    @Override
    public IPage<PlayClerkUserReviewReturnVo> selectByPage(PlayClerkUserReviewQueryVo vo) {
        MPJLambdaWrapper<PlayClerkUserReviewInfoEntity> lambdaQueryWrapper = new MPJLambdaWrapper<>();
        lambdaQueryWrapper.selectAll(PlayClerkUserReviewInfoEntity.class);
        // 店员表
        lambdaQueryWrapper.selectAs(PlayClerkUserInfoEntity::getAvatar, "clerkAvatar")
                .selectAs(PlayClerkUserInfoEntity::getSignature, "signature");
        lambdaQueryWrapper.leftJoin(PlayClerkUserInfoEntity.class, PlayClerkUserInfoEntity::getId,
                PlayClerkUserReviewInfoEntity::getClerkId);
        if (StrUtil.isNotBlank(vo.getClerkId())) {
            lambdaQueryWrapper.eq(PlayClerkUserReviewInfoEntity::getClerkId, vo.getClerkId());
        }
        if (StrUtil.isNotBlank(vo.getSex())) {
            lambdaQueryWrapper.eq(PlayClerkUserReviewInfoEntity::getSex, vo.getSex());
        }
        if (StrUtil.isNotBlank(vo.getNickname())) {
            lambdaQueryWrapper.like(PlayClerkUserReviewInfoEntity::getNickname, vo.getNickname());
        }
        if (StrUtil.isNotBlank(vo.getPhone())) {
            lambdaQueryWrapper.like(PlayClerkUserReviewInfoEntity::getPhone, vo.getPhone());
        }
        if (StrUtil.isNotBlank(vo.getReviewState())) {
            lambdaQueryWrapper.like(PlayClerkUserReviewInfoEntity::getReviewState, vo.getReviewState());
        }
        if (StrUtil.isNotBlank(vo.getWeiChatCode())) {
            lambdaQueryWrapper.like(PlayClerkUserReviewInfoEntity::getWeiChatCode, vo.getWeiChatCode());
        }
        // 加入组员的筛选
        // List<String> clerkIdList =
        // playClerkGroupInfoService.getValidClerkIdList(SecurityUtils.getLoginUser(),
        // null);
        // lambdaQueryWrapper.in(PlayClerkUserInfoEntity::getId, clerkIdList);
        lambdaQueryWrapper.orderByDesc(PlayClerkUserReviewInfoEntity::getAddTime);
        lambdaQueryWrapper.orderByDesc(PlayClerkUserReviewInfoEntity::getClerkId);
        return this.baseMapper.selectJoinPage(new Page<>(vo.getPageNum(), vo.getPageSize()),
                PlayClerkUserReviewReturnVo.class, lambdaQueryWrapper);
    }

    /**
     * 查询店员资料审核列表
     *
     * @param playClerkUserReviewInfo
     *            店员资料审核
     * @return 店员资料审核
     */
    @Override
    public IPage<PlayClerkUserReviewInfoEntity> selectPlayClerkUserReviewInfoByPage(
            PlayClerkUserReviewInfoEntity playClerkUserReviewInfo) {
        Page<PlayClerkUserReviewInfoEntity> page = new Page<>(1, 10);
        return this.baseMapper.selectPage(page, new LambdaQueryWrapper<>());
    }

    /**
     * 新增店员资料审核
     *
     * @param playClerkUserReviewInfo
     *            店员资料审核
     * @return 结果
     */
    @Override
    public boolean create(PlayClerkUserReviewInfoEntity playClerkUserReviewInfo) {
        if (StrUtil.isBlankIfStr(playClerkUserReviewInfo.getId())) {
            playClerkUserReviewInfo.setId(IdUtils.getUuid());
        }
        return save(playClerkUserReviewInfo);
    }

    /**
     * 修改店员资料审核
     *
     * @param entity
     *            店员资料审核
     * @return 结果
     */
    @Override
    public boolean update(PlayClerkUserReviewInfoEntity entity) {
        return updateById(entity);
    }

    /**
     * 修改店员资料审核
     *
     * @param vo
     *            店员资料审核对象
     */
    @Override
    public void updateDataReviewState(PlayClerkUserReviewStateEditVo vo) {
        PlayClerkUserReviewInfoEntity entity = this.selectPlayClerkUserReviewInfoById(vo.getId());
        PlayClerkUserInfoEntity userInfo = playClerkUserInfoService.selectById(entity.getClerkId());
        if ("1".equals(vo.getReviewState())) {
            BeanUtils.copyProperties(entity, userInfo);
            userInfo.setClerkState("1");
            userInfo.setId(entity.getClerkId());
            userInfo.setAlbum(entity.getAlbum());
            playClerkUserInfoService.update(userInfo);
            clerkCommodityService.initClerkCommodity(userInfo.getId());
        }
        BeanUtils.copyProperties(vo, entity);
        this.update(entity);

        // 发送消息
        wxCustomMpService.sendCheckMessage(entity, userInfo, vo.getReviewState());
    }

    /**
     * 批量删除店员资料审核
     *
     * @param ids
     *            需要删除的店员资料审核主键
     * @return 结果
     */
    @Override
    public int deletePlayClerkUserReviewInfoByIds(String[] ids) {
        return playClerkUserReviewInfoMapper.deleteBatchIds(Arrays.asList(ids));
    }

    /**
     * 删除店员资料审核信息
     *
     * @param id
     *            店员资料审核主键
     * @return 结果
     */
    @Override
    public int deletePlayClerkUserReviewInfoById(String id) {
        return playClerkUserReviewInfoMapper.deleteById(id);
    }
}


--- File: play-admin/src/main/java/com/starry/admin/modules/clerk/service/impl/PlayClerkWagesDetailsInfoServiceImpl.java ---
package com.starry.admin.modules.clerk.service.impl;

import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.starry.admin.modules.clerk.mapper.PlayClerkWagesDetailsInfoMapper;
import com.starry.admin.modules.clerk.module.entity.PlayClerkWagesDetailsInfoEntity;
import com.starry.admin.modules.clerk.service.IPlayClerkWagesDetailsInfoService;
import com.starry.common.utils.IdUtils;
import java.util.Arrays;
import java.util.List;
import javax.annotation.Resource;
import org.springframework.stereotype.Service;

/**
 * 店员工资明细信息Service业务层处理
 *
 * @author admin
 * @since 2024-05-31
 */
@Service
public class PlayClerkWagesDetailsInfoServiceImpl
        extends
            ServiceImpl<PlayClerkWagesDetailsInfoMapper, PlayClerkWagesDetailsInfoEntity>
        implements
            IPlayClerkWagesDetailsInfoService {
    @Resource
    private PlayClerkWagesDetailsInfoMapper playClerkWagesDetailsInfoMapper;

    @Override
    public PlayClerkWagesDetailsInfoEntity selectByClerkIdAndWagesId(String wagesId, String clerkId) {
        LambdaQueryWrapper<PlayClerkWagesDetailsInfoEntity> lambdaQueryWrapper = new LambdaQueryWrapper<>();
        lambdaQueryWrapper.eq(PlayClerkWagesDetailsInfoEntity::getWagesId, wagesId);
        lambdaQueryWrapper.eq(PlayClerkWagesDetailsInfoEntity::getClerkId, clerkId);
        return this.baseMapper.selectOne(lambdaQueryWrapper);
    }

    @Override
    public PlayClerkWagesDetailsInfoEntity selectByOrderId(String orderId) {
        LambdaQueryWrapper<PlayClerkWagesDetailsInfoEntity> lambdaQueryWrapper = new LambdaQueryWrapper<>();
        lambdaQueryWrapper.eq(PlayClerkWagesDetailsInfoEntity::getOrderId, orderId);
        return this.baseMapper.selectOne(lambdaQueryWrapper);
    }

    /**
     * 查询店员工资明细信息
     *
     * @param id
     *            店员工资明细信息主键
     * @return 店员工资明细信息
     */
    @Override
    public PlayClerkWagesDetailsInfoEntity selectPlayClerkWagesDetailsInfoById(String id) {
        return this.baseMapper.selectById(id);
    }

    @Override
    public List<PlayClerkWagesDetailsInfoEntity> selectByWagesId(String wagesId) {
        LambdaQueryWrapper<PlayClerkWagesDetailsInfoEntity> lambdaQueryWrapper = new LambdaQueryWrapper<>();
        lambdaQueryWrapper.eq(PlayClerkWagesDetailsInfoEntity::getWagesId, wagesId);
        return this.baseMapper.selectList(lambdaQueryWrapper);
    }

    /**
     * 查询店员工资明细信息列表
     *
     * @param playClerkWagesDetailsInfo
     *            店员工资明细信息
     * @return 店员工资明细信息
     */
    @Override
    public IPage<PlayClerkWagesDetailsInfoEntity> selectPlayClerkWagesDetailsInfoByPage(
            PlayClerkWagesDetailsInfoEntity playClerkWagesDetailsInfo) {
        Page<PlayClerkWagesDetailsInfoEntity> page = new Page<>(1, 10);
        return this.baseMapper.selectPage(page, new LambdaQueryWrapper<>());
    }

    /**
     * 新增店员工资明细信息
     *
     * @param playClerkWagesDetailsInfo
     *            店员工资明细信息
     * @return 结果
     */
    @Override
    public boolean create(PlayClerkWagesDetailsInfoEntity playClerkWagesDetailsInfo) {
        if (StrUtil.isBlankIfStr(playClerkWagesDetailsInfo.getId())) {
            playClerkWagesDetailsInfo.setId(IdUtils.getUuid());
        }
        return save(playClerkWagesDetailsInfo);
    }

    /**
     * 修改店员工资明细信息
     *
     * @param playClerkWagesDetailsInfo
     *            店员工资明细信息
     * @return 结果
     */
    @Override
    public boolean update(PlayClerkWagesDetailsInfoEntity playClerkWagesDetailsInfo) {
        return updateById(playClerkWagesDetailsInfo);
    }

    /**
     * 批量删除店员工资明细信息
     *
     * @param ids
     *            需要删除的店员工资明细信息主键
     * @return 结果
     */
    @Override
    public int deletePlayClerkWagesDetailsInfoByIds(String[] ids) {
        return playClerkWagesDetailsInfoMapper.deleteBatchIds(Arrays.asList(ids));
    }

    /**
     * 删除店员工资明细信息信息
     *
     * @param id
     *            店员工资明细信息主键
     * @return 结果
     */
    @Override
    public int deletePlayClerkWagesDetailsInfoById(String id) {
        return playClerkWagesDetailsInfoMapper.deleteById(id);
    }
}


--- File: play-admin/src/main/java/com/starry/admin/modules/clerk/service/impl/PlayClerkWagesInfoServiceImpl.java ---
package com.starry.admin.modules.clerk.service.impl;

import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.github.yulichang.wrapper.MPJLambdaWrapper;
import com.starry.admin.modules.clerk.mapper.PlayClerkWagesInfoMapper;
import com.starry.admin.modules.clerk.module.entity.PlayClerkUserInfoEntity;
import com.starry.admin.modules.clerk.module.entity.PlayClerkWagesInfoEntity;
import com.starry.admin.modules.clerk.module.vo.PlayClerkWagesInfoQueryVo;
import com.starry.admin.modules.clerk.module.vo.PlayClerkWagesInfoReturnVo;
import com.starry.admin.modules.clerk.service.IPlayClerkWagesInfoService;
import com.starry.admin.modules.personnel.service.IPlayPersonnelGroupInfoService;
import com.starry.admin.utils.SecurityUtils;
import com.starry.common.utils.IdUtils;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.util.Arrays;
import java.util.List;
import javax.annotation.Resource;
import org.springframework.stereotype.Service;

/**
 * 店员工资结算信息Service业务层处理
 *
 * @author admin
 * @since 2024-05-31
 */
@Service
public class PlayClerkWagesInfoServiceImpl extends ServiceImpl<PlayClerkWagesInfoMapper, PlayClerkWagesInfoEntity>
        implements
            IPlayClerkWagesInfoService {
    @Resource
    private PlayClerkWagesInfoMapper playClerkWagesInfoMapper;

    @Resource
    private IPlayPersonnelGroupInfoService playClerkGroupInfoService;

    @Override
    public PlayClerkWagesInfoEntity selectCurrentPeriodWagesByClerkId(String clerkId) {
        LambdaQueryWrapper<PlayClerkWagesInfoEntity> lambdaQueryWrapper = new LambdaQueryWrapper<>();
        lambdaQueryWrapper.eq(PlayClerkWagesInfoEntity::getClerkId, clerkId);
        lambdaQueryWrapper.eq(PlayClerkWagesInfoEntity::getHistoricalStatistics, "0");
        return this.baseMapper.selectOne(lambdaQueryWrapper);
    }

    @Override
    public List<PlayClerkWagesInfoEntity> selectHistoricalWagesByClerkId(String clerkId) {
        LambdaQueryWrapper<PlayClerkWagesInfoEntity> lambdaQueryWrapper = new LambdaQueryWrapper<>();
        lambdaQueryWrapper.eq(PlayClerkWagesInfoEntity::getClerkId, clerkId);
        lambdaQueryWrapper.eq(PlayClerkWagesInfoEntity::getHistoricalStatistics, "1");
        return this.baseMapper.selectList(lambdaQueryWrapper);
    }

    @Override
    public PlayClerkWagesInfoEntity getLastSettlement(String clerkId) {
        LambdaQueryWrapper<PlayClerkWagesInfoEntity> lambdaQueryWrapper = new LambdaQueryWrapper<>();
        lambdaQueryWrapper.eq(PlayClerkWagesInfoEntity::getClerkId, clerkId);
        lambdaQueryWrapper.eq(PlayClerkWagesInfoEntity::getHistoricalStatistics, "0");
        lambdaQueryWrapper.orderByDesc(PlayClerkWagesInfoEntity::getEndCountDate);
        return this.baseMapper.selectOne(lambdaQueryWrapper);
    }

    @Override
    public LocalDate getLastSettlementTime(String clerkId) {
        LambdaQueryWrapper<PlayClerkWagesInfoEntity> lambdaQueryWrapper = new LambdaQueryWrapper<>();
        lambdaQueryWrapper.eq(PlayClerkWagesInfoEntity::getClerkId, clerkId);
        lambdaQueryWrapper.eq(PlayClerkWagesInfoEntity::getHistoricalStatistics, "1");
        lambdaQueryWrapper.orderByDesc(PlayClerkWagesInfoEntity::getEndCountDate);
        PlayClerkWagesInfoEntity entity = this.baseMapper.selectOne(lambdaQueryWrapper);
        if (entity == null) {
            return LocalDate.of(2000, 1, 1);
        }
        return entity.getEndCountDate();
    }

    @Override
    public IPage<PlayClerkWagesInfoReturnVo> selectHistoricalByPage(PlayClerkWagesInfoQueryVo vo) {
        MPJLambdaWrapper<PlayClerkWagesInfoEntity> lambdaWrapper = new MPJLambdaWrapper<>();
        lambdaWrapper.selectAll(PlayClerkWagesInfoEntity.class);
        // 拼接店员表
        lambdaWrapper.selectAs(PlayClerkUserInfoEntity::getNickname, "clerkNickname");
        lambdaWrapper.leftJoin(PlayClerkUserInfoEntity.class, PlayClerkUserInfoEntity::getId,
                PlayClerkWagesInfoEntity::getClerkId);
        if (StrUtil.isNotBlank(vo.getGroupId())) {
            lambdaWrapper.eq(PlayClerkUserInfoEntity::getGroupId, vo.getGroupId());
        }
        if (StrUtil.isNotBlank(vo.getListingState())) {
            lambdaWrapper.eq(PlayClerkUserInfoEntity::getListingState, vo.getListingState());
        }
        if (StrUtil.isNotBlank(vo.getClerkId())) {
            lambdaWrapper.eq(PlayClerkUserInfoEntity::getId, vo.getClerkId());
        }
        if (StrUtil.isNotEmpty(vo.getSettlementDate())) {
            if (vo.getSettlementDate().contains("T")) {
                vo.setSettlementDate(vo.getSettlementDate().substring(0, vo.getSettlementDate().indexOf("T")));
            }
            lambdaWrapper.eq(PlayClerkWagesInfoEntity::getSettlementDate,
                    LocalDate.parse(vo.getSettlementDate(), DateTimeFormatter.ofPattern("yyyy-MM-dd")));
        }
        lambdaWrapper.eq(PlayClerkWagesInfoEntity::getHistoricalStatistics, "1");
        // 加入组员的筛选
        List<String> clerkIdList = playClerkGroupInfoService.getValidClerkIdList(SecurityUtils.getLoginUser(), null);
        lambdaWrapper.in(PlayClerkUserInfoEntity::getId, clerkIdList);
        return this.baseMapper.selectJoinPage(new Page<>(vo.getPageNum(), vo.getPageSize()),
                PlayClerkWagesInfoReturnVo.class, lambdaWrapper);
    }

    /**
     * 查询店员工资结算信息
     *
     * @param id
     *            店员工资结算信息主键
     * @return 店员工资结算信息
     */
    @Override
    public PlayClerkWagesInfoEntity selectPlayClerkWagesInfoById(String id) {
        return this.baseMapper.selectById(id);
    }

    /**
     * 查询店员工资结算信息列表
     *
     * @param playClerkWagesInfo
     *            店员工资结算信息
     * @return 店员工资结算信息
     */
    @Override
    public IPage<PlayClerkWagesInfoEntity> selectPlayClerkWagesInfoByPage(PlayClerkWagesInfoEntity playClerkWagesInfo) {
        Page<PlayClerkWagesInfoEntity> page = new Page<>(1, 10);
        return this.baseMapper.selectPage(page, new LambdaQueryWrapper<>());
    }

    /**
     * 新增店员工资结算信息
     *
     * @param playClerkWagesInfo
     *            店员工资结算信息
     * @return 结果
     */
    @Override
    public boolean create(PlayClerkWagesInfoEntity playClerkWagesInfo) {
        if (StrUtil.isBlankIfStr(playClerkWagesInfo.getId())) {
            playClerkWagesInfo.setId(IdUtils.getUuid());
        }
        return save(playClerkWagesInfo);
    }

    /**
     * 修改店员工资结算信息
     *
     * @param playClerkWagesInfo
     *            店员工资结算信息
     * @return 结果
     */
    @Override
    public boolean update(PlayClerkWagesInfoEntity playClerkWagesInfo) {
        return updateById(playClerkWagesInfo);
    }

    /**
     * 批量删除店员工资结算信息
     *
     * @param ids
     *            需要删除的店员工资结算信息主键
     * @return 结果
     */
    @Override
    public int deletePlayClerkWagesInfoByIds(String[] ids) {
        return playClerkWagesInfoMapper.deleteBatchIds(Arrays.asList(ids));
    }

    /**
     * 删除店员工资结算信息信息
     *
     * @param id
     *            店员工资结算信息主键
     * @return 结果
     */
    @Override
    public int deletePlayClerkWagesInfoById(String id) {
        return playClerkWagesInfoMapper.deleteById(id);
    }
}


--- File: play-admin/src/main/java/com/starry/admin/modules/clerk/service/impl/PlayCustomArticleInfoServiceImpl.java ---
package com.starry.admin.modules.clerk.service.impl;

import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.starry.admin.modules.clerk.mapper.PlayCustomArticleInfoMapper;
import com.starry.admin.modules.clerk.module.entity.PlayCustomArticleInfoEntity;
import com.starry.admin.modules.clerk.service.IPlayCustomArticleInfoService;
import com.starry.common.utils.IdUtils;
import java.util.Arrays;
import javax.annotation.Resource;
import org.springframework.stereotype.Service;

/**
 * 陪聊点赞动态信息Service业务层处理
 *
 * @author admin
 * @since 2024-05-04
 */
@Service
public class PlayCustomArticleInfoServiceImpl
        extends
            ServiceImpl<PlayCustomArticleInfoMapper, PlayCustomArticleInfoEntity>
        implements
            IPlayCustomArticleInfoService {
    @Resource
    private PlayCustomArticleInfoMapper playCustomArticleInfoMapper;

    @Override
    public PlayCustomArticleInfoEntity selectByArticleId(String articleId, String customId, String endorseType) {
        LambdaQueryWrapper<PlayCustomArticleInfoEntity> lambdaQueryWrapper = new LambdaQueryWrapper<>();
        lambdaQueryWrapper.eq(PlayCustomArticleInfoEntity::getArticleId, articleId);
        lambdaQueryWrapper.eq(PlayCustomArticleInfoEntity::getCustomId, customId);
        lambdaQueryWrapper.eq(PlayCustomArticleInfoEntity::getEndorseType, endorseType);
        return this.baseMapper.selectOne(lambdaQueryWrapper);
    }

    /**
     * 查询陪聊点赞动态信息
     *
     * @param id
     *            陪聊点赞动态信息主键
     * @return 陪聊点赞动态信息
     */
    @Override
    public PlayCustomArticleInfoEntity selectPlayCustomArticleInfoById(String id) {
        return this.baseMapper.selectById(id);
    }

    /**
     * 查询陪聊点赞动态信息列表
     *
     * @param playCustomArticleInfo
     *            陪聊点赞动态信息
     * @return 陪聊点赞动态信息
     */
    @Override
    public IPage<PlayCustomArticleInfoEntity> selectPlayCustomArticleInfoByPage(
            PlayCustomArticleInfoEntity playCustomArticleInfo) {
        Page<PlayCustomArticleInfoEntity> page = new Page<>(1, 10);
        return this.baseMapper.selectPage(page, new LambdaQueryWrapper<>());
    }

    /**
     * 新增陪聊点赞动态信息
     *
     * @param playCustomArticleInfo
     *            陪聊点赞动态信息
     * @return 结果
     */
    @Override
    public boolean create(PlayCustomArticleInfoEntity playCustomArticleInfo) {
        if (StrUtil.isBlankIfStr(playCustomArticleInfo.getId())) {
            playCustomArticleInfo.setId(IdUtils.getUuid());
        }
        return save(playCustomArticleInfo);
    }

    /**
     * 修改陪聊点赞动态信息
     *
     * @param playCustomArticleInfo
     *            陪聊点赞动态信息
     * @return 结果
     */
    @Override
    public boolean update(PlayCustomArticleInfoEntity playCustomArticleInfo) {
        return updateById(playCustomArticleInfo);
    }

    /**
     * 批量删除陪聊点赞动态信息
     *
     * @param ids
     *            需要删除的陪聊点赞动态信息主键
     * @return 结果
     */
    @Override
    public int deletePlayCustomArticleInfoByIds(String[] ids) {
        return playCustomArticleInfoMapper.deleteBatchIds(Arrays.asList(ids));
    }

    /**
     * 删除陪聊点赞动态信息信息
     *
     * @param id
     *            陪聊点赞动态信息主键
     * @return 结果
     */
    @Override
    public int deletePlayCustomArticleInfoById(String id) {
        return playCustomArticleInfoMapper.deleteById(id);
    }

    @Override
    public void deleteByArticleId(String articleId) {
        LambdaQueryWrapper<PlayCustomArticleInfoEntity> lambdaQueryWrapper = new LambdaQueryWrapper<>();
        lambdaQueryWrapper.eq(PlayCustomArticleInfoEntity::getArticleId, articleId);
        this.baseMapper.delete(lambdaQueryWrapper);
    }
}


--- File: play-admin/src/main/java/com/starry/admin/modules/custom/controller/PlayCustomFollowInfoController.java ---
package com.starry.admin.modules.custom.controller;

import com.baomidou.mybatisplus.core.metadata.IPage;
import com.starry.admin.modules.custom.entity.PlayCustomFollowInfoEntity;
import com.starry.admin.modules.custom.service.IPlayCustomFollowInfoService;
import com.starry.common.annotation.Log;
import com.starry.common.enums.BusinessType;
import com.starry.common.result.R;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiParam;
import io.swagger.annotations.ApiResponse;
import io.swagger.annotations.ApiResponses;
import javax.annotation.Resource;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.*;

/**
 * 顾客关注陪聊信息Controller
 *
 * @author admin
 * @since 2024-04-30
 */
@Api(tags = "顾客关注管理", description = "顾客关注店员信息管理相关接口，包括查询、新增、修改和删除等操作")
@RestController
@RequestMapping("/play/follow")
public class PlayCustomFollowInfoController {
    @Resource
    private IPlayCustomFollowInfoService playCustomFollowInfoService;

    /**
     * 查询顾客关注陪聊信息列表
     */
    @ApiOperation(value = "分页查询关注列表", notes = "分页查询顾客关注店员信息列表")
    @ApiResponses({
            @ApiResponse(code = 200, message = "操作成功", response = PlayCustomFollowInfoEntity.class, responseContainer = "Page")})
    @PreAuthorize("@customSs.hasPermission('play:info:list')")
    @GetMapping("/list")
    public R list(@ApiParam(value = "查询条件") PlayCustomFollowInfoEntity playCustomFollowInfo) {
        IPage<PlayCustomFollowInfoEntity> list = playCustomFollowInfoService
                .selectPlayCustomFollowInfoByPage(playCustomFollowInfo);
        return R.ok(list);
    }

    /**
     * 获取顾客关注陪聊信息详细信息
     */
    @ApiOperation(value = "获取关注详情", notes = "根据ID获取顾客关注店员详细信息")
    @ApiImplicitParam(name = "id", value = "关注记录ID", required = true, paramType = "path", dataType = "String", example = "1")
    @ApiResponses({@ApiResponse(code = 200, message = "操作成功", response = PlayCustomFollowInfoEntity.class)})
    @PreAuthorize("@customSs.hasPermission('play:info:query')")
    @GetMapping(value = "/{id}")
    public R getInfo(@PathVariable("id") String id) {
        return R.ok(playCustomFollowInfoService.selectPlayCustomFollowInfoById(id));
    }

    /**
     * 新增顾客关注陪聊信息
     */
    @ApiOperation(value = "新增关注记录", notes = "创建新的顾客关注店员信息")
    @ApiResponses({@ApiResponse(code = 200, message = "操作成功"), @ApiResponse(code = 500, message = "添加失败")})
    @PreAuthorize("@customSs.hasPermission('play:info:create')")
    @Log(title = "顾客关注陪聊信息", businessType = BusinessType.INSERT)
    @PostMapping("/create")
    public R create(
            @ApiParam(value = "关注信息", required = true) @RequestBody PlayCustomFollowInfoEntity playCustomFollowInfo) {
        boolean success = playCustomFollowInfoService.create(playCustomFollowInfo);
        if (success) {
            return R.ok();
        }
        return R.error("添加失败");
    }

    /**
     * 修改顾客关注陪聊信息
     */
    @ApiOperation(value = "修改关注记录", notes = "根据ID修改顾客关注店员信息")
    @ApiImplicitParam(name = "id", value = "关注记录ID", required = true, paramType = "path", dataType = "String", example = "1")
    @ApiResponses({@ApiResponse(code = 200, message = "操作成功"), @ApiResponse(code = 500, message = "修改失败")})
    @PreAuthorize("@customSs.hasPermission('play:info:edit')")
    @Log(title = "顾客关注陪聊信息", businessType = BusinessType.UPDATE)
    @PostMapping(value = "/update/{id}")
    public R update(@PathVariable String id,
            @ApiParam(value = "关注信息", required = true) @RequestBody PlayCustomFollowInfoEntity playCustomFollowInfo) {
        playCustomFollowInfo.setId(id);
        boolean success = playCustomFollowInfoService.update(playCustomFollowInfo);
        if (success) {
            return R.ok();
        }
        return R.error("修改失败");
    }

    /**
     * 删除顾客关注陪聊信息
     */
    @ApiOperation(value = "删除关注记录", notes = "根据ID批量删除顾客关注店员信息")
    @ApiImplicitParam(name = "ids", value = "关注记录ID数组", required = true, paramType = "path", dataType = "String[]", example = "1,2,3")
    @ApiResponses({@ApiResponse(code = 200, message = "操作成功", response = Integer.class)})
    @PreAuthorize("@customSs.hasPermission('play:info:remove')")
    @Log(title = "顾客关注陪聊信息", businessType = BusinessType.DELETE)
    @DeleteMapping("/{ids}")
    public R remove(@PathVariable String[] ids) {
        return R.ok(playCustomFollowInfoService.deletePlayCustomFollowInfoByIds(ids));
    }
}


--- File: play-admin/src/main/java/com/starry/admin/modules/custom/controller/PlayCustomGiftInfoController.java ---
package com.starry.admin.modules.custom.controller;

import com.baomidou.mybatisplus.core.metadata.IPage;
import com.starry.admin.modules.custom.module.entity.PlayCustomGiftInfoEntity;
import com.starry.admin.modules.custom.service.IPlayCustomGiftInfoService;
import com.starry.common.annotation.Log;
import com.starry.common.enums.BusinessType;
import com.starry.common.result.R;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiParam;
import io.swagger.annotations.ApiResponse;
import io.swagger.annotations.ApiResponses;
import javax.annotation.Resource;
import org.springframework.web.bind.annotation.*;

/**
 * 顾客和礼物关系Controller
 *
 * @author admin
 * @since 2024-06-05
 */
@Api(tags = "顾客礼物管理", description = "顾客与礼物关系管理相关接口，包括查询、新增、修改和删除等操作")
@RestController
@RequestMapping("/custom/giff")
public class PlayCustomGiftInfoController {
    @Resource
    private IPlayCustomGiftInfoService playCustomGiftInfoService;

    /**
     * 查询顾客和礼物关系列表
     */
    @ApiOperation(value = "分页查询礼物列表", notes = "分页查询顾客与礼物关系列表")
    @ApiResponses({
            @ApiResponse(code = 200, message = "操作成功", response = PlayCustomGiftInfoEntity.class, responseContainer = "Page")})
    @GetMapping("/list")
    public R list(@ApiParam(value = "查询条件") PlayCustomGiftInfoEntity playCustomGiftInfo) {
        IPage<PlayCustomGiftInfoEntity> list = playCustomGiftInfoService
                .selectPlayCustomGiftInfoByPage(playCustomGiftInfo);
        return R.ok(list);
    }

    /**
     * 获取顾客和礼物关系详细信息
     */
    @ApiOperation(value = "获取礼物详情", notes = "根据ID获取顾客与礼物关系详细信息")
    @ApiImplicitParam(name = "id", value = "礼物关系ID", required = true, paramType = "path", dataType = "String", example = "1")
    @ApiResponses({@ApiResponse(code = 200, message = "操作成功", response = PlayCustomGiftInfoEntity.class)})
    @GetMapping(value = "/{id}")
    public R getInfo(@PathVariable("id") String id) {
        return R.ok(playCustomGiftInfoService.selectPlayCustomGiftInfoById(id));
    }

    /**
     * 新增顾客和礼物关系
     */
    @ApiOperation(value = "新增礼物关系", notes = "创建新的顾客与礼物关系信息")
    @ApiResponses({@ApiResponse(code = 200, message = "操作成功"), @ApiResponse(code = 500, message = "添加失败")})
    @Log(title = "顾客和礼物关系", businessType = BusinessType.INSERT)
    @PostMapping("/create")
    public R create(
            @ApiParam(value = "礼物关系信息", required = true) @RequestBody PlayCustomGiftInfoEntity playCustomGiftInfo) {
        boolean success = playCustomGiftInfoService.create(playCustomGiftInfo);
        if (success) {
            return R.ok();
        }
        return R.error("添加失败");
    }

    /**
     * 修改顾客和礼物关系
     */
    @ApiOperation(value = "修改礼物关系", notes = "根据ID修改顾客与礼物关系信息")
    @ApiImplicitParam(name = "id", value = "礼物关系ID", required = true, paramType = "path", dataType = "String", example = "1")
    @ApiResponses({@ApiResponse(code = 200, message = "操作成功"), @ApiResponse(code = 500, message = "修改失败")})
    @Log(title = "顾客和礼物关系", businessType = BusinessType.UPDATE)
    @PostMapping(value = "/update/{id}")
    public R update(@PathVariable String id,
            @ApiParam(value = "礼物关系信息", required = true) @RequestBody PlayCustomGiftInfoEntity playCustomGiftInfo) {
        playCustomGiftInfo.setId(id);
        boolean success = playCustomGiftInfoService.update(playCustomGiftInfo);
        if (success) {
            return R.ok();
        }
        return R.error("修改失败");
    }

    /**
     * 删除顾客和礼物关系
     */
    @ApiOperation(value = "删除礼物关系", notes = "根据ID批量删除顾客与礼物关系信息")
    @ApiImplicitParam(name = "ids", value = "礼物关系ID数组", required = true, paramType = "path", dataType = "String[]", example = "1,2,3")
    @ApiResponses({@ApiResponse(code = 200, message = "操作成功", response = Integer.class)})
    @Log(title = "顾客和礼物关系", businessType = BusinessType.DELETE)
    @DeleteMapping("/{ids}")
    public R remove(@PathVariable String[] ids) {
        return R.ok(playCustomGiftInfoService.deletePlayCustomGiftInfoByIds(ids));
    }
}


--- File: play-admin/src/main/java/com/starry/admin/modules/custom/controller/PlayCustomLeaveMsgController.java ---
package com.starry.admin.modules.custom.controller;

import com.baomidou.mybatisplus.core.metadata.IPage;
import com.starry.admin.modules.custom.module.entity.PlayCustomLeaveMsgEntity;
import com.starry.admin.modules.custom.module.vo.PlayCustomLeaveMsgQueryVo;
import com.starry.admin.modules.custom.module.vo.PlayCustomLeaveMsgReturnVo;
import com.starry.admin.modules.custom.service.IPlayCustomLeaveMsgService;
import com.starry.common.annotation.Log;
import com.starry.common.enums.BusinessType;
import com.starry.common.result.R;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiParam;
import io.swagger.annotations.ApiResponse;
import io.swagger.annotations.ApiResponses;
import javax.annotation.Resource;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;

/**
 * 顾客留言Controller
 *
 * @author admin
 * @since 2024-05-07
 */
@Api(tags = "顾客留言管理", description = "顾客留言信息管理相关接口，包括查询、新增、修改和删除等操作")
@RestController
@RequestMapping("/custom/leave")
public class PlayCustomLeaveMsgController {
    @Resource
    private IPlayCustomLeaveMsgService playCustomLeaveMsgService;

    /**
     * 分页查询顾客留言列表
     */
    @ApiOperation(value = "分页查询留言列表", notes = "分页查询顾客留言信息列表")
    @ApiResponses({
            @ApiResponse(code = 200, message = "操作成功", response = PlayCustomLeaveMsgReturnVo.class, responseContainer = "Page")})
    @PostMapping("/listByPage")
    public R list(@ApiParam(value = "查询条件", required = true) @Validated @RequestBody PlayCustomLeaveMsgQueryVo vo) {
        IPage<PlayCustomLeaveMsgReturnVo> list = playCustomLeaveMsgService.selectByPage(vo);
        return R.ok(list);
    }

    /**
     * 获取顾客留言详细信息
     */
    @ApiOperation(value = "获取留言详情", notes = "根据ID获取顾客留言详细信息")
    @ApiImplicitParam(name = "id", value = "留言ID", required = true, paramType = "path", dataType = "String", example = "1")
    @ApiResponses({@ApiResponse(code = 200, message = "操作成功", response = PlayCustomLeaveMsgEntity.class)})
    @GetMapping(value = "/{id}")
    public R getInfo(@PathVariable("id") String id) {
        return R.ok(playCustomLeaveMsgService.selectPlayCustomLeaveMsgById(id));
    }

    /**
     * 新增顾客留言
     */
    @ApiOperation(value = "新增留言", notes = "创建新的顾客留言信息")
    @ApiResponses({@ApiResponse(code = 200, message = "操作成功"), @ApiResponse(code = 500, message = "添加失败")})
    // @PreAuthorize("@customSs.hasPermission('custom:leave:add')")
    @Log(title = "顾客留言", businessType = BusinessType.INSERT)
    @PostMapping("/create")
    public R create(
            @ApiParam(value = "留言信息", required = true) @RequestBody PlayCustomLeaveMsgEntity playCustomLeaveMsg) {
        boolean success = playCustomLeaveMsgService.create(playCustomLeaveMsg);
        if (success) {
            return R.ok();
        }
        return R.error("添加失败");
    }

    /**
     * 修改顾客留言
     */
    @ApiOperation(value = "修改留言", notes = "根据ID修改顾客留言信息")
    @ApiImplicitParam(name = "id", value = "留言ID", required = true, paramType = "path", dataType = "String", example = "1")
    @ApiResponses({@ApiResponse(code = 200, message = "操作成功"), @ApiResponse(code = 500, message = "修改失败")})
    // @PreAuthorize("@customSs.hasPermission('custom:leave:update')")
    @Log(title = "顾客留言", businessType = BusinessType.UPDATE)
    @PostMapping(value = "/update/{id}")
    public R update(@PathVariable String id,
            @ApiParam(value = "留言信息", required = true) @RequestBody PlayCustomLeaveMsgEntity playCustomLeaveMsg) {
        playCustomLeaveMsg.setId(id);
        boolean success = playCustomLeaveMsgService.update(playCustomLeaveMsg);
        if (success) {
            return R.ok();
        }
        return R.error("修改失败");
    }

    /**
     * 删除顾客留言
     */
    @ApiOperation(value = "删除留言", notes = "根据ID批量删除顾客留言信息")
    @ApiImplicitParam(name = "ids", value = "留言ID数组", required = true, paramType = "path", dataType = "String[]", example = "1,2,3")
    @ApiResponses({@ApiResponse(code = 200, message = "操作成功", response = Integer.class)})
    // @PreAuthorize("@customSs.hasPermission('custom:leave:delete')")
    @Log(title = "顾客留言", businessType = BusinessType.DELETE)
    @DeleteMapping("/{ids}")
    public R remove(@PathVariable String[] ids) {
        return R.ok(playCustomLeaveMsgService.deletePlayCustomLeaveMsgByIds(ids));
    }
}


--- File: play-admin/src/main/java/com/starry/admin/modules/custom/controller/PlayCustomLevelInfoController.java ---
package com.starry.admin.modules.custom.controller;

import com.starry.admin.common.exception.CustomException;
import com.starry.admin.modules.custom.module.entity.PlayCustomLevelInfoEntity;
import com.starry.admin.modules.custom.module.vo.PlayCustomLevelAddVo;
import com.starry.admin.modules.custom.module.vo.PlayCustomLevelEditVo;
import com.starry.admin.modules.custom.module.vo.PlayCustomLevelReturnVo;
import com.starry.admin.modules.custom.service.IPlayCustomLevelInfoService;
import com.starry.common.annotation.Log;
import com.starry.common.enums.BusinessType;
import com.starry.common.result.R;
import com.starry.common.utils.ConvertUtil;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiParam;
import io.swagger.annotations.ApiResponse;
import io.swagger.annotations.ApiResponses;
import java.util.List;
import javax.annotation.Resource;
import org.springframework.web.bind.annotation.*;

/**
 * 顾客等级Controller
 *
 * @author admin
 * @since 2024-05-04
 */
@RestController
@RequestMapping("/custom/level")
@Api(tags = "顾客等级管理", description = "顾客等级信息管理相关接口，包括查询、新增、修改和删除等操作")
public class PlayCustomLevelInfoController {
    @Resource
    private IPlayCustomLevelInfoService playCustomLevelInfoService;

    /**
     * 查询顾客等级列表
     */
    @PostMapping("/listAll")
    @ApiOperation(value = "查询等级列表", notes = "获取所有顾客等级信息列表")
    @ApiResponses({
            @ApiResponse(code = 200, message = "操作成功", response = PlayCustomLevelReturnVo.class, responseContainer = "List")})
    public R listAll() {
        List<PlayCustomLevelInfoEntity> list = playCustomLevelInfoService.selectPlayCustomLevelInfoByPage();
        return R.ok(ConvertUtil.entityToVoList(list, PlayCustomLevelReturnVo.class));
    }

    /**
     * 获取顾客等级详细信息
     */
    @GetMapping(value = "/{id}")
    @ApiOperation(value = "获取等级详情", notes = "根据ID获取顾客等级详细信息")
    @ApiImplicitParam(name = "id", value = "等级ID", required = true, paramType = "path", dataType = "String", example = "1")
    @ApiResponses({@ApiResponse(code = 200, message = "操作成功", response = PlayCustomLevelInfoEntity.class)})
    public R getInfo(@PathVariable("id") String id) {
        return R.ok(playCustomLevelInfoService.selectPlayCustomLevelInfoById(id));
    }

    /**
     * 新增顾客等级
     */
    // @PreAuthorize("@customSs.hasPermission('custom:level:add')")
    @Log(title = "店员等级", businessType = BusinessType.INSERT)
    @PostMapping("/create")
    @ApiOperation(value = "新增等级", notes = "创建新的顾客等级信息")
    @ApiResponses({@ApiResponse(code = 200, message = "操作成功"), @ApiResponse(code = 500, message = "添加失败")})
    public R create(@ApiParam(value = "等级信息", required = true) @RequestBody PlayCustomLevelAddVo vo) {
        PlayCustomLevelInfoEntity entity = ConvertUtil.entityToVo(vo, PlayCustomLevelInfoEntity.class);
        int level = playCustomLevelInfoService.selectMaxLevel();
        entity.setLevel(level + 1);
        boolean success = playCustomLevelInfoService.create(entity);
        if (success) {
            return R.ok();
        }
        return R.error("添加失败");
    }

    /**
     * 修改顾客等级
     */
    // @PreAuthorize("@customSs.hasPermission('custom:level:update')")
    @Log(title = "顾客等级", businessType = BusinessType.UPDATE)
    @PostMapping(value = "/update")
    @ApiOperation(value = "修改等级", notes = "修改顾客等级信息")
    @ApiResponses({@ApiResponse(code = 200, message = "操作成功"), @ApiResponse(code = 500, message = "修改失败"),
            @ApiResponse(code = 400, message = "对象不存在")})
    public R update(@ApiParam(value = "等级信息", required = true) @RequestBody PlayCustomLevelEditVo vo) {
        if (playCustomLevelInfoService.selectPlayCustomLevelInfoById(vo.getId()) == null) {
            throw new CustomException("对象不存在");
        }
        PlayCustomLevelInfoEntity entity = ConvertUtil.entityToVo(vo, PlayCustomLevelInfoEntity.class);
        boolean success = playCustomLevelInfoService.update(entity);
        if (success) {
            return R.ok();
        }
        return R.error("修改失败");
    }

    /**
     * 删除店员等级
     */
    @Log(title = "店员等级", businessType = BusinessType.DELETE)
    @DeleteMapping("delMaxLevel")
    @ApiOperation(value = "删除最高等级", notes = "删除系统中的最高等级")
    @ApiResponses({@ApiResponse(code = 200, message = "操作成功"), @ApiResponse(code = 400, message = "最后一级,不允许删除")})
    public R remove() {
        int level = playCustomLevelInfoService.selectMaxLevel();
        if (level <= 1) {
            throw new CustomException("最后一级,不允许删除");
        }
        playCustomLevelInfoService.delMaxLevelByLevel(level);
        return R.ok();
    }
}


--- File: play-admin/src/main/java/com/starry/admin/modules/custom/controller/PlayCustomRankingController.java ---
package com.starry.admin.modules.custom.controller;

import com.baomidou.mybatisplus.core.metadata.IPage;
import com.starry.admin.modules.custom.module.vo.PlayCustomRankingQueryVo;
import com.starry.admin.modules.custom.module.vo.PlayCustomRankingReturnVo;
import com.starry.admin.modules.custom.service.IPlayCustomUserInfoService;
import com.starry.common.result.R;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiParam;
import io.swagger.annotations.ApiResponse;
import io.swagger.annotations.ApiResponses;
import javax.annotation.Resource;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 * 顾客Controller
 *
 * @author admin
 * @since 2024-04-08
 */
@Api(tags = "顾客排名管理", description = "顾客消费排名相关接口，包括查询排名列表等操作")
@RestController
@RequestMapping("/custom/ranking")
public class PlayCustomRankingController {
    @Resource
    private IPlayCustomUserInfoService playCustomUserInfoService;

    /**
     * 查询顾客排名列表
     */
    @ApiOperation(value = "分页查询排名列表", notes = "分页查询顾客消费排名信息列表")
    @ApiResponses({
            @ApiResponse(code = 200, message = "操作成功", response = PlayCustomRankingReturnVo.class, responseContainer = "Page")})
    @PostMapping("/listByPage")
    public R list(@ApiParam(value = "查询条件", required = true) @Validated @RequestBody PlayCustomRankingQueryVo vo) {
        IPage<PlayCustomRankingReturnVo> list = playCustomUserInfoService.selectRankingByPage(vo);
        return R.ok(list);
    }

}


--- File: play-admin/src/main/java/com/starry/admin/modules/custom/controller/PlayCustomUserInfoController.java ---
package com.starry.admin.modules.custom.controller;

import com.baomidou.mybatisplus.core.metadata.IPage;
import com.starry.admin.modules.custom.module.entity.PlayCustomUserInfoEntity;
import com.starry.admin.modules.custom.module.vo.PlayCustomUserQueryVo;
import com.starry.admin.modules.custom.module.vo.PlayCustomUserReturnVo;
import com.starry.admin.modules.custom.module.vo.PlayCustomUserStateEditVo;
import com.starry.admin.modules.custom.service.IPlayCustomUserInfoService;
import com.starry.common.annotation.Log;
import com.starry.common.enums.BusinessType;
import com.starry.common.result.R;
import com.starry.common.utils.ConvertUtil;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiParam;
import io.swagger.annotations.ApiResponse;
import io.swagger.annotations.ApiResponses;
import javax.annotation.Resource;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;

/**
 * 顾客Controller
 *
 * @author admin
 * @since 2024-04-08
 */
@Api(tags = "顾客管理", description = "顾客信息管理相关接口，包括查询、新增、修改和删除等操作")
@RestController
@RequestMapping("/custom/user")
public class PlayCustomUserInfoController {
    @Resource
    private IPlayCustomUserInfoService playCustomUserInfoService;

    /**
     * 查询顾客列表
     */
    @ApiOperation(value = "分页查询顾客列表", notes = "分页查询顾客信息列表")
    @ApiResponses({
            @ApiResponse(code = 200, message = "操作成功", response = PlayCustomUserReturnVo.class, responseContainer = "Page")})
    @PostMapping("/listByPage")
    public R list(@ApiParam(value = "查询条件", required = true) @Validated @RequestBody PlayCustomUserQueryVo vo) {
        IPage<PlayCustomUserReturnVo> list = playCustomUserInfoService.selectByPage(vo);
        return R.ok(list);
    }

    /**
     * 获取顾客详细信息
     */
    @ApiOperation(value = "获取顾客详情", notes = "根据ID获取顾客详细信息")
    @ApiImplicitParam(name = "id", value = "顾客ID", required = true, paramType = "path", dataType = "String", example = "1")
    @ApiResponses({@ApiResponse(code = 200, message = "操作成功", response = PlayCustomUserInfoEntity.class)})
    @GetMapping(value = "/{id}")
    public R getInfo(@PathVariable("id") String id) {
        return R.ok(playCustomUserInfoService.selectById(id));
    }

    /**
     * 新增顾客
     */
    @ApiOperation(value = "新增顾客", notes = "创建新的顾客信息")
    @ApiResponses({@ApiResponse(code = 200, message = "操作成功"), @ApiResponse(code = 500, message = "添加失败")})
    @Log(title = "顾客", businessType = BusinessType.INSERT)
    @PostMapping("/create")
    public R create(
            @ApiParam(value = "顾客信息", required = true) @RequestBody PlayCustomUserInfoEntity playCustomUserInfo) {
        boolean success = playCustomUserInfoService.create(playCustomUserInfo);
        if (success) {
            return R.ok();
        }
        return R.error("添加失败");
    }

    /**
     * 修改顾客状态
     */
    @ApiOperation(value = "修改顾客状态", notes = "修改顾客的状态信息，如黑名单状态、关注状态等")
    @ApiResponses({@ApiResponse(code = 200, message = "操作成功"), @ApiResponse(code = 500, message = "修改失败")})
    @Log(title = "顾客", businessType = BusinessType.UPDATE)
    @PostMapping(value = "/updateState")
    public R updateState(
            @ApiParam(value = "顾客状态信息", required = true) @Validated @RequestBody PlayCustomUserStateEditVo vo) {
        PlayCustomUserInfoEntity entity = ConvertUtil.entityToVo(vo, PlayCustomUserInfoEntity.class);
        boolean success = playCustomUserInfoService.update(entity);
        if (success) {
            return R.ok();
        }
        return R.error("修改失败");
    }

    /**
     * 修改顾客
     */
    @ApiOperation(value = "修改顾客信息", notes = "根据ID修改顾客基本信息")
    @ApiImplicitParam(name = "id", value = "顾客ID", required = true, paramType = "path", dataType = "String", example = "1")
    @ApiResponses({@ApiResponse(code = 200, message = "操作成功"), @ApiResponse(code = 500, message = "修改失败")})
    @Log(title = "顾客", businessType = BusinessType.UPDATE)
    @PostMapping(value = "/update/{id}")
    public R update(@PathVariable String id,
            @ApiParam(value = "顾客信息", required = true) @RequestBody PlayCustomUserInfoEntity playCustomUserInfo) {
        playCustomUserInfo.setId(id);
        boolean success = playCustomUserInfoService.update(playCustomUserInfo);
        if (success) {
            return R.ok();
        }
        return R.error("修改失败");
    }

    /**
     * 删除顾客
     */
    @ApiOperation(value = "删除顾客", notes = "根据ID批量删除顾客信息")
    @ApiImplicitParam(name = "ids", value = "顾客ID数组", required = true, paramType = "path", dataType = "String[]", example = "1,2,3")
    @ApiResponses({@ApiResponse(code = 200, message = "操作成功", response = Integer.class)})
    @Log(title = "顾客", businessType = BusinessType.DELETE)
    @DeleteMapping("/{ids}")
    public R remove(@PathVariable String[] ids) {
        return R.ok(playCustomUserInfoService.deletePlayCustomUserInfoByIds(ids));
    }
}


--- File: play-admin/src/main/java/com/starry/admin/modules/custom/entity/PlayCustomFollowInfoEntity.java ---
package com.starry.admin.modules.custom.entity;

import com.baomidou.mybatisplus.annotation.TableName;
import com.starry.common.domain.BaseEntity;
import java.util.Date;
import lombok.Data;
import lombok.EqualsAndHashCode;

/**
 * 顾客关注陪聊信息对象 play_custom_follow_info
 *
 * @author admin
 * @since 2024-04-30
 */
@Data
@EqualsAndHashCode(callSuper = false)
@TableName("play_custom_follow_info")
public class PlayCustomFollowInfoEntity extends BaseEntity<PlayCustomFollowInfoEntity> {

    /**
     * UUID
     */
    private String id;

    /**
     * 租户ID
     */
    private String tenantId;

    /**
     * 顾客ID
     */
    private String customId;

    /**
     * 陪聊ID
     */
    private String clerkId;

    /**
     * 关注时间
     */
    private Date followTime;

    /**
     * 取消关注时间
     */
    private Date unfollowTime;

    /**
     * 关注状态[0:未关注，1:已关注]
     */
    private String followState;

}


--- File: play-admin/src/main/java/com/starry/admin/modules/custom/mapper/PlayCustomFollowInfoMapper.java ---
package com.starry.admin.modules.custom.mapper;

import com.github.yulichang.base.MPJBaseMapper;
import com.starry.admin.modules.custom.entity.PlayCustomFollowInfoEntity;

/**
 * 顾客关注陪聊信息Mapper接口
 *
 * @author admin
 * @since 2024-04-30
 */
public interface PlayCustomFollowInfoMapper extends MPJBaseMapper<PlayCustomFollowInfoEntity> {

}


--- File: play-admin/src/main/java/com/starry/admin/modules/custom/mapper/PlayCustomGiftInfoMapper.java ---
package com.starry.admin.modules.custom.mapper;

import com.github.yulichang.base.MPJBaseMapper;
import com.starry.admin.modules.custom.module.entity.PlayCustomGiftInfoEntity;

/**
 * 顾客和礼物关系Mapper接口
 *
 * @author admin
 * @since 2024-06-05
 */
public interface PlayCustomGiftInfoMapper extends MPJBaseMapper<PlayCustomGiftInfoEntity> {

}


--- File: play-admin/src/main/java/com/starry/admin/modules/custom/mapper/PlayCustomLeaveMsgMapper.java ---
package com.starry.admin.modules.custom.mapper;

import com.github.yulichang.base.MPJBaseMapper;
import com.starry.admin.modules.custom.module.entity.PlayCustomLeaveMsgEntity;

/**
 * 顾客留言Mapper接口
 *
 * @author admin
 * @since 2024-05-07
 */
public interface PlayCustomLeaveMsgMapper extends MPJBaseMapper<PlayCustomLeaveMsgEntity> {

}


--- File: play-admin/src/main/java/com/starry/admin/modules/custom/mapper/PlayCustomLevelInfoMapper.java ---
package com.starry.admin.modules.custom.mapper;

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.starry.admin.modules.custom.module.entity.PlayCustomLevelInfoEntity;

/**
 * 顾客等级Mapper接口
 *
 * @author admin
 * @since 2024-05-04
 */
public interface PlayCustomLevelInfoMapper extends BaseMapper<PlayCustomLevelInfoEntity> {

}


--- File: play-admin/src/main/java/com/starry/admin/modules/custom/mapper/PlayCustomUserInfoMapper.java ---
package com.starry.admin.modules.custom.mapper;

import com.github.yulichang.base.MPJBaseMapper;
import com.starry.admin.modules.custom.module.entity.PlayCustomUserInfoEntity;

/**
 * 顾客Mapper接口
 *
 * @author admin
 * @since 2024-04-08
 */
public interface PlayCustomUserInfoMapper extends MPJBaseMapper<PlayCustomUserInfoEntity> {

}


--- File: play-admin/src/main/java/com/starry/admin/modules/custom/module/entity/PlayCustomGiftInfoEntity.java ---
package com.starry.admin.modules.custom.module.entity;

import com.baomidou.mybatisplus.annotation.TableName;
import com.starry.common.domain.BaseEntity;
import lombok.Data;
import lombok.EqualsAndHashCode;

/**
 * 顾客和礼物关系对象 play_custom_gift_info
 *
 * @author admin
 * @since 2024-06-05
 */
@Data
@EqualsAndHashCode(callSuper = false)
@TableName("play_custom_gift_info")
public class PlayCustomGiftInfoEntity extends BaseEntity<PlayCustomGiftInfoEntity> {

    /**
     * UUID
     */
    private String id;

    /**
     * 租户ID
     */
    private String tenantId;

    /**
     * 顾客ID
     */
    private String customId;

    /**
     * 礼物ID
     */
    private String giffId;

    /**
     * 礼物数量
     */
    private Long giffNumber;

}


--- File: play-admin/src/main/java/com/starry/admin/modules/custom/module/entity/PlayCustomLeaveMsgEntity.java ---
package com.starry.admin.modules.custom.module.entity;

import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableName;
import com.starry.admin.common.conf.StringTypeHandler;
import com.starry.common.domain.BaseEntity;
import java.util.Date;
import java.util.List;
import lombok.Data;
import lombok.EqualsAndHashCode;

/**
 * 顾客留言对象 play_custom_leave_msg
 *
 * @author admin
 * @since 2024-05-07
 */
@Data
@EqualsAndHashCode(callSuper = false)
@TableName(value = "play_custom_leave_msg", autoResultMap = true)
public class PlayCustomLeaveMsgEntity extends BaseEntity<PlayCustomLeaveMsgEntity> {

    /**
     * UUID
     */
    private String id;

    /**
     * 租户ID
     */
    private String tenantId;

    /**
     * 顾客ID
     */
    private String customId;

    /**
     * 留言内容
     */
    private String content;

    /**
     * 图片
     */
    @TableField(typeHandler = StringTypeHandler.class)
    private List<String> images;

    /**
     * 留言时间
     */
    private Date msgTime;

    /**
     * 备注
     */
    private String remark;
}


--- File: play-admin/src/main/java/com/starry/admin/modules/custom/module/entity/PlayCustomLevelInfoEntity.java ---
package com.starry.admin.modules.custom.module.entity;

import com.baomidou.mybatisplus.annotation.TableName;
import com.starry.common.domain.BaseEntity;
import lombok.Data;
import lombok.EqualsAndHashCode;

/**
 * 顾客等级对象 play_custom_level_info
 *
 * @author admin
 * @since 2024-05-04
 */
@Data
@EqualsAndHashCode(callSuper = false)
@TableName("play_custom_level_info")
public class PlayCustomLevelInfoEntity extends BaseEntity<PlayCustomLevelInfoEntity> {

    /**
     * UUID
     */
    private String id;

    /**
     * 租户ID
     */
    private String tenantId;

    /**
     * 等级名称
     */
    private String name;

    /**
     * 等级数字（排序字段）
     */
    private Integer level;

    /**
     * 上一级消费金额
     */
    private String consumptionAmount;

    /**
     * 满减比例
     */
    private Integer discount;

    /**
     * 头像框地址
     */
    private String avatarFrameAddress;

}


--- File: play-admin/src/main/java/com/starry/admin/modules/custom/module/entity/PlayCustomUserInfoEntity.java ---
package com.starry.admin.modules.custom.module.entity;

import com.baomidou.mybatisplus.annotation.TableName;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.starry.common.domain.BaseEntity;
import java.math.BigDecimal;
import java.util.Date;
import lombok.Data;
import lombok.EqualsAndHashCode;

/**
 * 顾客对象 play_custom_user_info
 *
 * @author admin
 * @since 2024-04-08
 */
@Data
@EqualsAndHashCode(callSuper = false)
@TableName("play_custom_user_info")
public class PlayCustomUserInfoEntity extends BaseEntity<PlayCustomUserInfoEntity> {

    /**
     * UUID
     */
    private String id;

    /**
     * 租户ID
     */
    private String tenantId;

    /**
     * 用户的标识，对当前公众号唯一
     */
    private String openid;

    /**
     * 用户的标识，对当前公众号唯一
     */
    private String unionid;

    /**
     * 顾客昵称
     */
    private String nickname;

    /**
     * 顾客性别（0:未知;1:男,2:女）
     */
    private Integer sex;

    /**
     * 头像
     */
    private String avatar;

    /**
     * 手机号码
     */
    private String phone;

    /**
     * 微信号码
     */
    private String weiChatCode;

    /**
     * 等级ID
     */
    private String levelId;

    /**
     * 所在国家
     */
    private String country;

    /**
     * 所在省份
     */
    private String province;

    /**
     * 所在城市
     */
    private String city;

    /**
     * 账户余额
     */
    private BigDecimal accountBalance;

    /**
     * 累计充值金额
     */
    private BigDecimal accumulatedRechargeAmount;

    /**
     * 累计消费金额
     */
    private BigDecimal accumulatedConsumptionAmount;

    /**
     * 余额状态[0:不存在余额，1:存在余额]
     */
    private String accountState;

    /**
     * 关注状态[0:未关注，1:已关注]
     */
    private String subscribeState;

    /**
     * 黑名单状态[0:非黑名单，1:黑名单]
     */
    private String blacklistState;

    /**
     * 违规状态[0:未违规,1:违规]
     */
    private String violationState;

    /**
     * 是否下单状态[0:未下单过，1:下单过]
     */
    private String purchaseState;

    /**
     * 绑定手机状态[0:未绑定,1:绑定]
     */
    private String mobilePhoneState;

    /**
     * 实名状态【1：已实名，0：未实名】
     */
    private String realState;

    /**
     * 是否必须实名【2:跟随店铺设置，1：必须实名，0：非必须实名】
     */
    private String mandatoryRealState;

    /**
     * 注册时间
     */
    private Date registrationTime;

    /**
     * 上次登录时间
     */
    private Date lastLoginTime;

    /**
     * 首次下单时间
     */
    private Date firstPurchaseTime;

    /**
     * 最后一次下单时间
     */
    private Date lastPurchaseTime;

    /**
     * 隐藏等级[0:不隐藏;1:隐藏]
     */
    private String hideLevelState;

    /**
     * 隐藏排名[0:不隐藏;1:隐藏]
     */
    private String hideRankingState;

    /**
     * 备注
     */
    private String remark;

    /**
     * 最近一次登录token
     */
    @JsonIgnore
    private String token;

    public PlayCustomUserInfoEntity() {
    }

    public PlayCustomUserInfoEntity(String id, BigDecimal accountBalance) {
        this.id = id;
        this.accountBalance = accountBalance;
    }
}


--- File: play-admin/src/main/java/com/starry/admin/modules/custom/module/vo/PlayCustomLeaveMsgQueryVo.java ---
package com.starry.admin.modules.custom.module.vo;

import com.starry.common.domain.BasePageEntity;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import lombok.EqualsAndHashCode;

/**
 * 顾客留言查询对象
 *
 * @author admin
 * @since 2024/5/7 16:29
 **/
@Data
@EqualsAndHashCode(callSuper = true)
@ApiModel(value = "顾客留言查询参数", description = "查询顾客留言信息的条件参数")
public class PlayCustomLeaveMsgQueryVo extends BasePageEntity {

    @ApiModelProperty(value = "顾客ID", example = "1", notes = "特定顾客的ID")
    private String customId;

    @ApiModelProperty(value = "留言内容", example = "服务很好", notes = "留言的内容，支持模糊查询")
    private String content;
}


--- File: play-admin/src/main/java/com/starry/admin/modules/custom/module/vo/PlayCustomLeaveMsgReturnVo.java ---
package com.starry.admin.modules.custom.module.vo;

import com.baomidou.mybatisplus.annotation.TableField;
import com.starry.admin.common.conf.StringTypeHandler;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import java.util.Date;
import java.util.List;
import lombok.Data;

/**
 * 顾客留言查询返回对象
 *
 * @author admin
 * @since 2024/5/7 16:29
 **/
@Data
@ApiModel(value = "顾客留言返回数据", description = "顾客留言信息的返回数据")
public class PlayCustomLeaveMsgReturnVo {

    /**
     * UUID
     */
    @ApiModelProperty(value = "留言ID", example = "1")
    private String id;

    /**
     * 留言内容
     */
    @ApiModelProperty(value = "留言内容", example = "服务很好，希望以后再次合作")
    private String content;

    /**
     * 图片
     */
    @ApiModelProperty(value = "留言图片", example = "[\"https://example.com/image1.jpg\",\"https://example.com/image2.jpg\"]")
    @TableField(typeHandler = StringTypeHandler.class)
    private List<String> images;

    /**
     * 留言时间
     */
    @ApiModelProperty(value = "留言时间", example = "2024-01-01 12:00:00")
    private Date msgTime;

    /**
     * 备注
     */
    @ApiModelProperty(value = "备注", example = "重要留言")
    private String remark;

    /**
     * 顾客Id
     */
    @ApiModelProperty(value = "顾客ID", example = "1")
    private String customId;

    /**
     * 顾客昵称
     */
    @ApiModelProperty(value = "顾客头像", example = "https://example.com/avatar.jpg")
    private String customAvatar;

    /**
     * 头像
     */
    @ApiModelProperty(value = "顾客昵称", example = "张三")
    private String customNickname;
}


--- File: play-admin/src/main/java/com/starry/admin/modules/custom/module/vo/PlayCustomLevelAddVo.java ---
package com.starry.admin.modules.custom.module.vo;

import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import javax.validation.constraints.NotBlank;
import lombok.Data;

@Data
@ApiModel(value = "顾客等级添加参数", description = "新增顾客等级信息的请求参数")
public class PlayCustomLevelAddVo {

    /**
     * 等级名称
     */
    @NotBlank(message = "等级名称不能为空")
    @ApiModelProperty(value = "等级名称", required = true, example = "钻石会员", notes = "顾客等级的名称")
    private String name;

    /**
     * 上一级消费金额
     */
    @NotBlank(message = "消费金额不能为空")
    @ApiModelProperty(value = "消费金额", required = true, example = "1000", notes = "达到该等级需要的消费金额")
    private String consumptionAmount;

    /**
     * 满减比例
     */
    @NotBlank(message = "满减比例不能为空")
    @ApiModelProperty(value = "满减比例", required = true, example = "95", notes = "该等级享受的满减折扣比例，如95表示95折")
    private Integer discount;

    /**
     * 头像框地址
     */
    @ApiModelProperty(value = "头像框地址", example = "https://example.com/frame.png", notes = "该等级专属的头像框图片地址")
    private String avatarFrameAddress;

}


--- File: play-admin/src/main/java/com/starry/admin/modules/custom/module/vo/PlayCustomLevelEditVo.java ---
package com.starry.admin.modules.custom.module.vo;

import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import javax.validation.constraints.NotBlank;
import lombok.Data;

@Data
@ApiModel(value = "顾客等级修改参数", description = "修改顾客等级信息的请求参数")
public class PlayCustomLevelEditVo {

    /**
     * UUID
     */
    @NotBlank(message = "ID不能为空")
    @ApiModelProperty(value = "等级ID", required = true, example = "1", notes = "顾客等级的ID")
    private String id;

    /**
     * 等级名称
     */
    @NotBlank(message = "等级名称不能为空")
    @ApiModelProperty(value = "等级名称", required = true, example = "钻石会员", notes = "顾客等级的名称")
    private String name;

    /**
     * 上一级消费金额
     */
    @NotBlank(message = "消费金额不能为空")
    @ApiModelProperty(value = "消费金额", required = true, example = "1000", notes = "达到该等级需要的消费金额")
    private String consumptionAmount;

    /**
     * 满减比例
     */
    @NotBlank(message = "满减比例不能为空")
    @ApiModelProperty(value = "满减比例", required = true, example = "95", notes = "该等级享受的满减折扣比例，如95表示95折")
    private Integer discount;

    /**
     * 头像框地址
     */
    @ApiModelProperty(value = "头像框地址", example = "https://example.com/frame.png", notes = "该等级专属的头像框图片地址")
    private String avatarFrameAddress;

}


--- File: play-admin/src/main/java/com/starry/admin/modules/custom/module/vo/PlayCustomLevelReturnVo.java ---
package com.starry.admin.modules.custom.module.vo;

import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;

/**
 * 顾客等级查询对象
 *
 * @author admin
 */
@Data
@ApiModel(value = "顾客等级返回数据", description = "顾客等级信息的返回数据")
public class PlayCustomLevelReturnVo {

    /**
     * UUID
     */
    @ApiModelProperty(value = "等级ID", example = "1")
    private String id;
    /**
     * 等级名称
     */
    @ApiModelProperty(value = "等级名称", example = "钻石会员")
    private String name;

    /**
     * 等级数字（排序字段）
     */
    @ApiModelProperty(value = "等级数值", example = "3", notes = "等级数字，用于排序")
    private Integer level;

    /**
     * 上一级消费金额
     */
    @ApiModelProperty(value = "消费金额", example = "1000", notes = "达到该等级需要的消费金额")
    private String consumptionAmount;

    /**
     * 满减比例
     */
    @ApiModelProperty(value = "满减比例", example = "95", notes = "该等级享受的满减折扣比例，如95表示95折")
    private Integer discount;

    /**
     * 头像框地址
     */
    @ApiModelProperty(value = "头像框地址", example = "https://example.com/frame.png")
    private String avatarFrameAddress;

}


--- File: play-admin/src/main/java/com/starry/admin/modules/custom/module/vo/PlayCustomRankingQueryVo.java ---
package com.starry.admin.modules.custom.module.vo;

import com.starry.common.domain.BasePageEntity;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import java.util.List;
import lombok.Data;
import lombok.EqualsAndHashCode;

/**
 * 顾客排名分页查询对象
 *
 * @author admin
 * @since 2024/5/14 下午7:47
 **/
@EqualsAndHashCode(callSuper = true)
@Data
@ApiModel(value = "顾客排名查询参数", description = "查询顾客消费排名信息的条件参数")
public class PlayCustomRankingQueryVo extends BasePageEntity {
    /**
     * userId
     */
    @ApiModelProperty(value = "顾客ID", example = "1", notes = "特定顾客的ID")
    private String id;

    /**
     * 下单时间
     */
    @ApiModelProperty(value = "下单时间范围", example = "['2024-01-01 00:00:00','2024-12-31 23:59:59']", notes = "下单时间范围，包含开始和结束时间")
    private List<String> purchaserTime;
}


--- File: play-admin/src/main/java/com/starry/admin/modules/custom/module/vo/PlayCustomRankingReturnVo.java ---
package com.starry.admin.modules.custom.module.vo;

import com.fasterxml.jackson.annotation.JsonIgnore;
import com.starry.admin.modules.order.module.entity.PlayOrderInfoEntity;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.util.List;
import lombok.Data;

/**
 * 顾客分页查询返回对象
 *
 * @author admin
 * @since 2024/5/14 下午7:47
 **/
@Data
@ApiModel(value = "顾客排名返回数据", description = "顾客消费排名信息的返回数据")
public class PlayCustomRankingReturnVo {
    /**
     * UUID
     */
    @ApiModelProperty(value = "顾客ID", example = "1")
    private String id;

    /**
     * 顾客昵称
     */
    @ApiModelProperty(value = "顾客昵称", example = "张三")
    private String nickname;

    /**
     * 头像
     */
    @ApiModelProperty(value = "头像", example = "https://example.com/avatar.jpg")
    private String avatar;

    /**
     * 排名
     */
    @ApiModelProperty(value = "排名", example = "1", notes = "顾客消费排名索引")
    private Integer rankingIndex;

    /**
     * 订单列表
     */
    @JsonIgnore
    @ApiModelProperty(value = "订单列表", hidden = true)
    private List<PlayOrderInfoEntity> orderInfos;

    /**
     * 下单时间-起始时间
     */
    @ApiModelProperty(value = "下单起始时间", example = "2024-01-01 00:00:00")
    private String beginPurchaserTime;

    /**
     * 下单时间-终止时间
     */
    @ApiModelProperty(value = "下单终止时间", example = "2024-12-31 23:59:59")
    private String endPurchaserTime;

    /**
     * 订单总数
     */
    @ApiModelProperty(value = "订单总数", example = "10")
    private String orderNumber;

    /**
     * 续单总数
     */
    @ApiModelProperty(value = "续单总数", example = "5", notes = "顾客续费订单的总数量")
    private String orderContinueNumber;

    /**
     * 订单单价
     */
    @ApiModelProperty(value = "订单单价", example = "100.00")
    private String orderPrice;

    /**
     * 订单总金额
     */
    @ApiModelProperty(value = "订单总金额", example = "1000.00")
    private BigDecimal orderTotalAmount;

    /**
     * 统计时间
     */
    @ApiModelProperty(value = "统计时间", example = "2024-01-01")
    private String statisticalTime = LocalDate.now().toString();

}


--- File: play-admin/src/main/java/com/starry/admin/modules/custom/module/vo/PlayCustomUserQueryVo.java ---
package com.starry.admin.modules.custom.module.vo;

import com.starry.common.domain.BasePageEntity;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import java.util.List;
import lombok.Data;
import lombok.EqualsAndHashCode;

/**
 * 顾客分页查询对象
 *
 * @author admin
 * @since 2024/5/14 下午7:47
 **/
@EqualsAndHashCode(callSuper = true)
@Data
@ApiModel(value = "顾客查询参数", description = "查询顾客信息的条件参数")
public class PlayCustomUserQueryVo extends BasePageEntity {

    @ApiModelProperty(value = "顾客ID", example = "1", notes = "特定顾客的ID")
    private String id;

    /**
     * 用户的标识，对当前公众号唯一
     */
    @ApiModelProperty(value = "OpenID", example = "o6_bmjrPTlm6_2sgVt7hMZOPfL2M", notes = "微信用户的唯一标识")
    private String openid;

    /**
     * 顾客昵称
     */
    @ApiModelProperty(value = "昵称", example = "张三", notes = "顾客的昵称，支持模糊查询")
    private String nickname;

    /**
     * 顾客性别（0:未知;1:男,2:女）
     */
    @ApiModelProperty(value = "性别", example = "1", notes = "0:未知;1:男;2:女")
    private String sex;

    /**
     * 微信号
     */
    @ApiModelProperty(value = "微信号", example = "zhangsan123", notes = "顾客的微信号")
    private String weiChatCode;

    /**
     * 绑定手机状态[0:未绑定,1:绑定]
     */
    @ApiModelProperty(value = "绑定手机状态", example = "1", notes = "0:未绑定,1:绑定")
    private String mobilePhoneState;

    /**
     * 手机号码
     */
    @ApiModelProperty(value = "手机号码", example = "13800138000", notes = "顾客的手机号码")
    private String phone;

    /**
     * 等级ID
     */
    @ApiModelProperty(value = "等级ID", example = "1", notes = "顾客等级的ID")
    private String levelId;

    /**
     * 余额状态[0:不存在余额，1:存在余额]
     */
    @ApiModelProperty(value = "余额状态", example = "1", notes = "0:不存在余额，1:存在余额")
    private String accountState;

    /**
     * 关注状态[0:未关注，1:已关注]
     */
    @ApiModelProperty(value = "关注状态", example = "1", notes = "0:未关注，1:已关注")
    private String subscribeState;

    /**
     * 违规状态[0:未违规,1:违规]
     */
    @ApiModelProperty(value = "违规状态", example = "0", notes = "0:未违规,1:违规")
    private String violationState;

    /**
     * 是否下单状态[0:未下单过，1:下单过]
     */
    @ApiModelProperty(value = "下单状态", example = "1", notes = "0:未下单过，1:下单过")
    private String purchaseState;

    /**
     * 黑名单状态[0:非黑名单，1:黑名单]
     */
    @ApiModelProperty(value = "黑名单状态", example = "0", notes = "0:非黑名单，1:黑名单")
    private String blacklistState;

    /**
     * 注册时间
     */
    @ApiModelProperty(value = "注册时间范围", example = "['2024-01-01 00:00:00','2024-12-31 23:59:59']", notes = "注册时间范围，包含开始和结束时间")
    private List<String> registrationTime;

    /**
     * 首次下单时间
     */
    @ApiModelProperty(value = "首次下单时间范围", example = "['2024-01-01 00:00:00','2024-12-31 23:59:59']", notes = "首次下单时间范围，包含开始和结束时间")
    private List<String> firstPurchaseTime;

    /**
     * 最后一次下单时间
     */
    @ApiModelProperty(value = "最后下单时间范围", example = "['2024-01-01 00:00:00','2024-12-31 23:59:59']", notes = "最后一次下单时间范围，包含开始和结束时间")
    private List<String> lastPurchaseTime;
    /**
     * 备注
     */
    @ApiModelProperty(value = "备注", example = "重要客户", notes = "顾客相关备注信息")
    private String remark;

    /**
     * 排序字段
     */
    @ApiModelProperty(value = "排序字段", example = "registrationTime", notes = "排序的字段名称")
    private String sort;

}


--- File: play-admin/src/main/java/com/starry/admin/modules/custom/module/vo/PlayCustomUserReturnVo.java ---
package com.starry.admin.modules.custom.module.vo;

import com.starry.admin.modules.order.module.entity.PlayOrderInfoEntity;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import java.math.BigDecimal;
import java.util.Date;
import java.util.List;
import lombok.Data;

/**
 * 顾客分页查询返回对象
 *
 * @author admin
 * @since 2024/5/14 下午7:47
 **/
@Data
@ApiModel(value = "顾客返回数据", description = "顾客信息的返回数据")
public class PlayCustomUserReturnVo {
    /**
     * UUID
     */
    @ApiModelProperty(value = "顾客ID", example = "1")
    private String id;

    /**
     * 租户ID
     */
    @ApiModelProperty(value = "租户ID", example = "tenant123", notes = "系统租户标识")
    private String tenantId;

    /**
     * 用户的标识，对当前公众号唯一
     */
    @ApiModelProperty(value = "OpenID", example = "o6_bmjrPTlm6_2sgVt7hMZOPfL2M", notes = "微信用户的唯一标识")
    private String openid;

    /**
     * 顾客昵称
     */
    @ApiModelProperty(value = "昵称", example = "张三")
    private String nickname;

    /**
     * 顾客性别（0:未知;1:男,2:女）
     */
    @ApiModelProperty(value = "性别", example = "1", notes = "0:未知;1:男;2:女")
    private Integer sex;

    /**
     * 头像
     */
    @ApiModelProperty(value = "头像", example = "https://example.com/avatar.jpg")
    private String avatar;

    /**
     * 手机号码
     */
    @ApiModelProperty(value = "手机号码", example = "13800138000")
    private String phone;

    /**
     * 微信号码
     */
    @ApiModelProperty(value = "微信号", example = "zhangsan123")
    private String weiChatCode;

    /**
     * 所在国家
     */
    @ApiModelProperty(value = "国家", example = "中国")
    private String country;

    /**
     * 所在省份
     */
    @ApiModelProperty(value = "省份", example = "广东省")
    private String province;

    /**
     * 所在城市
     */
    @ApiModelProperty(value = "城市", example = "深圳市")
    private String city;

    /**
     * 账户余额
     */
    @ApiModelProperty(value = "账户余额", example = "1000.50")
    private BigDecimal accountBalance;

    /**
     * 余额状态[0:不存在余额，1:存在余额]
     */
    @ApiModelProperty(value = "余额状态", example = "1", notes = "0:不存在余额，1:存在余额")
    private String accountState;

    /**
     * 关注状态[0:未关注，1:已关注]
     */
    @ApiModelProperty(value = "关注状态", example = "1", notes = "0:未关注，1:已关注")
    private String subscribeState;

    /**
     * 黑名单状态[0:非黑名单，1:黑名单]
     */
    @ApiModelProperty(value = "黑名单状态", example = "0", notes = "0:非黑名单，1:黑名单")
    private String blacklistState;

    /**
     * 违规状态[0:未违规,1:违规]
     */
    @ApiModelProperty(value = "违规状态", example = "0", notes = "0:未违规,1:违规")
    private String violationState;

    /**
     * 是否下单状态[0:未下单过，1:下单过]
     */
    @ApiModelProperty(value = "下单状态", example = "1", notes = "0:未下单过，1:下单过")
    private String purchaseState;

    /**
     * 绑定手机状态[0:未绑定,1:绑定]
     */
    @ApiModelProperty(value = "绑定手机状态", example = "1", notes = "0:未绑定,1:绑定")
    private String mobilePhoneState;

    /**
     * 实名状态【1：已实名，0：未实名】
     */
    @ApiModelProperty(value = "实名状态", example = "1", notes = "1：已实名，0：未实名")
    private String realState;

    /**
     * 是否必须实名【2:跟随店铺设置，1：必须实名，0：非必须实名】
     */
    @ApiModelProperty(value = "必须实名状态", example = "1", notes = "2:跟随店铺设置，1：必须实名，0：非必须实名")
    private String mandatoryRealState;

    /**
     * 注册时间
     */
    @ApiModelProperty(value = "注册时间", example = "2024-01-01 12:00:00")
    private Date registrationTime;

    /**
     * 上次登录时间
     */
    @ApiModelProperty(value = "上次登录时间", example = "2024-01-15 12:00:00")
    private Date lastLoginTime;

    /**
     * 首次下单时间
     */
    @ApiModelProperty(value = "首次下单时间", example = "2024-01-05 12:00:00")
    private Date firstPurchaseTime;

    /**
     * 最后一次下单时间
     */
    @ApiModelProperty(value = "最后下单时间", example = "2024-01-20 12:00:00")
    private Date lastPurchaseTime;

    /**
     * 备注
     */
    @ApiModelProperty(value = "备注", example = "重要客户")
    private String remark;

    /**
     * 等级ID
     */
    @ApiModelProperty(value = "等级ID", example = "1")
    private String levelId;

    /**
     * 等级名称
     */
    @ApiModelProperty(value = "等级名称", example = "钻石会员")
    private String levelName;

    /**
     * 订单列表
     */
    @ApiModelProperty(value = "订单列表", notes = "顾客的订单列表")
    private List<PlayOrderInfoEntity> orderInfos;
    /**
     * 订单总数
     */
    @ApiModelProperty(value = "订单总数", example = "10")
    private String orderNumber;

    /**
     * 订单单价
     */
    @ApiModelProperty(value = "订单单价", example = "100.00")
    private String orderPrice;

    /**
     * 订单总金额
     */
    @ApiModelProperty(value = "订单总金额", example = "1000.00")
    private BigDecimal orderTotalAmount;
}


--- File: play-admin/src/main/java/com/starry/admin/modules/custom/module/vo/PlayCustomUserStateEditVo.java ---
package com.starry.admin.modules.custom.module.vo;

import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import javax.validation.constraints.NotBlank;
import lombok.Data;

/**
 * @author admin
 */
@Data
@ApiModel(value = "顾客状态修改参数", description = "修改顾客状态信息的请求参数")
public class PlayCustomUserStateEditVo {

    /**
     * id
     */
    @NotBlank(message = "id不能为空")
    @ApiModelProperty(value = "顾客ID", required = true, example = "1", notes = "顾客的ID")
    private String id;

    /**
     * 余额状态[0:不存在余额，1:存在余额]
     */
    @ApiModelProperty(value = "余额状态", example = "1", notes = "0:不存在余额，1:存在余额")
    private String accountState;

    /**
     * 关注状态[0:未关注，1:已关注]
     */
    @ApiModelProperty(value = "关注状态", example = "1", notes = "0:未关注，1:已关注")
    private String subscribeState;

    /**
     * 黑名单状态[0:非黑名单，1:黑名单]
     */
    @ApiModelProperty(value = "黑名单状态", example = "0", notes = "0:非黑名单，1:黑名单")
    private String blacklistState;

    /**
     * 违规状态[0:未违规,1:违规]
     */
    @ApiModelProperty(value = "违规状态", example = "0", notes = "0:未违规,1:违规")
    private String violationState;

    /**
     * 是否下单状态[0:未未下单，1:下单过]
     */
    @ApiModelProperty(value = "下单状态", example = "1", notes = "0:未下单过，1:下单过")
    private String purchaseState;

    /**
     * 绑定手机状态[0:未绑定,1:绑定]
     */
    @ApiModelProperty(value = "绑定手机状态", example = "1", notes = "0:未绑定,1:绑定")
    private String mobilePhoneState;

    /**
     * 实名状态【1：已实名，0：未实名】
     */
    @ApiModelProperty(value = "实名状态", example = "1", notes = "1：已实名，0：未实名")
    private String realState;

    /**
     * 是否必须实名【0:非必须实名;1:必须实名;2:跟随店铺设置】
     */
    @ApiModelProperty(value = "必须实名状态", example = "1", notes = "2:跟随店铺设置，1：必须实名，0：非必须实名")
    private String mandatoryRealState;

    @ApiModelProperty(value = "备注", example = "重要客户", notes = "顾客相关备注信息")
    private String remark;

}


--- File: play-admin/src/main/java/com/starry/admin/modules/custom/service/IPlayCustomFollowInfoService.java ---
package com.starry.admin.modules.custom.service;

import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.service.IService;
import com.starry.admin.modules.custom.entity.PlayCustomFollowInfoEntity;
import com.starry.admin.modules.weichat.entity.PlayClerkFollowQueryVo;
import com.starry.admin.modules.weichat.entity.PlayClerkFollowReturnVo;

/**
 * 顾客关注陪聊信息Service接口
 *
 * @author admin
 * @since 2024-04-30
 */
public interface IPlayCustomFollowInfoService extends IService<PlayCustomFollowInfoEntity> {
    /**
     * 查询顾客关注陪聊信息
     *
     * @param id
     *            顾客关注陪聊信息主键
     * @return 顾客关注陪聊信息
     */
    PlayCustomFollowInfoEntity selectPlayCustomFollowInfoById(String id);

    /**
     * 修改关注状态
     *
     * @param customUserId
     *            顾客ID
     * @param clerkUserId
     *            陪聊ID
     * @return 关注状态[0:未关注,1:已关注]
     */
    String queryFollowState(String customUserId, String clerkUserId);

    /**
     * 修改关注状态
     *
     * @param customUserId
     *            顾客ID
     * @param clerkUserId
     *            陪聊ID
     * @param followState
     *            关注状态[0:未关注,1:已关注]
     */
    void updateFollowState(String customUserId, String clerkUserId, String followState);

    /**
     * 分页查询陪聊关注状态
     *
     * @param vo
     *            店员关注状态 查询对象
     * @return 顾客关注陪聊信息集合
     * @author admin
     * @since 2024/5/10 10:28
     **/
    IPage<PlayClerkFollowReturnVo> selectByPage(PlayClerkFollowQueryVo vo);

    /**
     * 查询顾客关注陪聊信息列表
     *
     * @param playCustomFollowInfo
     *            顾客关注陪聊信息
     * @return 顾客关注陪聊信息集合
     */
    IPage<PlayCustomFollowInfoEntity> selectPlayCustomFollowInfoByPage(PlayCustomFollowInfoEntity playCustomFollowInfo);

    /**
     * 新增顾客关注陪聊信息
     *
     * @param playCustomFollowInfo
     *            顾客关注陪聊信息
     * @return 结果
     */
    boolean create(PlayCustomFollowInfoEntity playCustomFollowInfo);

    /**
     * 修改顾客关注陪聊信息
     *
     * @param playCustomFollowInfo
     *            顾客关注陪聊信息
     * @return 结果
     */
    boolean update(PlayCustomFollowInfoEntity playCustomFollowInfo);

    /**
     * 批量删除顾客关注陪聊信息
     *
     * @param ids
     *            需要删除的顾客关注陪聊信息主键集合
     * @return 结果
     */
    int deletePlayCustomFollowInfoByIds(String[] ids);

    /**
     * 删除顾客关注陪聊信息信息
     *
     * @param id
     *            顾客关注陪聊信息主键
     * @return 结果
     */
    int deletePlayCustomFollowInfoById(String id);
}


--- File: play-admin/src/main/java/com/starry/admin/modules/custom/service/IPlayCustomGiftInfoService.java ---
package com.starry.admin.modules.custom.service;

import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.service.IService;
import com.starry.admin.modules.custom.module.entity.PlayCustomGiftInfoEntity;
import java.util.List;

/**
 * 顾客和礼物关系Service接口
 *
 * @author admin
 * @since 2024-06-05
 */
public interface IPlayCustomGiftInfoService extends IService<PlayCustomGiftInfoEntity> {

    /**
     * 根据顾客ID和礼物ID，查询当前礼物活点亮情况
     *
     * @param giftId
     *            礼物ID
     * @param customId
     *            顾客ID
     * @return 物活点亮情况
     */
    PlayCustomGiftInfoEntity selectByGiftIdAndCustomId(String giftId, String customId);

    /**
     * 查询顾客已点亮礼物
     *
     * @param customId
     *            顾客IF
     * @return 顾客已点亮礼物列表
     */
    List<PlayCustomGiftInfoEntity> selectBtyCustomId(String customId);

    /**
     * 查询顾客和礼物关系
     *
     * @param id
     *            顾客和礼物关系主键
     * @return 顾客和礼物关系
     */
    PlayCustomGiftInfoEntity selectPlayCustomGiftInfoById(String id);

    /**
     * 查询顾客和礼物关系列表
     *
     * @param playCustomGiftInfo
     *            顾客和礼物关系
     * @return 顾客和礼物关系集合
     */
    IPage<PlayCustomGiftInfoEntity> selectPlayCustomGiftInfoByPage(PlayCustomGiftInfoEntity playCustomGiftInfo);

    /**
     * 新增顾客和礼物关系
     *
     * @param playCustomGiftInfo
     *            顾客和礼物关系
     * @return 结果
     */
    boolean create(PlayCustomGiftInfoEntity playCustomGiftInfo);

    /**
     * 修改顾客和礼物关系
     *
     * @param playCustomGiftInfo
     *            顾客和礼物关系
     * @return 结果
     */
    boolean update(PlayCustomGiftInfoEntity playCustomGiftInfo);

    /**
     * 批量删除顾客和礼物关系
     *
     * @param ids
     *            需要删除的顾客和礼物关系主键集合
     * @return 结果
     */
    int deletePlayCustomGiftInfoByIds(String[] ids);

    /**
     * 删除顾客和礼物关系信息
     *
     * @param id
     *            顾客和礼物关系主键
     * @return 结果
     */
    int deletePlayCustomGiftInfoById(String id);
}


--- File: play-admin/src/main/java/com/starry/admin/modules/custom/service/IPlayCustomLeaveMsgService.java ---
package com.starry.admin.modules.custom.service;

import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.service.IService;
import com.starry.admin.modules.custom.module.entity.PlayCustomLeaveMsgEntity;
import com.starry.admin.modules.custom.module.vo.PlayCustomLeaveMsgQueryVo;
import com.starry.admin.modules.custom.module.vo.PlayCustomLeaveMsgReturnVo;

/**
 * 顾客留言Service接口
 *
 * @author admin
 * @since 2024-05-07
 */
public interface IPlayCustomLeaveMsgService extends IService<PlayCustomLeaveMsgEntity> {
    /**
     * 查询顾客留言
     *
     * @param id
     *            顾客留言主键
     * @return 顾客留言
     */
    PlayCustomLeaveMsgEntity selectPlayCustomLeaveMsgById(String id);

    /**
     * 查询当前用户不允许留言的原因
     *
     * @param customId
     *            顾客ID
     * @return 没有权限原因
     */
    String queryNoMessagesAllowedReason(String customId);

    /**
     * 分页查询顾客留言列表
     *
     * @param vo
     *            顾客留言分页查询对象
     * @return 顾客留言集合
     */
    IPage<PlayCustomLeaveMsgReturnVo> selectByPage(PlayCustomLeaveMsgQueryVo vo);

    /**
     * 查询顾客留言列表
     *
     * @param playCustomLeaveMsg
     *            顾客留言
     * @return 顾客留言集合
     */
    IPage<PlayCustomLeaveMsgEntity> selectPlayCustomLeaveMsgByPage(PlayCustomLeaveMsgEntity playCustomLeaveMsg);

    /**
     * 新增顾客留言
     *
     * @param playCustomLeaveMsg
     *            顾客留言
     * @return 结果
     */
    boolean create(PlayCustomLeaveMsgEntity playCustomLeaveMsg);

    /**
     * 修改顾客留言
     *
     * @param playCustomLeaveMsg
     *            顾客留言
     * @return 结果
     */
    boolean update(PlayCustomLeaveMsgEntity playCustomLeaveMsg);

    /**
     * 批量删除顾客留言
     *
     * @param ids
     *            需要删除的顾客留言主键集合
     * @return 结果
     */
    int deletePlayCustomLeaveMsgByIds(String[] ids);

    /**
     * 删除顾客留言信息
     *
     * @param id
     *            顾客留言主键
     * @return 结果
     */
    int deletePlayCustomLeaveMsgById(String id);
}


--- File: play-admin/src/main/java/com/starry/admin/modules/custom/service/IPlayCustomLevelInfoService.java ---
package com.starry.admin.modules.custom.service;

import com.baomidou.mybatisplus.extension.service.IService;
import com.starry.admin.modules.custom.module.entity.PlayCustomLevelInfoEntity;
import com.starry.admin.modules.system.module.entity.SysTenantEntity;
import java.util.List;

/**
 * 顾客等级Service接口
 *
 * @author admin
 * @since 2024-05-04
 */
public interface IPlayCustomLevelInfoService extends IService<PlayCustomLevelInfoEntity> {

    /**
     * 初始化顾客等级
     *
     * @param sysTenantEntity
     *            租户细信息
     * @author admin
     * @since 2024/7/19 15:13
     **/
    void initDefaultLevel(SysTenantEntity sysTenantEntity);

    /**
     * 获取新增顾客时，默认最低等级ID
     *
     * @return PlayCustomLevelInfoEntity
     */
    PlayCustomLevelInfoEntity getDefaultLevel();

    /**
     * 查询顾客等级列表
     *
     * @return 顾客等级集合
     */
    List<PlayCustomLevelInfoEntity> selectAll();

    /**
     * 查询顾客等级
     *
     * @param id
     *            顾客等级主键
     * @return 顾客等级
     */
    PlayCustomLevelInfoEntity selectPlayCustomLevelInfoById(String id);

    /**
     * 查询顾客等级列表
     *
     * @return 顾客等级集合
     */
    List<PlayCustomLevelInfoEntity> selectPlayCustomLevelInfoByPage();

    /**
     * 新增顾客等级
     *
     * @param playCustomLevelInfo
     *            顾客等级
     * @return 结果
     */
    boolean create(PlayCustomLevelInfoEntity playCustomLevelInfo);

    /**
     * 修改顾客等级
     *
     * @param playCustomLevelInfo
     *            顾客等级
     * @return 结果
     */
    boolean update(PlayCustomLevelInfoEntity playCustomLevelInfo);

    /**
     * 批量删除顾客等级
     *
     * @param ids
     *            需要删除的顾客等级主键集合
     * @return 结果
     */
    int deletePlayCustomLevelInfoByIds(String[] ids);

    /**
     * 删除顾客等级信息
     *
     * @param id
     *            顾客等级主键
     * @return 结果
     */
    int deletePlayCustomLevelInfoById(String id);

    /**
     * 查询最大等级
     *
     * @return 最大等级
     */
    int selectMaxLevel();

    /**
     * 删除最大等级
     */
    void delMaxLevelByLevel(Integer level);
}


--- File: play-admin/src/main/java/com/starry/admin/modules/custom/service/IPlayCustomUserInfoService.java ---
package com.starry.admin.modules.custom.service;

import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.service.IService;
import com.starry.admin.modules.custom.module.entity.PlayCustomUserInfoEntity;
import com.starry.admin.modules.custom.module.vo.PlayCustomRankingQueryVo;
import com.starry.admin.modules.custom.module.vo.PlayCustomRankingReturnVo;
import com.starry.admin.modules.custom.module.vo.PlayCustomUserQueryVo;
import com.starry.admin.modules.custom.module.vo.PlayCustomUserReturnVo;
import com.starry.admin.modules.order.module.entity.PlayOrderInfoEntity;
import java.math.BigDecimal;
import java.util.List;

/**
 * 顾客Service接口
 *
 * @author admin
 * @since 2024-04-08
 */
public interface IPlayCustomUserInfoService extends IService<PlayCustomUserInfoEntity> {

    /**
     * 查询顾客
     *
     * @param openId
     *            顾客主键
     * @return 顾客
     */
    PlayCustomUserInfoEntity selectByOpenid(String openId);

    /**
     * 根据顾客等级，查询顾客充值余额时,实际支付金额
     *
     * @param rechargeAmount
     *            重置金额，单位分
     * @param customId
     *            顾客ID
     * @return 实际支付金额
     */
    BigDecimal getCustomPaymentAmount(BigDecimal rechargeAmount, String customId);

    /**
     * 顾客账户余额充值
     *
     * @param rechargeAmount
     *            充值金额，单位分
     * @param customId
     *            顾客ID
     * @param orderId
     *            订单ID
     */
    void customAccountBalanceRecharge(BigDecimal rechargeAmount, String customId, String orderId);

    /**
     * 查询顾客
     *
     * @param id
     *            顾客主键
     * @return 顾客
     */
    PlayCustomUserInfoEntity selectById(String id);

    /**
     * 分页查询顾客列表
     *
     * @param vo
     *            顾客查询对象
     * @return 顾客集合
     */
    IPage<PlayCustomUserReturnVo> selectByPage(PlayCustomUserQueryVo vo);

    /**
     * 分页查询顾客排名列表
     *
     * @param vo
     *            顾客查询对象
     * @return 顾客集合
     */
    IPage<PlayCustomRankingReturnVo> selectRankingByPage(PlayCustomRankingQueryVo vo);

    /**
     * 新增顾客
     *
     * @param playCustomUserInfo
     *            顾客
     * @return 结果
     */
    boolean create(PlayCustomUserInfoEntity playCustomUserInfo);

    /**
     * 更新token
     *
     * @param id
     *            账户余额
     * @param token
     *            TOKEN
     * @author admin
     * @since 2024/4/9 14:33
     **/
    void updateTokenById(String id, String token);

    /**
     * 更新账号余额
     *
     * @param id
     *            用户ID
     * @param accountBalance
     *            账户余额
     * @author admin
     * @since 2024/4/9 14:33
     **/
    void updateAccountBalanceById(String id, BigDecimal accountBalance);

    /**
     * 更新顾客账户余额信息
     *
     * @param userId
     *            用户ID
     * @param balanceBeforeOperation
     *            操作前余额
     * @param balanceAfterOperation
     *            操作后余额
     * @param operationType
     *            操作类型（0:充值;1:消费;2:服务;3:退款）
     * @param operationAction
     *            操作动作
     * @param balanceMoney
     *            操作金额
     * @param giftAmount
     *            赠送金额
     * @param orderId
     *            订单ID
     */
    void updateAccountBalanceById(String userId, BigDecimal balanceBeforeOperation, BigDecimal balanceAfterOperation,
            String operationType, String operationAction, BigDecimal balanceMoney, BigDecimal giftAmount,
            String orderId);

    /**
     * 修改顾客
     *
     * @param playCustomUserInfo
     *            顾客
     * @return 结果
     */
    boolean update(PlayCustomUserInfoEntity playCustomUserInfo);

    /**
     * 批量删除顾客
     *
     * @param ids
     *            需要删除的顾客主键集合
     * @return 结果
     */
    int deletePlayCustomUserInfoByIds(String[] ids);

    /**
     * 删除顾客信息
     *
     * @param id
     *            顾客主键
     * @return 结果
     */
    int deletePlayCustomUserInfoById(String id);

    List<PlayCustomUserInfoEntity> simpleList();

    /**
     * 保存订单信息
     *
     * @param entity
     *            订单实体 * @since 2024/7/18 17:13
     * @author admin
     **/
    void saveOrderInfo(PlayOrderInfoEntity entity);
}


--- File: play-admin/src/main/java/com/starry/admin/modules/custom/service/impl/PlayCustomFollowInfoServiceImpl.java ---
package com.starry.admin.modules.custom.service.impl;

import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.github.yulichang.wrapper.MPJLambdaWrapper;
import com.starry.admin.modules.clerk.module.entity.PlayClerkUserInfoEntity;
import com.starry.admin.modules.custom.entity.PlayCustomFollowInfoEntity;
import com.starry.admin.modules.custom.mapper.PlayCustomFollowInfoMapper;
import com.starry.admin.modules.custom.service.IPlayCustomFollowInfoService;
import com.starry.admin.modules.weichat.entity.PlayClerkFollowQueryVo;
import com.starry.admin.modules.weichat.entity.PlayClerkFollowReturnVo;
import com.starry.common.utils.IdUtils;
import java.util.Arrays;
import java.util.Date;
import javax.annotation.Resource;
import org.springframework.stereotype.Service;

/**
 * 顾客关注陪聊信息Service业务层处理
 *
 * @author admin
 * @since 2024-04-30
 */
@Service
public class PlayCustomFollowInfoServiceImpl extends ServiceImpl<PlayCustomFollowInfoMapper, PlayCustomFollowInfoEntity>
        implements
            IPlayCustomFollowInfoService {
    @Resource
    private PlayCustomFollowInfoMapper playCustomFollowInfoMapper;

    /**
     * 查询顾客关注陪聊信息
     *
     * @param id
     *            顾客关注陪聊信息主键
     * @return 顾客关注陪聊信息
     */
    @Override
    public PlayCustomFollowInfoEntity selectPlayCustomFollowInfoById(String id) {
        return this.baseMapper.selectById(id);
    }

    @Override
    public String queryFollowState(String customUserId, String clerkUserId) {
        LambdaQueryWrapper<PlayCustomFollowInfoEntity> lambdaQueryWrapper = new LambdaQueryWrapper<>();
        lambdaQueryWrapper.eq(PlayCustomFollowInfoEntity::getClerkId, clerkUserId);
        lambdaQueryWrapper.eq(PlayCustomFollowInfoEntity::getCustomId, customUserId);
        PlayCustomFollowInfoEntity entity = this.baseMapper.selectOne(lambdaQueryWrapper);
        return entity == null ? "0" : entity.getFollowState();
    }

    @Override
    public void updateFollowState(String customUserId, String clerkUserId, String followState) {
        LambdaQueryWrapper<PlayCustomFollowInfoEntity> lambdaQueryWrapper = new LambdaQueryWrapper<>();
        lambdaQueryWrapper.eq(PlayCustomFollowInfoEntity::getClerkId, clerkUserId);
        lambdaQueryWrapper.eq(PlayCustomFollowInfoEntity::getCustomId, customUserId);
        PlayCustomFollowInfoEntity entity = this.baseMapper.selectOne(lambdaQueryWrapper);
        if (entity == null) {
            entity = new PlayCustomFollowInfoEntity();
            entity.setCustomId(customUserId);
            entity.setFollowState(followState);
            entity.setClerkId(clerkUserId);
            entity.setFollowTime(new Date());
            this.baseMapper.insert(entity);
        } else {
            entity.setFollowState(followState);
            this.baseMapper.updateById(entity);
        }
    }

    @Override
    public IPage<PlayClerkFollowReturnVo> selectByPage(PlayClerkFollowQueryVo vo) {
        MPJLambdaWrapper<PlayCustomFollowInfoEntity> lambdaQueryWrapper = new MPJLambdaWrapper<PlayCustomFollowInfoEntity>()
                // 查询主表全部字段
                .selectAll(PlayCustomFollowInfoEntity.class)
                // 陪聊用户表全部字段
                .selectAll(PlayClerkUserInfoEntity.class)
                // 陪聊用户表
                .leftJoin(PlayClerkUserInfoEntity.class, PlayClerkUserInfoEntity::getId,
                        PlayCustomFollowInfoEntity::getClerkId);
        if (StrUtil.isNotBlank(vo.getCustomId())) {
            lambdaQueryWrapper.eq(PlayCustomFollowInfoEntity::getCustomId, vo.getCustomId());
        }
        if (StrUtil.isNotBlank(vo.getFollowState())) {
            lambdaQueryWrapper.eq(PlayCustomFollowInfoEntity::getFollowState, vo.getFollowState());
        }
        IPage<PlayClerkFollowReturnVo> iPage = this.baseMapper.selectJoinPage(
                new Page<>(vo.getPageNum(), vo.getPageSize()), PlayClerkFollowReturnVo.class, lambdaQueryWrapper);
        for (PlayClerkFollowReturnVo record : iPage.getRecords()) {
            LambdaQueryWrapper<PlayCustomFollowInfoEntity> lambdaQueryWrapper1 = new LambdaQueryWrapper<>();
            lambdaQueryWrapper1.eq(PlayCustomFollowInfoEntity::getClerkId, record.getClerkId());
            record.setFollowNumber(this.baseMapper.selectList(lambdaQueryWrapper1).size());
        }
        return iPage;
    }

    /**
     * 查询顾客关注陪聊信息列表
     *
     * @param playCustomFollowInfo
     *            顾客关注陪聊信息
     * @return 顾客关注陪聊信息
     */
    @Override
    public IPage<PlayCustomFollowInfoEntity> selectPlayCustomFollowInfoByPage(
            PlayCustomFollowInfoEntity playCustomFollowInfo) {
        Page<PlayCustomFollowInfoEntity> page = new Page<>(1, 10);
        return this.baseMapper.selectPage(page, new LambdaQueryWrapper<>());
    }

    /**
     * 新增顾客关注陪聊信息
     *
     * @param playCustomFollowInfo
     *            顾客关注陪聊信息
     * @return 结果
     */
    @Override
    public boolean create(PlayCustomFollowInfoEntity playCustomFollowInfo) {
        if (StrUtil.isBlankIfStr(playCustomFollowInfo.getId())) {
            playCustomFollowInfo.setId(IdUtils.getUuid());
        }
        return save(playCustomFollowInfo);
    }

    /**
     * 修改顾客关注陪聊信息
     *
     * @param playCustomFollowInfo
     *            顾客关注陪聊信息
     * @return 结果
     */
    @Override
    public boolean update(PlayCustomFollowInfoEntity playCustomFollowInfo) {
        return updateById(playCustomFollowInfo);
    }

    /**
     * 批量删除顾客关注陪聊信息
     *
     * @param ids
     *            需要删除的顾客关注陪聊信息主键
     * @return 结果
     */
    @Override
    public int deletePlayCustomFollowInfoByIds(String[] ids) {
        return playCustomFollowInfoMapper.deleteBatchIds(Arrays.asList(ids));
    }

    /**
     * 删除顾客关注陪聊信息信息
     *
     * @param id
     *            顾客关注陪聊信息主键
     * @return 结果
     */
    @Override
    public int deletePlayCustomFollowInfoById(String id) {
        return playCustomFollowInfoMapper.deleteById(id);
    }
}


--- File: play-admin/src/main/java/com/starry/admin/modules/custom/service/impl/PlayCustomGiftInfoServiceImpl.java ---
package com.starry.admin.modules.custom.service.impl;

import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.starry.admin.modules.custom.mapper.PlayCustomGiftInfoMapper;
import com.starry.admin.modules.custom.module.entity.PlayCustomGiftInfoEntity;
import com.starry.admin.modules.custom.service.IPlayCustomGiftInfoService;
import com.starry.common.utils.IdUtils;
import java.util.Arrays;
import java.util.List;
import javax.annotation.Resource;
import org.springframework.stereotype.Service;

/**
 * 顾客和礼物关系Service业务层处理
 *
 * @author admin
 * @since 2024-06-05
 */
@Service
public class PlayCustomGiftInfoServiceImpl extends ServiceImpl<PlayCustomGiftInfoMapper, PlayCustomGiftInfoEntity>
        implements
            IPlayCustomGiftInfoService {
    @Resource
    private PlayCustomGiftInfoMapper playCustomGiftInfoMapper;

    @Override
    public PlayCustomGiftInfoEntity selectByGiftIdAndCustomId(String giftId, String customId) {
        LambdaQueryWrapper<PlayCustomGiftInfoEntity> lambdaQueryWrapper = new LambdaQueryWrapper<>();
        lambdaQueryWrapper.eq(PlayCustomGiftInfoEntity::getCustomId, customId);
        lambdaQueryWrapper.eq(PlayCustomGiftInfoEntity::getGiffId, giftId);
        return this.baseMapper.selectOne(lambdaQueryWrapper);
    }

    /**
     * 根据店员ID查询店员活动礼物列表
     *
     * @param customId
     *            店员ID
     * @return 店员活动礼物列表
     */
    @Override
    public List<PlayCustomGiftInfoEntity> selectBtyCustomId(String customId) {
        LambdaQueryWrapper<PlayCustomGiftInfoEntity> lambdaQueryWrapper = new LambdaQueryWrapper<>();
        lambdaQueryWrapper.eq(PlayCustomGiftInfoEntity::getCustomId, customId);
        return this.baseMapper.selectList(lambdaQueryWrapper);
    }

    /**
     * 查询顾客和礼物关系
     *
     * @param id
     *            顾客和礼物关系主键
     * @return 顾客和礼物关系
     */
    @Override
    public PlayCustomGiftInfoEntity selectPlayCustomGiftInfoById(String id) {
        return this.baseMapper.selectById(id);
    }

    /**
     * 查询顾客和礼物关系列表
     *
     * @param playCustomGiftInfo
     *            顾客和礼物关系
     * @return 顾客和礼物关系
     */
    @Override
    public IPage<PlayCustomGiftInfoEntity> selectPlayCustomGiftInfoByPage(PlayCustomGiftInfoEntity playCustomGiftInfo) {
        Page<PlayCustomGiftInfoEntity> page = new Page<>(1, 10);
        return this.baseMapper.selectPage(page, new LambdaQueryWrapper<PlayCustomGiftInfoEntity>());
    }

    /**
     * 新增顾客和礼物关系
     *
     * @param playCustomGiftInfo
     *            顾客和礼物关系
     * @return 结果
     */
    @Override
    public boolean create(PlayCustomGiftInfoEntity playCustomGiftInfo) {
        if (StrUtil.isBlankIfStr(playCustomGiftInfo.getId())) {
            playCustomGiftInfo.setId(IdUtils.getUuid());
        }
        return save(playCustomGiftInfo);
    }

    /**
     * 修改顾客和礼物关系
     *
     * @param playCustomGiftInfo
     *            顾客和礼物关系
     * @return 结果
     */
    @Override
    public boolean update(PlayCustomGiftInfoEntity playCustomGiftInfo) {
        return updateById(playCustomGiftInfo);
    }

    /**
     * 批量删除顾客和礼物关系
     *
     * @param ids
     *            需要删除的顾客和礼物关系主键
     * @return 结果
     */
    @Override
    public int deletePlayCustomGiftInfoByIds(String[] ids) {
        return playCustomGiftInfoMapper.deleteBatchIds(Arrays.asList(ids));
    }

    /**
     * 删除顾客和礼物关系信息
     *
     * @param id
     *            顾客和礼物关系主键
     * @return 结果
     */
    @Override
    public int deletePlayCustomGiftInfoById(String id) {
        return playCustomGiftInfoMapper.deleteById(id);
    }
}


--- File: play-admin/src/main/java/com/starry/admin/modules/custom/service/impl/PlayCustomLeaveMsgServiceImpl.java ---
package com.starry.admin.modules.custom.service.impl;

import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.github.yulichang.wrapper.MPJLambdaWrapper;
import com.starry.admin.common.conf.ThreadLocalRequestDetail;
import com.starry.admin.common.exception.CustomException;
import com.starry.admin.modules.custom.mapper.PlayCustomLeaveMsgMapper;
import com.starry.admin.modules.custom.module.entity.PlayCustomLeaveMsgEntity;
import com.starry.admin.modules.custom.module.entity.PlayCustomUserInfoEntity;
import com.starry.admin.modules.custom.module.vo.PlayCustomLeaveMsgQueryVo;
import com.starry.admin.modules.custom.module.vo.PlayCustomLeaveMsgReturnVo;
import com.starry.admin.modules.custom.service.IPlayCustomLeaveMsgService;
import com.starry.common.utils.IdUtils;
import com.starry.common.utils.StringUtils;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.util.Arrays;
import javax.annotation.Resource;
import org.springframework.stereotype.Service;

/**
 * 顾客留言Service业务层处理
 *
 * @author admin
 * @since 2024-05-07
 */
@Service
public class PlayCustomLeaveMsgServiceImpl extends ServiceImpl<PlayCustomLeaveMsgMapper, PlayCustomLeaveMsgEntity>
        implements
            IPlayCustomLeaveMsgService {
    @Resource
    private PlayCustomLeaveMsgMapper playCustomLeaveMsgMapper;

    @Override
    public IPage<PlayCustomLeaveMsgReturnVo> selectByPage(PlayCustomLeaveMsgQueryVo vo) {
        MPJLambdaWrapper<PlayCustomLeaveMsgEntity> lambdaQueryWrapper = new MPJLambdaWrapper<PlayCustomLeaveMsgEntity>()
                // 查询主表全部字段
                .selectAll(PlayCustomLeaveMsgEntity.class)
                // 查询顾客表
                .selectAs(PlayCustomUserInfoEntity::getId, "customId")
                .selectAs(PlayCustomUserInfoEntity::getAvatar, "customAvatar")
                .selectAs(PlayCustomUserInfoEntity::getNickname, "customNickname")
                // 子表
                .leftJoin(PlayCustomUserInfoEntity.class, PlayCustomUserInfoEntity::getId,
                        PlayCustomLeaveMsgEntity::getCustomId);
        return this.baseMapper.selectJoinPage(new Page<>(vo.getPageNum(), vo.getPageSize()),
                PlayCustomLeaveMsgReturnVo.class, lambdaQueryWrapper);
    }

    /**
     * 查询顾客留言
     *
     * @param id
     *            顾客留言主键
     * @return 顾客留言
     */
    @Override
    public PlayCustomLeaveMsgEntity selectPlayCustomLeaveMsgById(String id) {
        return this.baseMapper.selectById(id);
    }

    /**
     * 查询顾客留言列表
     *
     * @param playCustomLeaveMsg
     *            顾客留言
     * @return 顾客留言
     */
    @Override
    public IPage<PlayCustomLeaveMsgEntity> selectPlayCustomLeaveMsgByPage(PlayCustomLeaveMsgEntity playCustomLeaveMsg) {
        Page<PlayCustomLeaveMsgEntity> page = new Page<>(1, 10);
        return this.baseMapper.selectPage(page, new LambdaQueryWrapper<>());
    }

    /**
     * 查询当前用户不允许留言的原因 1、一个人在同一天只能留言一次
     *
     * @param customId
     *            顾客ID
     * @return 当前用户不允许留言的原因
     */
    @Override
    public String queryNoMessagesAllowedReason(String customId) {
        // 1、一个人在同一天只能留言一次
        LocalDateTime now = LocalDateTime.now();
        LocalDateTime start = LocalDateTime.of(now.toLocalDate(), LocalTime.MIN);
        LocalDateTime end = LocalDateTime.of(now.toLocalDate(), LocalTime.MAX);
        LambdaQueryWrapper<PlayCustomLeaveMsgEntity> lambdaQueryWrapper = new LambdaQueryWrapper<>();
        lambdaQueryWrapper.eq(PlayCustomLeaveMsgEntity::getCustomId, customId);
        lambdaQueryWrapper.between(PlayCustomLeaveMsgEntity::getMsgTime, start, end);
        if (this.baseMapper.selectOne(lambdaQueryWrapper) != null) {
            return "一天只能留言一次";
        }
        // 2、
        // 3、
        return "";
    }

    /**
     * 新增顾客留言
     *
     * @param playCustomLeaveMsg
     *            顾客留言
     * @return 结果
     */
    @Override
    public boolean create(PlayCustomLeaveMsgEntity playCustomLeaveMsg) {
        String noMessagesAllowedReason = this
                .queryNoMessagesAllowedReason(ThreadLocalRequestDetail.getCustomUserInfo().getId());
        if (StringUtils.isNotEmpty(noMessagesAllowedReason)) {
            throw new CustomException(noMessagesAllowedReason);
        }
        if (StrUtil.isBlankIfStr(playCustomLeaveMsg.getId())) {
            playCustomLeaveMsg.setId(IdUtils.getUuid());
        }
        return save(playCustomLeaveMsg);
    }

    /**
     * 修改顾客留言
     *
     * @param playCustomLeaveMsg
     *            顾客留言
     * @return 结果
     */
    @Override
    public boolean update(PlayCustomLeaveMsgEntity playCustomLeaveMsg) {
        return updateById(playCustomLeaveMsg);
    }

    /**
     * 批量删除顾客留言
     *
     * @param ids
     *            需要删除的顾客留言主键
     * @return 结果
     */
    @Override
    public int deletePlayCustomLeaveMsgByIds(String[] ids) {
        return playCustomLeaveMsgMapper.deleteBatchIds(Arrays.asList(ids));
    }

    /**
     * 删除顾客留言信息
     *
     * @param id
     *            顾客留言主键
     * @return 结果
     */
    @Override
    public int deletePlayCustomLeaveMsgById(String id) {
        return playCustomLeaveMsgMapper.deleteById(id);
    }
}


--- File: play-admin/src/main/java/com/starry/admin/modules/custom/service/impl/PlayCustomLevelInfoServiceImpl.java ---
package com.starry.admin.modules.custom.service.impl;

import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.starry.admin.modules.custom.mapper.PlayCustomLevelInfoMapper;
import com.starry.admin.modules.custom.module.entity.PlayCustomLevelInfoEntity;
import com.starry.admin.modules.custom.service.IPlayCustomLevelInfoService;
import com.starry.admin.modules.system.module.entity.SysTenantEntity;
import com.starry.common.utils.IdUtils;
import java.util.Arrays;
import java.util.List;
import javax.annotation.Resource;
import org.springframework.stereotype.Service;

/**
 * 顾客等级Service业务层处理
 *
 * @author admin
 * @since 2024-05-04
 */
@Service
public class PlayCustomLevelInfoServiceImpl extends ServiceImpl<PlayCustomLevelInfoMapper, PlayCustomLevelInfoEntity>
        implements
            IPlayCustomLevelInfoService {
    @Resource
    private PlayCustomLevelInfoMapper playCustomLevelInfoMapper;

    @Override
    public void initDefaultLevel(SysTenantEntity sysTenantEntity) {
        List<PlayCustomLevelInfoEntity> list = this.selectAll();
        if (list == null || list.isEmpty()) {
            PlayCustomLevelInfoEntity entity = new PlayCustomLevelInfoEntity();
            entity.setLevel(1);
            entity.setName("V1");
            entity.setConsumptionAmount("1");
            entity.setDiscount(100);
            entity.setTenantId(sysTenantEntity.getTenantId());
            this.baseMapper.insert(entity);
        }
    }

    @Override
    public PlayCustomLevelInfoEntity getDefaultLevel() {
        List<PlayCustomLevelInfoEntity> list = this.selectAll();
        if (list != null && !list.isEmpty()) {
            return list.get(0);
        } else {
            PlayCustomLevelInfoEntity entity = new PlayCustomLevelInfoEntity();
            entity.setLevel(1);
            entity.setName("V1");
            entity.setConsumptionAmount("1");
            entity.setDiscount(100);
            this.baseMapper.insert(entity);
            return entity;
        }
    }

    @Override
    public List<PlayCustomLevelInfoEntity> selectAll() {
        LambdaQueryWrapper<PlayCustomLevelInfoEntity> lambdaQueryWrapper = new LambdaQueryWrapper<>();
        lambdaQueryWrapper.orderByAsc(PlayCustomLevelInfoEntity::getLevel);
        return this.baseMapper.selectList(lambdaQueryWrapper);
    }

    /**
     * 查询顾客等级
     *
     * @param id
     *            顾客等级主键
     * @return 顾客等级
     */
    @Override
    public PlayCustomLevelInfoEntity selectPlayCustomLevelInfoById(String id) {
        return this.baseMapper.selectById(id);
    }

    /**
     * 查询顾客等级列表
     *
     * @return 顾客等级
     */
    @Override
    public List<PlayCustomLevelInfoEntity> selectPlayCustomLevelInfoByPage() {
        LambdaQueryWrapper<PlayCustomLevelInfoEntity> lambdaQueryWrapper = new LambdaQueryWrapper<>();
        lambdaQueryWrapper.orderByAsc(PlayCustomLevelInfoEntity::getLevel);
        return this.baseMapper.selectList(lambdaQueryWrapper);
    }

    /**
     * 新增顾客等级
     *
     * @param playCustomLevelInfo
     *            顾客等级
     * @return 结果
     */
    @Override
    public boolean create(PlayCustomLevelInfoEntity playCustomLevelInfo) {
        if (StrUtil.isBlankIfStr(playCustomLevelInfo.getId())) {
            playCustomLevelInfo.setId(IdUtils.getUuid());
        }
        return save(playCustomLevelInfo);
    }

    /**
     * 修改顾客等级
     *
     * @param playCustomLevelInfo
     *            顾客等级
     * @return 结果
     */
    @Override
    public boolean update(PlayCustomLevelInfoEntity playCustomLevelInfo) {
        return updateById(playCustomLevelInfo);
    }

    /**
     * 批量删除顾客等级
     *
     * @param ids
     *            需要删除的顾客等级主键
     * @return 结果
     */
    @Override
    public int deletePlayCustomLevelInfoByIds(String[] ids) {
        return playCustomLevelInfoMapper.deleteBatchIds(Arrays.asList(ids));
    }

    /**
     * 删除顾客等级信息
     *
     * @param id
     *            顾客等级主键
     * @return 结果
     */
    @Override
    public int deletePlayCustomLevelInfoById(String id) {
        return playCustomLevelInfoMapper.deleteById(id);
    }

    @Override
    public int selectMaxLevel() {
        QueryWrapper<PlayCustomLevelInfoEntity> queryWrapper = new QueryWrapper<>();
        queryWrapper.select("max(level) as level ");
        PlayCustomLevelInfoEntity entity = this.baseMapper.selectOne(queryWrapper);
        return entity == null ? 0 : entity.getLevel();
    }

    @Override
    public void delMaxLevelByLevel(Integer level) {
        LambdaQueryWrapper<PlayCustomLevelInfoEntity> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(PlayCustomLevelInfoEntity::getLevel, level);
        this.baseMapper.delete(queryWrapper);
    }
}


--- File: play-admin/src/main/java/com/starry/admin/modules/custom/service/impl/PlayCustomUserInfoServiceImpl.java ---
package com.starry.admin.modules.custom.service.impl;

import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.github.yulichang.wrapper.MPJLambdaWrapper;
import com.starry.admin.common.exception.CustomException;
import com.starry.admin.modules.custom.mapper.PlayCustomUserInfoMapper;
import com.starry.admin.modules.custom.module.entity.PlayCustomLevelInfoEntity;
import com.starry.admin.modules.custom.module.entity.PlayCustomUserInfoEntity;
import com.starry.admin.modules.custom.module.vo.PlayCustomRankingQueryVo;
import com.starry.admin.modules.custom.module.vo.PlayCustomRankingReturnVo;
import com.starry.admin.modules.custom.module.vo.PlayCustomUserQueryVo;
import com.starry.admin.modules.custom.module.vo.PlayCustomUserReturnVo;
import com.starry.admin.modules.custom.service.IPlayCustomUserInfoService;
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.IPlayBalanceDetailsInfoService;
import com.starry.common.utils.IdUtils;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.*;
import java.util.stream.Collectors;
import javax.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;

/**
 * 顾客Service业务层处理
 *
 * @author admin
 * @since 2024-04-08
 */
@Slf4j
@Service
public class PlayCustomUserInfoServiceImpl extends ServiceImpl<PlayCustomUserInfoMapper, PlayCustomUserInfoEntity>
        implements
            IPlayCustomUserInfoService {
    @Resource
    private PlayCustomUserInfoMapper playCustomUserInfoMapper;

    @Resource
    private PlayOrderInfoServiceImpl playOrderInfoService;

    @Resource
    private IPlayBalanceDetailsInfoService playBalanceDetailsInfoService;

    @Override
    public PlayCustomUserInfoEntity selectByOpenid(String openId) {
        LambdaQueryWrapper<PlayCustomUserInfoEntity> lambdaQueryWrapper = new LambdaQueryWrapper<>();
        lambdaQueryWrapper.eq(PlayCustomUserInfoEntity::getOpenid, openId);
        return this.baseMapper.selectOne(lambdaQueryWrapper);
    }

    @Override
    public BigDecimal getCustomPaymentAmount(BigDecimal rechargeAmount, String customId) {
        // 根据顾客ID，查询顾客对应等级
        MPJLambdaWrapper<PlayCustomUserInfoEntity> lambdaQueryWrapper = new MPJLambdaWrapper<>();
        lambdaQueryWrapper.selectAll(PlayCustomLevelInfoEntity.class);
        lambdaQueryWrapper.leftJoin(PlayCustomLevelInfoEntity.class, PlayCustomLevelInfoEntity::getId,
                PlayCustomUserInfoEntity::getLevelId);
        lambdaQueryWrapper.eq(PlayCustomUserInfoEntity::getId, customId);
        PlayCustomLevelInfoEntity entity = this.baseMapper.selectJoinOne(PlayCustomLevelInfoEntity.class,
                lambdaQueryWrapper);
        if (entity == null) {
            log.error("未查询当前顾客的充值优惠金额，支付金额等于充值金额,rechargeAmount={},customId={}", rechargeAmount, customId);
            return rechargeAmount;
        }
        try {
            BigDecimal paymentAmount = BigDecimal.valueOf(entity.getDiscount())
                    .divide(new BigDecimal(100), 2, RoundingMode.HALF_UP).multiply(rechargeAmount);
            log.info("等级为{}的顾客{},充值{}元，实际应该支付{}元", entity.getName(), customId, rechargeAmount, paymentAmount);
            return paymentAmount;
        } catch (Exception e) {
            log.error("计算顾客充值优惠金额发生异常，支付金额等于充值金额,rechargeAmount={},customId={}", rechargeAmount, customId);
            return rechargeAmount;
        }
    }

    @Override
    public void customAccountBalanceRecharge(BigDecimal rechargeAmount, String customId, String orderId) {
        PlayCustomUserInfoEntity userInfo = this.selectById(customId);
        // 查询充值赠送金额（暂是设置为0）
        // 此处我想实现：超过50送5，超过100送10，200送20，300送30，400送40，500送50
        BigDecimal giftAmount = calculateGiftMoney(rechargeAmount);
        // 修改用户账户余额
        this.updateAccountBalanceById(customId, userInfo.getAccountBalance(),
                userInfo.getAccountBalance().add(rechargeAmount).add(giftAmount), "0", "充值",
                rechargeAmount.add(giftAmount), giftAmount, orderId);
    }

    private static BigDecimal calculateGiftMoney(BigDecimal rechargeAmount) {
        // 此处我想实现：超过50送5，超过100送10，200送20，300送30，400送40，500送50
        BigDecimal giftAmount = BigDecimal.ZERO;
        if (rechargeAmount.compareTo(new BigDecimal(50)) >= 0) {
            giftAmount = new BigDecimal(5);
        }
        if (rechargeAmount.compareTo(new BigDecimal(100)) >= 0) {
            giftAmount = new BigDecimal(10);
        }
        if (rechargeAmount.compareTo(new BigDecimal(200)) >= 0) {
            giftAmount = new BigDecimal(20);
        }
        if (rechargeAmount.compareTo(new BigDecimal(300)) >= 0) {
            giftAmount = new BigDecimal(30);
        }
        if (rechargeAmount.compareTo(new BigDecimal(400)) >= 0) {
            giftAmount = new BigDecimal(40);
        }
        if (rechargeAmount.compareTo(new BigDecimal(500)) >= 0) {
            giftAmount = new BigDecimal(50);
        }
        return giftAmount;
    }

    /**
     * 查询顾客
     *
     * @param id
     *            顾客主键
     * @return 顾客
     */
    @Override
    public PlayCustomUserInfoEntity selectById(String id) {
        PlayCustomUserInfoEntity entity = this.baseMapper.selectById(id);
        if (entity == null) {
            throw new CustomException("顾客不存在");
        }
        return entity;
    }

    @Override
    public IPage<PlayCustomRankingReturnVo> selectRankingByPage(PlayCustomRankingQueryVo vo) {
        MPJLambdaWrapper<PlayCustomUserInfoEntity> lambdaQueryWrapper = new MPJLambdaWrapper<>();
        lambdaQueryWrapper.selectCollection(PlayOrderInfoEntity.class, PlayCustomRankingReturnVo::getOrderInfos)
                .leftJoin(PlayOrderInfoEntity.class, PlayOrderInfoEntity::getPurchaserBy,
                        PlayCustomUserInfoEntity::getId);
        if (StrUtil.isNotBlank(vo.getId())) {
            lambdaQueryWrapper.eq(PlayCustomUserInfoEntity::getId, vo.getId());
        }
        if (vo.getPurchaserTime() != null && vo.getPurchaserTime().size() == 2) {
            lambdaQueryWrapper.between(PlayOrderInfoEntity::getPurchaserTime, vo.getPurchaserTime().get(0),
                    vo.getPurchaserTime().get(1));
        }

        IPage<PlayCustomRankingReturnVo> page = this.baseMapper.selectJoinPage(
                new Page<>(vo.getPageNum(), vo.getPageSize()), PlayCustomRankingReturnVo.class, lambdaQueryWrapper);
        int index = 1;
        for (PlayCustomRankingReturnVo record : page.getRecords()) {
            record.setRankingIndex((vo.getPageNum() - 1) * vo.getPageSize() + index);
            if (vo.getPurchaserTime() != null && vo.getPurchaserTime().size() == 2) {
                record.setBeginPurchaserTime(vo.getPurchaserTime().get(0));
                record.setEndPurchaserTime(vo.getPurchaserTime().get(1));
            }
            BigDecimal orderTotalAmount = new BigDecimal("0");
            int orderContinueNumber = 0;
            int orderNumber = 0;
            for (PlayOrderInfoEntity orderInfo : record.getOrderInfos()) {
                if (orderInfo.getId() == null) {
                    continue;
                }
                if ("0".equals(orderInfo.getFirstOrder())) {
                    orderContinueNumber++;
                }
                orderNumber++;
                if (!"4".equals(orderInfo.getOrderStatus())) {
                    orderTotalAmount = orderTotalAmount.add(orderInfo.getFinalAmount());
                }
            }
            record.setOrderTotalAmount(orderTotalAmount);
            record.setOrderNumber(String.valueOf(orderNumber));
            record.setOrderContinueNumber(String.valueOf(orderContinueNumber));
            if (orderNumber != 0) {
                record.setOrderPrice(
                        String.valueOf(orderTotalAmount.divide(new BigDecimal(orderNumber), 2, RoundingMode.HALF_UP)));
            } else {
                record.setOrderPrice("0");
            }
            index++;
        }
        // 按照订单顺序进行排序
        page.setRecords(page.getRecords().stream()
                .sorted(Comparator.comparing(PlayCustomRankingReturnVo::getOrderNumber).reversed())
                .collect(Collectors.toList()));
        return page;
    }

    @Override
    public IPage<PlayCustomUserReturnVo> selectByPage(PlayCustomUserQueryVo vo) {
        MPJLambdaWrapper<PlayCustomUserInfoEntity> lambdaQueryWrapper = new MPJLambdaWrapper<>();
        lambdaQueryWrapper.selectAll(PlayCustomUserInfoEntity.class);
        lambdaQueryWrapper.selectAs(PlayCustomLevelInfoEntity::getName, "levelName");
        lambdaQueryWrapper.leftJoin(PlayCustomLevelInfoEntity.class, PlayCustomLevelInfoEntity::getId,
                PlayCustomUserInfoEntity::getLevelId);
        if (StrUtil.isNotBlank(vo.getId())) {
            lambdaQueryWrapper.eq(PlayCustomUserInfoEntity::getId, vo.getId());
        }
        if (StrUtil.isNotBlank(vo.getNickname())) {
            lambdaQueryWrapper.like(PlayCustomUserInfoEntity::getNickname, vo.getNickname());
        }
        if (StrUtil.isNotBlank(vo.getRemark())) {
            lambdaQueryWrapper.like(PlayCustomUserInfoEntity::getRemark, vo.getRemark());
        }
        if (StrUtil.isNotBlank(vo.getWeiChatCode())) {
            lambdaQueryWrapper.like(PlayCustomUserInfoEntity::getWeiChatCode, vo.getWeiChatCode());
        }
        if (StrUtil.isNotBlank(vo.getPhone())) {
            lambdaQueryWrapper.eq(PlayCustomUserInfoEntity::getPhone, vo.getPhone());
        }
        if (StrUtil.isNotBlank(vo.getSex())) {
            lambdaQueryWrapper.eq(PlayCustomUserInfoEntity::getSex, vo.getSex());
        }
        if (StrUtil.isNotBlank(vo.getLevelId())) {
            lambdaQueryWrapper.eq(PlayCustomUserInfoEntity::getLevelId, vo.getLevelId());
        }
        if (StrUtil.isNotBlank(vo.getSubscribeState())) {
            lambdaQueryWrapper.eq(PlayCustomUserInfoEntity::getSubscribeState, vo.getSubscribeState());
        }
        if (StrUtil.isNotBlank(vo.getBlacklistState())) {
            lambdaQueryWrapper.eq(PlayCustomUserInfoEntity::getBlacklistState, vo.getBlacklistState());
        }
        if (StrUtil.isNotBlank(vo.getViolationState())) {
            lambdaQueryWrapper.eq(PlayCustomUserInfoEntity::getViolationState, vo.getViolationState());
        }
        if (StrUtil.isNotBlank(vo.getPurchaseState())) {
            lambdaQueryWrapper.eq(PlayCustomUserInfoEntity::getPurchaseState, vo.getPurchaseState());
        }
        if (StrUtil.isNotBlank(vo.getMobilePhoneState())) {
            lambdaQueryWrapper.eq(PlayCustomUserInfoEntity::getMobilePhoneState, vo.getMobilePhoneState());
        }
        if (vo.getRegistrationTime() != null && vo.getRegistrationTime().size() == 2) {
            lambdaQueryWrapper.between(PlayCustomUserInfoEntity::getRegistrationTime, vo.getRegistrationTime().get(0),
                    vo.getRegistrationTime().get(1));
        }
        if (vo.getFirstPurchaseTime() != null && vo.getFirstPurchaseTime().size() == 2) {
            lambdaQueryWrapper.between(PlayCustomUserInfoEntity::getFirstPurchaseTime, vo.getFirstPurchaseTime().get(0),
                    vo.getFirstPurchaseTime().get(1));
        }
        if (vo.getLastPurchaseTime() != null && vo.getLastPurchaseTime().size() == 2) {
            lambdaQueryWrapper.between(PlayCustomUserInfoEntity::getLastPurchaseTime, vo.getLastPurchaseTime().get(0),
                    vo.getLastPurchaseTime().get(1));
        }
        if (StrUtil.isNotBlank(vo.getSort())) {
            if ("levelId".equals(vo.getSort())) {
                lambdaQueryWrapper.orderByDesc(PlayCustomUserInfoEntity::getLevelId);
            }
            if ("accountBalance".equals(vo.getSort())) {
                lambdaQueryWrapper.orderByDesc(PlayCustomUserInfoEntity::getAccountBalance);
            }
            if ("lastPurchaseTime".equals(vo.getSort())) {
                lambdaQueryWrapper.orderByDesc(PlayCustomUserInfoEntity::getLastPurchaseTime);
            }
        }

        IPage<PlayCustomUserReturnVo> page = this.baseMapper.selectJoinPage(
                new Page<>(vo.getPageNum(), vo.getPageSize()), PlayCustomUserReturnVo.class, lambdaQueryWrapper);
        for (PlayCustomUserReturnVo record : page.getRecords()) {
            List<PlayOrderInfoEntity> orderInfoEntities = playOrderInfoService.list(Wrappers
                    .lambdaQuery(PlayOrderInfoEntity.class).eq(PlayOrderInfoEntity::getPurchaserBy, record.getId())
                    .in(PlayOrderInfoEntity::getPlaceType, "0", "2"));
            BigDecimal orderTotalAmount = new BigDecimal("0");
            for (PlayOrderInfoEntity orderInfoEntity : orderInfoEntities) {
                orderTotalAmount = orderTotalAmount.add(orderInfoEntity.getFinalAmount());
            }
            record.setOrderNumber(String.valueOf(orderInfoEntities.size()));
            record.setOrderTotalAmount(orderTotalAmount);
            if (!orderInfoEntities.isEmpty()) {
                record.setOrderPrice(String.valueOf(
                        orderTotalAmount.divide(new BigDecimal(orderInfoEntities.size()), 2, RoundingMode.HALF_UP)));
            } else {
                record.setProvince("0");
            }

        }
        return page;

    }

    /**
     * 新增顾客
     *
     * @param playCustomUserInfo
     *            顾客
     * @return 结果
     */
    @Override
    public boolean create(PlayCustomUserInfoEntity playCustomUserInfo) {
        if (StrUtil.isBlankIfStr(playCustomUserInfo.getId())) {
            playCustomUserInfo.setId(IdUtils.getUuid());
        }
        return save(playCustomUserInfo);
    }

    @Override
    public void updateTokenById(String id, String token) {
        PlayCustomUserInfoEntity entity = new PlayCustomUserInfoEntity();
        entity.setToken(token);
        entity.setId(id);
        this.baseMapper.updateById(entity);
    }

    @Override
    public void updateAccountBalanceById(String id, BigDecimal accountBalance) {
        // 更新余额
        this.baseMapper.updateById(new PlayCustomUserInfoEntity(id, accountBalance));
    }

    @Override
    public void updateAccountBalanceById(String userId, BigDecimal balanceBeforeOperation,
            BigDecimal balanceAfterOperation, String operationType, String operationAction, BigDecimal balanceMoney,
            BigDecimal giftAmount, String orderId) {
        // 更新余额
        PlayCustomUserInfoEntity entity = new PlayCustomUserInfoEntity();
        entity.setId(userId);
        entity.setAccountBalance(balanceAfterOperation);
        entity.setAccountState(balanceAfterOperation.compareTo(new BigDecimal(0)) > 0 ? "1" : "0");
        this.baseMapper.updateById(entity);
        // 记录余额变更记录
        playBalanceDetailsInfoService.insertBalanceDetailsInfo("1", userId, balanceBeforeOperation,
                balanceAfterOperation, operationType, operationAction, balanceMoney, giftAmount, orderId);
    }

    /**
     * 修改顾客
     *
     * @param playCustomUserInfo
     *            顾客
     * @return 结果
     */
    @Override
    public boolean update(PlayCustomUserInfoEntity playCustomUserInfo) {
        return updateById(playCustomUserInfo);
    }

    /**
     * 批量删除顾客
     *
     * @param ids
     *            需要删除的顾客主键
     * @return 结果
     */
    @Override
    public int deletePlayCustomUserInfoByIds(String[] ids) {
        return playCustomUserInfoMapper.deleteBatchIds(Arrays.asList(ids));
    }

    /**
     * 删除顾客信息
     *
     * @param id
     *            顾客主键
     * @return 结果
     */
    @Override
    public int deletePlayCustomUserInfoById(String id) {
        return playCustomUserInfoMapper.deleteById(id);
    }

    @Override
    public List<PlayCustomUserInfoEntity> simpleList() {
        LambdaQueryWrapper<PlayCustomUserInfoEntity> wrapper = Wrappers.lambdaQuery(PlayCustomUserInfoEntity.class);
        wrapper.orderByDesc(PlayCustomUserInfoEntity::getId).eq(PlayCustomUserInfoEntity::getDeleted, 0).select(
                PlayCustomUserInfoEntity::getAvatar, PlayCustomUserInfoEntity::getId,
                PlayCustomUserInfoEntity::getNickname, PlayCustomUserInfoEntity::getAvatar);
        return baseMapper.selectList(wrapper);
    }

    @Override
    public void saveOrderInfo(PlayOrderInfoEntity entity) {
        String id = entity.getPurchaserBy();
        LambdaUpdateWrapper<PlayCustomUserInfoEntity> wrapper = Wrappers.lambdaUpdate(PlayCustomUserInfoEntity.class)
                .eq(PlayCustomUserInfoEntity::getId, id).set(PlayCustomUserInfoEntity::getLastPurchaseTime, new Date());
        PlayCustomUserInfoEntity userInfoEntity = selectById(id);
        if (Objects.isNull(userInfoEntity.getFirstPurchaseTime())) {
            wrapper.set(PlayCustomUserInfoEntity::getFirstPurchaseTime, new Date());
        }
        wrapper.set(PlayCustomUserInfoEntity::getWeiChatCode, entity.getWeiChatCode());
        this.update(wrapper);
    }
}


--- File: play-admin/src/main/java/com/starry/admin/modules/order/controller/PlayOrderComplaintInfoController.java ---
package com.starry.admin.modules.order.controller;

import com.baomidou.mybatisplus.core.metadata.IPage;
import com.starry.admin.modules.order.module.entity.PlayOrderComplaintInfoEntity;
import com.starry.admin.modules.order.module.vo.PlayOrderComplaintQueryVo;
import com.starry.admin.modules.order.module.vo.PlayOrderComplaintReturnVo;
import com.starry.admin.modules.order.service.IPlayOrderComplaintInfoService;
import com.starry.common.annotation.Log;
import com.starry.common.enums.BusinessType;
import com.starry.common.result.R;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiParam;
import io.swagger.annotations.ApiResponse;
import io.swagger.annotations.ApiResponses;
import javax.annotation.Resource;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;

/**
 * 订单投诉信息Controller
 *
 * @author admin
 * @since 2024-05-07
 */
@Api(tags = "订单投诉管理", description = "订单投诉信息管理相关接口，包括查询、新增、修改和删除等操作")
@RestController
@RequestMapping("/order/complaint")
public class PlayOrderComplaintInfoController {
    @Resource
    private IPlayOrderComplaintInfoService playOrderComplaintInfoService;

    /**
     * 查询订单投诉信息列表
     */
    @ApiOperation(value = "分页查询投诉列表", notes = "分页查询订单投诉信息列表")
    @ApiResponses({
            @ApiResponse(code = 200, message = "操作成功", response = PlayOrderComplaintReturnVo.class, responseContainer = "Page")})
    @PostMapping("/listByPage")
    public R list(@ApiParam(value = "查询条件", required = true) @Validated @RequestBody PlayOrderComplaintQueryVo vo) {
        IPage<PlayOrderComplaintReturnVo> list = playOrderComplaintInfoService.selectByPage(vo);
        return R.ok(list);
    }

    /**
     * 获取订单投诉信息详细信息
     */
    @ApiOperation(value = "获取投诉详情", notes = "根据ID获取订单投诉详细信息")
    @ApiImplicitParam(name = "id", value = "投诉记录ID", required = true, paramType = "path", dataType = "String", example = "1")
    @ApiResponses({@ApiResponse(code = 200, message = "操作成功", response = PlayOrderComplaintInfoEntity.class)})
    @GetMapping(value = "/{id}")
    public R getInfo(@PathVariable("id") String id) {
        return R.ok(playOrderComplaintInfoService.selectPlayOrderComplaintInfoById(id));
    }

    /**
     * 新增订单投诉信息
     */
    @ApiOperation(value = "新增投诉记录", notes = "创建新的订单投诉信息")
    @ApiResponses({@ApiResponse(code = 200, message = "操作成功"), @ApiResponse(code = 500, message = "添加失败")})
    // @PreAuthorize("@customSs.hasPermission('order:complaint:add')")
    @Log(title = "订单投诉信息", businessType = BusinessType.INSERT)
    @PostMapping("/create")
    public R create(
            @ApiParam(value = "投诉信息", required = true) @RequestBody PlayOrderComplaintInfoEntity playOrderComplaintInfo) {
        boolean success = playOrderComplaintInfoService.create(playOrderComplaintInfo);
        if (success) {
            return R.ok();
        }
        return R.error("添加失败");
    }

    /**
     * 修改订单投诉信息
     */
    @ApiOperation(value = "修改投诉记录", notes = "根据ID修改订单投诉信息")
    @ApiImplicitParam(name = "id", value = "投诉记录ID", required = true, paramType = "path", dataType = "String", example = "1")
    @ApiResponses({@ApiResponse(code = 200, message = "操作成功"), @ApiResponse(code = 500, message = "修改失败")})
    // @PreAuthorize("@customSs.hasPermission('order:complaint:update')")
    @Log(title = "订单投诉信息", businessType = BusinessType.UPDATE)
    @PostMapping(value = "/update/{id}")
    public R update(@PathVariable String id,
            @ApiParam(value = "投诉信息", required = true) @RequestBody PlayOrderComplaintInfoEntity playOrderComplaintInfo) {
        playOrderComplaintInfo.setId(id);
        boolean success = playOrderComplaintInfoService.update(playOrderComplaintInfo);
        if (success) {
            return R.ok();
        }
        return R.error("修改失败");
    }

    /**
     * 删除订单投诉信息
     */
    @ApiOperation(value = "删除投诉记录", notes = "根据ID批量删除订单投诉信息")
    @ApiImplicitParam(name = "ids", value = "投诉记录ID数组", required = true, paramType = "path", dataType = "String[]", example = "1,2,3")
    @ApiResponses({@ApiResponse(code = 200, message = "操作成功", response = Integer.class)})
    // @PreAuthorize("@customSs.hasPermission('order:complaint:delete')")
    @Log(title = "订单投诉信息", businessType = BusinessType.DELETE)
    @DeleteMapping("/{ids}")
    public R remove(@PathVariable String[] ids) {
        return R.ok(playOrderComplaintInfoService.deletePlayOrderComplaintInfoByIds(ids));
    }
}


--- File: play-admin/src/main/java/com/starry/admin/modules/order/controller/PlayOrderContinueInfoController.java ---
package com.starry.admin.modules.order.controller;

import com.baomidou.mybatisplus.core.metadata.IPage;
import com.starry.admin.modules.order.module.vo.PlayOrderContinueQueryVo;
import com.starry.admin.modules.order.module.vo.PlayOrderContinueReturnVo;
import com.starry.admin.modules.order.module.vo.PlayOrderReviewStateEditVo;
import com.starry.admin.modules.order.service.IPlayOrderContinueInfoService;
import com.starry.common.result.R;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiParam;
import io.swagger.annotations.ApiResponse;
import io.swagger.annotations.ApiResponses;
import javax.annotation.Resource;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 * 订单续单信息Controller
 *
 * @author admin
 * @since 2024-05-10
 */
@Api(tags = "订单续单管理", description = "订单续单信息管理相关接口，包括查询和审核等操作")
@RestController
@RequestMapping("/order/continue")
public class PlayOrderContinueInfoController {
    @Resource
    private IPlayOrderContinueInfoService playOrderContinueInfoService;

    /**
     * 查询订单续单信息列表
     */
    @ApiOperation(value = "分页查询续单列表", notes = "分页查询订单续单信息列表")
    @ApiResponses({
            @ApiResponse(code = 200, message = "操作成功", response = PlayOrderContinueReturnVo.class, responseContainer = "Page")})
    @PostMapping("/listByPage")
    public R list(@ApiParam(value = "查询条件", required = true) @Validated @RequestBody PlayOrderContinueQueryVo vo) {
        IPage<PlayOrderContinueReturnVo> list = playOrderContinueInfoService.selectPlayByPage(vo);
        return R.ok(list);
    }

    /**
     * 审批续单申申请
     */
    @ApiOperation(value = "审批续单申请", notes = "审核店员的续单申请")
    @ApiResponses({@ApiResponse(code = 200, message = "审批成功"), @ApiResponse(code = 500, message = "审批失败")})
    // @PreAuthorize("@customSs.hasPermission('order:continue:update')")
    @PostMapping("/updateReviewState")
    public R updateReviewState(
            @ApiParam(value = "审批信息", required = true) @Validated @RequestBody PlayOrderReviewStateEditVo vo) {
        playOrderContinueInfoService.updateReviewState(vo);
        return R.ok("成功");
    }
}


--- File: play-admin/src/main/java/com/starry/admin/modules/order/controller/PlayOrderEvaluateInfoController.java ---
package com.starry.admin.modules.order.controller;

import com.baomidou.mybatisplus.core.metadata.IPage;
import com.starry.admin.modules.order.module.entity.PlayOrderEvaluateInfoEntity;
import com.starry.admin.modules.order.module.vo.PlayOrderEvaluateEditStateVo;
import com.starry.admin.modules.order.module.vo.PlayOrderEvaluateQueryVo;
import com.starry.admin.modules.order.module.vo.PlayOrderEvaluateReturnVo;
import com.starry.admin.modules.order.service.IPlayOrderEvaluateInfoService;
import com.starry.common.annotation.Log;
import com.starry.common.enums.BusinessType;
import com.starry.common.result.R;
import com.starry.common.utils.ConvertUtil;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiParam;
import io.swagger.annotations.ApiResponse;
import io.swagger.annotations.ApiResponses;
import javax.annotation.Resource;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;

/**
 * 订单评价信息Controller
 *
 * @author admin
 * @since 2024-05-07
 */
@Api(tags = "订单评价管理", description = "订单评价信息管理相关接口，包括查询、修改和删除等操作")
@RestController
@RequestMapping("/order/evaluate")
public class PlayOrderEvaluateInfoController {
    @Resource
    private IPlayOrderEvaluateInfoService playOrderEvaluateInfoService;

    /**
     * 查询订单评价信息列表
     */
    @ApiOperation(value = "分页查询评价列表", notes = "分页查询订单评价信息列表")
    @ApiResponses({
            @ApiResponse(code = 200, message = "操作成功", response = PlayOrderEvaluateReturnVo.class, responseContainer = "Page")})
    @PostMapping("/listByPage")
    public R list(@ApiParam(value = "查询条件", required = true) @Validated @RequestBody PlayOrderEvaluateQueryVo vo) {
        IPage<PlayOrderEvaluateReturnVo> list = playOrderEvaluateInfoService.selectByPage(vo);
        return R.ok(list);
    }

    /**
     * 修改订单评价信息
     */
    @ApiOperation(value = "修改评价状态", notes = "修改订单评价的显示状态")
    @ApiResponses({@ApiResponse(code = 200, message = "操作成功"), @ApiResponse(code = 500, message = "修改失败")})
    // @PreAuthorize("@customSs.hasPermission('order:evaluate:update')")
    @Log(title = "修改订单评价状态", businessType = BusinessType.UPDATE)
    @PostMapping(value = "/updateStatus")
    public R update(
            @ApiParam(value = "评价状态信息", required = true) @Validated @RequestBody PlayOrderEvaluateEditStateVo vo) {
        PlayOrderEvaluateInfoEntity entity = ConvertUtil.entityToVo(vo, PlayOrderEvaluateInfoEntity.class);
        boolean success = playOrderEvaluateInfoService.update(entity);
        if (success) {
            return R.ok();
        }
        return R.error("修改失败");
    }

    /**
     * 删除订单评价信息
     */
    @ApiOperation(value = "删除评价记录", notes = "根据ID批量删除订单评价信息")
    @ApiImplicitParam(name = "ids", value = "评价记录ID数组", required = true, paramType = "path", dataType = "String[]", example = "1,2,3")
    @ApiResponses({@ApiResponse(code = 200, message = "操作成功", response = Integer.class)})
    // @PreAuthorize("@customSs.hasPermission('order:evaluate:delete')")
    @Log(title = "订单评价信息", businessType = BusinessType.DELETE)
    @DeleteMapping("/{ids}")
    public R remove(@PathVariable String[] ids) {
        return R.ok(playOrderEvaluateInfoService.deletePlayOrderEvaluateInfoByIds(ids));
    }
}


--- File: play-admin/src/main/java/com/starry/admin/modules/order/controller/PlayOrderInfoController.java ---
package com.starry.admin.modules.order.controller;

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.custom.module.entity.PlayCustomUserInfoEntity;
import com.starry.admin.modules.custom.service.IPlayCustomUserInfoService;
import com.starry.admin.modules.order.module.entity.PlayOrderInfoEntity;
import com.starry.admin.modules.order.module.vo.*;
import com.starry.admin.modules.order.service.IPlayOrderInfoService;
import com.starry.admin.modules.order.service.IPlayOrderRefundInfoService;
import com.starry.admin.modules.shop.module.vo.PlayCommodityInfoVo;
import com.starry.admin.modules.shop.service.IPlayCommodityInfoService;
import com.starry.admin.modules.weichat.service.WxCustomMpService;
import com.starry.admin.utils.SecurityUtils;
import com.starry.common.annotation.Log;
import com.starry.common.context.CustomSecurityContextHolder;
import com.starry.common.enums.BusinessType;
import com.starry.common.result.R;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiParam;
import io.swagger.annotations.ApiResponse;
import io.swagger.annotations.ApiResponses;
import java.math.BigDecimal;
import javax.annotation.Resource;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;

/**
 * 订单管理
 *
 * @author admin
 * @since 2024-03-20
 */
@Api(tags = "订单管理", description = "订单信息管理相关接口，包括查询、修改、退款和删除等操作")
@RestController
@RequestMapping("/order/order/")
public class PlayOrderInfoController {
    @Resource
    private IPlayOrderInfoService orderInfoService;

    @Resource
    private WxCustomMpService wxCustomMpService;

    @Resource
    private IPlayClerkUserInfoService playClerkUserInfoService;

    @Resource
    private IPlayCommodityInfoService playCommodityInfoService;

    @Resource
    private IPlayOrderRefundInfoService playOrderRefundInfoService;
    @Resource
    private IPlayCustomUserInfoService customUserInfoService;

    /**
     * 分页查询订单列表
     */
    @ApiOperation(value = "分页查询订单列表", notes = "分页查询订单信息列表")
    @ApiResponses({
            @ApiResponse(code = 200, message = "操作成功", response = PlayOrderInfoEntity.class, responseContainer = "Page")})
    @PostMapping("/listByPage")
    public R listByPage(@ApiParam(value = "查询条件", required = true) @Validated @RequestBody PlayOrderInfoQueryVo vo) {
        return R.ok(orderInfoService.selectOrderInfoPage(vo));
    }

    /**
     * 订单退款
     */
    @ApiOperation(value = "订单退款", notes = "处理订单退款操作")
    @ApiResponses({@ApiResponse(code = 200, message = "退款成功"), @ApiResponse(code = 500, message = "退款失败，包含详细错误信息")})
    // @PreAuthorize("@customSs.hasPermission('order:order:update')")
    @PostMapping("/orderRefund")
    public R orderRefund(@ApiParam(value = "退款信息", required = true) @Validated @RequestBody PlayOrderRefundAddVo vo) {
        PlayOrderInfoEntity orderInfo = orderInfoService.selectOrderInfoById(vo.getOrderId());
        if (orderInfo.getFinalAmount().compareTo(vo.getRefundAmount()) < 0) {
            throw new CustomException("退款金额不能大于支付金额");
        }
        if ("3".equals(orderInfo.getOrderStatus())) {
            throw new CustomException("【已完成】的订单无法操作退款");
        }
        if ("4".equals(orderInfo.getOrderStatus())) {
            throw new CustomException("【已取消】的订单无法操作退款");
        }
        if ("1".equals(orderInfo.getRefundType())) {
            throw new CustomException("每个订单只能退款一次~");
        }
        PlayOrderInfoEntity updateOrderInfo = new PlayOrderInfoEntity();
        updateOrderInfo.setId(orderInfo.getId());
        updateOrderInfo.setRefundType("1");
        // 订单退款，订单状态变为已取消
        updateOrderInfo.setOrderStatus("4");
        updateOrderInfo.setRefundAmount(vo.getRefundAmount());
        // 修改订单状态
        orderInfoService.update(updateOrderInfo);
        // 记录退款信息
        String refundType = orderInfo.getFinalAmount().compareTo(vo.getRefundAmount()) == 0 ? "0" : "1";

        PlayCustomUserInfoEntity customUserInfo = customUserInfoService.getById(orderInfo.getPurchaserBy());
        customUserInfoService.updateAccountBalanceById(customUserInfo.getId(), customUserInfo.getAccountBalance(),
                customUserInfo.getAccountBalance().add(orderInfo.getOrderMoney()), "3", "订单退款",
                orderInfo.getOrderMoney(), BigDecimal.ZERO, vo.getOrderId());

        playOrderRefundInfoService.add(orderInfo.getId(), orderInfo.getPurchaserBy(), orderInfo.getAcceptBy(),
                orderInfo.getPayMethod(), refundType, vo.getRefundAmount(), vo.getRefundReason(), "2",
                SecurityUtils.getUserId(), "0", "0");
        return R.ok("退款成功");
    }

    /**
     * 更换店员
     */
    @ApiOperation(value = "更换店员", notes = "修改订单的接单店员")
    @ApiResponses({@ApiResponse(code = 200, message = "操作成功"), @ApiResponse(code = 500, message = "操作失败")})
    // @PreAuthorize("@customSs.hasPermission('order:order:update')")
    @PostMapping("/orderEditAcceptBy")
    public R orderEditAcceptBy(
            @ApiParam(value = "更换店员信息", required = true) @Validated @RequestBody PlayOrderEditAcceptByVo vo) {
        orderInfoService.updateStateTo1("2", CustomSecurityContextHolder.getUserId(), vo.getAcceptBy(),
                vo.getOrderId());
        PlayClerkUserInfoEntity clerkUserInfo = playClerkUserInfoService.selectById(vo.getAcceptBy());
        PlayOrderInfoEntity orderInfo = orderInfoService.selectOrderInfoById(vo.getOrderId());
        PlayCommodityInfoVo commodityInfo = playCommodityInfoService.queryCommodityInfo(orderInfo.getCommodityId(),
                clerkUserInfo.getLevelId());
        wxCustomMpService.sendCreateOrderMessage(clerkUserInfo.getTenantId(), clerkUserInfo.getOpenid(),
                orderInfo.getOrderNo(), orderInfo.getOrderMoney().toString(), commodityInfo.getCommodityName());
        return R.ok("操作成功");
    }

    /**
     * 后台备注
     */
    @ApiOperation(value = "修改后台备注", notes = "修改订单的后台备注信息")
    @ApiResponses({@ApiResponse(code = 200, message = "操作成功", response = Boolean.class),
            @ApiResponse(code = 500, message = "操作失败")})
    // @PreAuthorize("@customSs.hasPermission('order:order:update')")
    @PostMapping("/orderEditBackendRemark")
    public R orderEditBackendRemark(
            @ApiParam(value = "后台备注信息", required = true) @Validated @RequestBody PlayOrderEditBackendRemarkByVo vo) {
        PlayOrderInfoEntity orderInfo = orderInfoService.selectOrderInfoById(vo.getOrderId());
        PlayOrderInfoEntity updateOrderInfo = new PlayOrderInfoEntity();
        updateOrderInfo.setId(orderInfo.getId());
        updateOrderInfo.setBackendRemark(vo.getBackendRemark());
        return R.ok(orderInfoService.update(updateOrderInfo));
    }

    /**
     * 重新抢单
     */
    @ApiOperation(value = "重新抢单", notes = "将订单重新设置为抢单状态")
    @ApiResponses({@ApiResponse(code = 200, message = "操作成功", response = Boolean.class),
            @ApiResponse(code = 500, message = "操作失败，包含详细错误信息")})
    // @PreAuthorize("@customSs.hasPermission('order:order:update')")
    @PostMapping("/orderEditRandomSingle")
    public R orderEditRandomSingle(
            @ApiParam(value = "重新抢单信息", required = true) @Validated @RequestBody PlayOrderEditRandomSingleVo vo) {
        PlayOrderInfoEntity orderInfo = orderInfoService.selectOrderInfoById(vo.getOrderId());
        if (!"1".equals(orderInfo.getPlaceType())) {
            throw new CustomException("订单类型错误，无法重新抢单");
        }
        PlayOrderInfoEntity updateOrderInfo = new PlayOrderInfoEntity();
        updateOrderInfo.setSex(vo.getSex());
        updateOrderInfo.setLevelId(vo.getLevelId());
        updateOrderInfo.setExcludeHistory(vo.getExcludeHistory());
        updateOrderInfo.setRemark(vo.getRemark());
        updateOrderInfo.setId(vo.getOrderId());
        updateOrderInfo.setOrderStatus("0");
        updateOrderInfo.setAcceptBy("");
        updateOrderInfo.setEstimatedRevenueRatio(0);
        updateOrderInfo.setEstimatedRevenue(BigDecimal.ZERO);
        updateOrderInfo.setRemark(vo.getRemark());
        return R.ok(orderInfoService.update(updateOrderInfo));
    }

    /**
     * 根据ID查询订单
     */
    @ApiOperation(value = "获取订单详情", notes = "根据ID获取订单详细信息")
    @ApiImplicitParam(name = "id", value = "订单ID", required = true, paramType = "path", dataType = "String", example = "1")
    @ApiResponses({@ApiResponse(code = 200, message = "操作成功", response = PlayOrderInfoEntity.class)})
    @GetMapping(value = "/{id}")
    public R getInfo(@PathVariable("id") String id) {
        return R.ok(orderInfoService.selectById(id));
    }

    /**
     * 根据ID修改订单信息
     */
    @ApiOperation(value = "修改订单信息", notes = "根据ID修改订单基本信息")
    @ApiImplicitParam(name = "id", value = "订单ID", required = true, paramType = "path", dataType = "String", example = "1")
    @ApiResponses({@ApiResponse(code = 200, message = "操作成功"), @ApiResponse(code = 500, message = "修改失败")})
    // @PreAuthorize("@customSs.hasPermission('order:order:update')")
    @Log(title = "修改普通订单", businessType = BusinessType.UPDATE)
    @PostMapping(value = "/update/{id}")
    public R update(@PathVariable String id,
            @ApiParam(value = "订单信息", required = true) @RequestBody PlayOrderInfoEntity orderInfoEntity) {
        orderInfoEntity.setId(id);
        boolean success = orderInfoService.update(orderInfoEntity);
        if (success) {
            return R.ok();
        }
        return R.error("修改失败");
    }

    /**
     * 删除订单
     */
    @ApiOperation(value = "删除订单", notes = "根据ID批量删除订单信息")
    @ApiImplicitParam(name = "ids", value = "订单ID数组", required = true, paramType = "path", dataType = "String[]", example = "1,2,3")
    @ApiResponses({@ApiResponse(code = 200, message = "操作成功", response = Integer.class)})
    // @PreAuthorize("@customSs.hasPermission('order:order:delete')")
    @Log(title = "删除订单", businessType = BusinessType.DELETE)
    @DeleteMapping("/{ids}")
    public R remove(@PathVariable String[] ids) {
        return R.ok(orderInfoService.deleteOrderInfoByIds(ids));
    }
}


--- File: play-admin/src/main/java/com/starry/admin/modules/order/controller/PlayOrderRefundInfoController.java ---
package com.starry.admin.modules.order.controller;

import com.baomidou.mybatisplus.core.metadata.IPage;
import com.starry.admin.modules.order.module.entity.PlayOrderRefundInfoEntity;
import com.starry.admin.modules.order.module.vo.PlayOrderRefundQueryVo;
import com.starry.admin.modules.order.module.vo.PlayOrderRefundReturnVo;
import com.starry.admin.modules.order.service.IPlayOrderRefundInfoService;
import com.starry.common.annotation.Log;
import com.starry.common.enums.BusinessType;
import com.starry.common.result.R;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiParam;
import io.swagger.annotations.ApiResponse;
import io.swagger.annotations.ApiResponses;
import javax.annotation.Resource;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;

/**
 * 订单退款信息Controller
 *
 * @author admin
 * @since 2024-05-09
 */
@Api(tags = "订单退款管理", description = "订单退款信息管理相关接口，包括查询、新增、修改和删除等操作")
@RestController
@RequestMapping("/order/refund")
public class PlayOrderRefundInfoController {
    @Resource
    private IPlayOrderRefundInfoService playOrderRefundInfoService;

    /**
     * 查询订单退款信息列表
     */
    @ApiOperation(value = "分页查询退款列表", notes = "分页查询订单退款信息列表")
    @ApiResponses({
            @ApiResponse(code = 200, message = "操作成功", response = PlayOrderRefundReturnVo.class, responseContainer = "Page")})
    @PostMapping("/list")
    public R list(@ApiParam(value = "查询条件", required = true) @Validated @RequestBody PlayOrderRefundQueryVo vo) {
        IPage<PlayOrderRefundReturnVo> list = playOrderRefundInfoService.selectByPage(vo);
        return R.ok(list);
    }

    /**
     * 获取订单退款信息详细信息
     */
    @ApiOperation(value = "获取退款详情", notes = "根据ID获取订单退款详细信息")
    @ApiImplicitParam(name = "id", value = "退款记录ID", required = true, paramType = "path", dataType = "String", example = "1")
    @ApiResponses({@ApiResponse(code = 200, message = "操作成功", response = PlayOrderRefundInfoEntity.class)})
    @GetMapping(value = "/{id}")
    public R getInfo(@PathVariable("id") String id) {
        return R.ok(playOrderRefundInfoService.selectPlayOrderRefundInfoById(id));
    }

    /**
     * 新增订单退款信息
     */
    @ApiOperation(value = "新增退款记录", notes = "创建新的订单退款信息")
    @ApiResponses({@ApiResponse(code = 200, message = "操作成功"), @ApiResponse(code = 500, message = "添加失败")})
    @Log(title = "订单退款信息", businessType = BusinessType.INSERT)
    @PostMapping("/create")
    public R create(
            @ApiParam(value = "退款信息", required = true) @RequestBody PlayOrderRefundInfoEntity playOrderRefundInfo) {
        boolean success = playOrderRefundInfoService.create(playOrderRefundInfo);
        if (success) {
            return R.ok();
        }
        return R.error("添加失败");
    }

    /**
     * 修改订单退款信息
     */
    @ApiOperation(value = "修改退款记录", notes = "根据ID修改订单退款信息")
    @ApiImplicitParam(name = "id", value = "退款记录ID", required = true, paramType = "path", dataType = "String", example = "1")
    @ApiResponses({@ApiResponse(code = 200, message = "操作成功"), @ApiResponse(code = 500, message = "修改失败")})
    @Log(title = "订单退款信息", businessType = BusinessType.UPDATE)
    @PostMapping(value = "/update/{id}")
    public R update(@PathVariable String id,
            @ApiParam(value = "退款信息", required = true) @RequestBody PlayOrderRefundInfoEntity playOrderRefundInfo) {
        playOrderRefundInfo.setId(id);
        boolean success = playOrderRefundInfoService.update(playOrderRefundInfo);
        if (success) {
            return R.ok();
        }
        return R.error("修改失败");
    }

    /**
     * 删除订单退款信息
     */
    @ApiOperation(value = "删除退款记录", notes = "根据ID批量删除订单退款信息")
    @ApiImplicitParam(name = "ids", value = "退款记录ID数组", required = true, paramType = "path", dataType = "String[]", example = "1,2,3")
    @ApiResponses({@ApiResponse(code = 200, message = "操作成功", response = Integer.class)})
    @Log(title = "订单退款信息", businessType = BusinessType.DELETE)
    @DeleteMapping("/{ids}")
    public R remove(@PathVariable String[] ids) {
        return R.ok(playOrderRefundInfoService.deletePlayOrderRefundInfoByIds(ids));
    }
}


--- File: play-admin/src/main/java/com/starry/admin/modules/order/controller/PlayOrderRewardInfoController.java ---
package com.starry.admin.modules.order.controller;

import com.baomidou.mybatisplus.core.metadata.IPage;
import com.starry.admin.modules.order.module.vo.PlayOrderRewardQueryVo;
import com.starry.admin.modules.order.module.vo.PlayOrderRewardReturnVo;
import com.starry.admin.modules.order.service.IPlayOrderInfoService;
import com.starry.common.annotation.Log;
import com.starry.common.enums.BusinessType;
import com.starry.common.result.R;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiParam;
import io.swagger.annotations.ApiResponse;
import io.swagger.annotations.ApiResponses;
import javax.annotation.Resource;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 * 打赏订单
 *
 * @author admin
 * @since 2024-03-20
 */
@Api(tags = "打赏订单管理", description = "打赏订单信息管理相关接口，包括查询等操作")
@RestController
@RequestMapping("/order/reward/")
public class PlayOrderRewardInfoController {

    @Resource
    private IPlayOrderInfoService orderInfoService;

    /**
     * 分页查询打赏订单列表
     */
    @ApiOperation(value = "分页查询打赏订单", notes = "分页查询打赏订单信息列表")
    @ApiResponses({
            @ApiResponse(code = 200, message = "操作成功", response = PlayOrderRewardReturnVo.class, responseContainer = "Page")})
    // @PreAuthorize("@customSs.hasPermission('order:reward:list')")
    @Log(title = "查询打赏订单", businessType = BusinessType.INSERT)
    @PostMapping("/listByPage")
    public R queryRewardOrder(
            @ApiParam(value = "查询条件", required = true) @Validated @RequestBody PlayOrderRewardQueryVo vo) {
        IPage<PlayOrderRewardReturnVo> page = orderInfoService.selectRewardOrderInfoByPage(vo);
        return R.ok(page);
    }

}


--- File: play-admin/src/main/java/com/starry/admin/modules/order/mapper/PlayOrderComplaintInfoMapper.java ---
package com.starry.admin.modules.order.mapper;

import com.github.yulichang.base.MPJBaseMapper;
import com.starry.admin.modules.order.module.entity.PlayOrderComplaintInfoEntity;

/**
 * 订单投诉信息Mapper接口
 *
 * @author admin
 * @since 2024-05-07
 */
public interface PlayOrderComplaintInfoMapper extends MPJBaseMapper<PlayOrderComplaintInfoEntity> {

}


--- File: play-admin/src/main/java/com/starry/admin/modules/order/mapper/PlayOrderContinueInfoMapper.java ---
package com.starry.admin.modules.order.mapper;

import com.github.yulichang.base.MPJBaseMapper;
import com.starry.admin.modules.order.module.entity.PlayOrderContinueInfoEntity;

/**
 * 订单续单信息Mapper接口
 *
 * @author admin
 * @since 2024-05-10
 */
public interface PlayOrderContinueInfoMapper extends MPJBaseMapper<PlayOrderContinueInfoEntity> {

}


--- File: play-admin/src/main/java/com/starry/admin/modules/order/mapper/PlayOrderEvaluateInfoMapper.java ---
package com.starry.admin.modules.order.mapper;

import com.github.yulichang.base.MPJBaseMapper;
import com.starry.admin.modules.order.module.entity.PlayOrderEvaluateInfoEntity;

/**
 * 订单评价信息Mapper接口
 *
 * @author admin
 * @since 2024-05-07
 */
public interface PlayOrderEvaluateInfoMapper extends MPJBaseMapper<PlayOrderEvaluateInfoEntity> {

}


--- File: play-admin/src/main/java/com/starry/admin/modules/order/mapper/PlayOrderInfoMapper.java ---
package com.starry.admin.modules.order.mapper;

import com.github.yulichang.base.MPJBaseMapper;
import com.starry.admin.modules.order.module.entity.PlayOrderInfoEntity;

/**
 * 订单Mapper接口
 *
 * @author admin
 * @since 2024-03-20
 */
public interface PlayOrderInfoMapper extends MPJBaseMapper<PlayOrderInfoEntity> {

}


--- File: play-admin/src/main/java/com/starry/admin/modules/order/mapper/PlayOrderRefundInfoMapper.java ---
package com.starry.admin.modules.order.mapper;

import com.github.yulichang.base.MPJBaseMapper;
import com.starry.admin.modules.order.module.entity.PlayOrderRefundInfoEntity;

/**
 * 订单退款信息Mapper接口
 *
 * @author admin
 * @since 2024-05-09
 */
public interface PlayOrderRefundInfoMapper extends MPJBaseMapper<PlayOrderRefundInfoEntity> {

}


--- File: play-admin/src/main/java/com/starry/admin/modules/order/module/constant/OrderConstant.java ---
package com.starry.admin.modules.order.module.constant;

/**
 * @author admin
 * @since 2024/5/8 15:41
 **/
public class OrderConstant {

    /**
     * 订单状态-待接单
     *
     * @since 2024/5/8 15:42
     **/
    public final static String ORDER_STATUS_0 = "0";

    /**
     * 订单状态-待开始
     *
     * @since 2024/5/8 15:42
     **/
    public final static String ORDER_STATUS_1 = "1";

    /**
     * 订单状态-服务中
     *
     * @since 2024/5/8 15:42
     **/
    public final static String ORDER_STATUS_2 = "2";

    /**
     * 订单状态-已完成
     *
     * @since 2024/5/8 15:42
     **/
    public final static String ORDER_STATUS_3 = "3";

    /**
     * 订单状态-已取消
     *
     * @since 2024/5/8 15:42
     **/
    public final static String ORDER_STATUS_4 = "4";

}


--- File: play-admin/src/main/java/com/starry/admin/modules/order/module/entity/PlayOrderComplaintInfoEntity.java ---
package com.starry.admin.modules.order.module.entity;

import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableName;
import com.starry.admin.common.conf.StringTypeHandler;
import com.starry.common.domain.BaseEntity;
import java.util.Date;
import java.util.List;
import lombok.Data;
import lombok.EqualsAndHashCode;

/**
 * 订单投诉信息对象 play_order_complaint_info
 *
 * @author admin
 * @since 2024-05-07
 */
@Data
@EqualsAndHashCode(callSuper = false)
@TableName(value = "play_order_complaint_info", autoResultMap = true)
public class PlayOrderComplaintInfoEntity extends BaseEntity<PlayOrderComplaintInfoEntity> {

    /**
     * uuid
     */
    private String id;

    /**
     * 租户ID
     */
    private String tenantId;

    /**
     * 订单ID
     */
    private String orderId;

    /**
     * 顾客ID
     */
    private String customId;

    /**
     * 店员ID
     */
    private String clerkId;

    /**
     * 商品ID
     */
    private String commodityId;

    /**
     * 投诉人微信号码
     */
    private String wxChatCode;

    /**
     * 投诉内容
     */
    private String complaintCon;

    /**
     * 投诉时间
     */
    private Date complaintTime;

    /**
     * 图片列表
     */
    @TableField(typeHandler = StringTypeHandler.class)
    private List<String> images;

    /**
     * 数据是否隐藏（0:显示，1:隐藏）
     */
    private String hidden;

}


--- File: play-admin/src/main/java/com/starry/admin/modules/order/module/entity/PlayOrderContinueInfoEntity.java ---
package com.starry.admin.modules.order.module.entity;

import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableName;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.starry.admin.common.conf.StringTypeHandler;
import com.starry.common.domain.BaseEntity;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.List;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.springframework.format.annotation.DateTimeFormat;

/**
 * 订单续单信息对象 play_order_continue_info
 *
 * @author admin
 * @since 2024-05-10
 */
@Data
@EqualsAndHashCode(callSuper = false)
@TableName(value = "play_order_continue_info", autoResultMap = true)
public class PlayOrderContinueInfoEntity extends BaseEntity<PlayOrderContinueInfoEntity> {

    /**
     * uuid
     */
    private String id;

    /**
     * 租户ID
     */
    private String tenantId;

    /**
     * 订单ID
     */
    private String orderId;

    /**
     * 订单编号
     */
    private String orderNo;

    /**
     * 顾客ID
     */
    private String customId;

    /**
     * 店员ID
     */
    private String clerkId;

    /**
     * 下单类型（0：指定单，1：随机单。2：打赏单）
     */
    private String placeType;

    /**
     * 下单金额
     */
    private BigDecimal orderMoney;

    /**
     * 订单最终金额（支付金额）
     */
    private BigDecimal finalAmount;

    /**
     * 申请消息
     */
    private String continueMsg;

    /**
     * 截图
     */
    @TableField(typeHandler = StringTypeHandler.class)
    private List<String> images;

    /**
     * 是否需要审核，0:不需要，1:需要
     */
    private String reviewedRequired;

    /**
     * 审核状态（0:未审核，1:已通过，2:不通过）
     */
    private String reviewedState;

    /**
     * 审核人
     */
    private String reviewedBy;

    /**
     * 审核时间
     */
    @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
    private LocalDateTime reviewedTime;

    /**
     * 审核内容
     */
    private String reviewedRemark;

    /**
     * 申请时间
     */
    @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
    private LocalDateTime continueTime;

}


--- File: play-admin/src/main/java/com/starry/admin/modules/order/module/entity/PlayOrderEvaluateInfoEntity.java ---
package com.starry.admin.modules.order.module.entity;

import com.baomidou.mybatisplus.annotation.TableName;
import com.starry.common.domain.BaseEntity;
import java.util.Date;
import lombok.Data;
import lombok.EqualsAndHashCode;

/**
 * 订单评价信息对象 play_order_evaluate_info
 *
 * @author admin
 * @since 2024-05-07
 */
@Data
@EqualsAndHashCode(callSuper = false)
@TableName("play_order_evaluate_info")
public class PlayOrderEvaluateInfoEntity extends BaseEntity<PlayOrderEvaluateInfoEntity> {

    /**
     * uuid
     */
    private String id;

    /**
     * 租户ID
     */
    private String tenantId;

    /**
     * 订单ID
     */
    private String orderId;

    /**
     * 顾客ID
     */
    private String customId;

    /**
     * 店员ID
     */
    private String clerkId;

    /**
     * 商品ID
     */
    private String commodityId;

    /**
     * 匿名评价(0:匿名，1:非匿名)
     */
    private String anonymous;

    /**
     * 评价类型(0:好评，1差评)
     */
    private String evaluateType;

    /**
     * 评价等级【1-5星，最低1星，最高5星】
     */
    private Integer evaluateLevel;

    /**
     * 评价内容
     */
    private String evaluateCon;

    /**
     * 评价时间
     */
    private Date evaluateTime;

    /**
     * 数据是否隐藏（0:显示，1:隐藏）
     */
    private String hidden;

}


--- File: play-admin/src/main/java/com/starry/admin/modules/order/module/entity/PlayOrderInfoEntity.java ---
package com.starry.admin.modules.order.module.entity;

import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableName;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.starry.admin.common.conf.StringTypeHandler;
import com.starry.common.domain.BaseEntity;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.List;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.springframework.format.annotation.DateTimeFormat;

/**
 * 订单对象 order_info
 *
 * @author admin
 * @since 2024-03-20
 */
@Data
@EqualsAndHashCode(callSuper = false)

@TableName(value = "play_order_info", autoResultMap = true)
public class PlayOrderInfoEntity extends BaseEntity<PlayOrderInfoEntity> {

    /**
     * uuid
     */
    private String id;

    /**
     * 订单编号
     */
    private String orderNo;

    /**
     * 租户ID
     */
    private String tenantId;

    /**
     * 订单状态【0:1:2:3:4】 0：已下单（待接单） 1：已接单（待开始） 2：已开始（服务中） 3：已完成 4：已取消
     */
    private String orderStatus;

    /**
     * 订单类型【-1:退款订单;0:充值订单；1:提现订单；2:普通订单】
     */
    private String orderType;

    /**
     * 下单类型（-1:其他类型;0:指定单;1:随机单;2:打赏单）
     */
    private String placeType;

    /**
     * 打赏类型（0:余额;1:礼物）
     */
    private String rewardType;

    /**
     * 是否是首单【0：不是，1：是】
     */
    private String firstOrder;

    /**
     * 退款类型【0：未退款，1：已退款】
     */
    private String refundType;

    /**
     * 退款金额
     */
    private BigDecimal refundAmount;

    /**
     * 退款原因
     */
    private String refundReason;

    /**
     * 商品ID
     */
    private String commodityId;

    /**
     * 商品类型[0:礼物，1：服务]
     */
    private String commodityType;
    /**
     * 商品单价
     */
    private BigDecimal commodityPrice;

    /**
     * 商品属性-服务时长
     **/
    private String serviceDuration;

    /**
     * 商品名称
     */
    private String commodityName;

    /**
     * 商品数量
     */
    private String commodityNumber;

    /**
     * 订单要求
     */
    @TableField(typeHandler = StringTypeHandler.class)
    private List<String> labels;

    /**
     * 是否使用优惠券[0:未使用,1:已使用]
     */
    private String useCoupon;

    /**
     * 优惠券列表
     */
    @TableField(typeHandler = StringTypeHandler.class)
    private List<String> couponIds;

    /**
     * 是否是客服录入订单[0:不是,1:是]
     */
    private String backendEntry;

    /**
     * 支付方式，0：余额支付,1:微信支付,2:支付宝支付
     */
    private String payMethod;

    /**
     * 支付状态,[-1:支付失败,0:未支付;1:支付完成]
     */
    private String payState;

    /**
     * 支付消息
     */
    private String payMsg;

    /**
     * 订单金额
     */
    private BigDecimal orderMoney;

    /**
     * 订单最终金额（支付金额）
     */
    private BigDecimal finalAmount;

    /**
     * 店员预计收入
     */
    private BigDecimal estimatedRevenue;

    /**
     * 店员收入比例
     */
    private Integer estimatedRevenueRatio;

    /**
     * 优惠金额
     *
     * @since 2024/5/13 11:45
     **/
    private BigDecimal discountAmount;

    /**
     * 下单人
     */
    private String purchaserBy;

    /**
     * 下单时间
     */
    @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
    private LocalDateTime purchaserTime;

    /**
     * 微信号码
     */
    private String weiChatCode;

    /**
     * 是否需要审核，0:不需要，1:需要
     */
    private String reviewRequired;

    /**
     * 审核状态（0:未审核，1:已通过，2:不通过）
     */
    private String reviewState;

    /**
     * 审核人
     */
    private String reviewedBy;

    /**
     * 审核时间
     */
    @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
    private LocalDateTime reviewedTime;

    /**
     * 审核内容
     */
    private String reviewedRemark;

    /**
     * 接单人
     */
    private String acceptBy;

    /**
     * 订单分组ID（和接单人同一个分组,接单时设置）
     */
    private String groupId;

    /**
     * 接单时间
     */
    @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
    private LocalDateTime acceptTime;

    /**
     * 开始时间
     */
    @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
    private LocalDateTime orderStartTime;

    /**
     * 结束时间
     */
    @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
    private LocalDateTime orderEndTime;

    /**
     * 取消时间
     */
    @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
    private LocalDateTime orderCancelTime;

    /**
     * 订单备注
     */
    private String remark;

    /**
     * 后台备注
     */
    private String backendRemark;

    /**
     * 订单结算状态(0:未结算;1:已结算)
     */
    private String orderSettlementState;

    /**
     * 订单结算时间
     */
    @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
    private LocalDateTime orderSettlementTime;

    /**
     * 订单超时状态(0:未超时;1:已超时)
     */
    private String ordersExpiredState;

    /**
     * 随机单要求-店员性别（0:未知;1:男;2:女）
     */
    private String sex;

    /**
     * 随机单要求-店员等级ID
     */
    private String levelId;

    /**
     * 随机单要求-是否排除下单过的成员（0:不排除;1:排除）
     */
    private String excludeHistory;

    private BigDecimal profitSharingAmount;

    public PlayOrderInfoEntity() {

    }

    public PlayOrderInfoEntity(String id, String orderStatus) {
        this.id = id;
        this.orderStatus = orderStatus;
    }

    public PlayOrderInfoEntity(String id, String orderStatus, String orderType, String placeType) {
        this.id = id;
        this.orderStatus = orderStatus;
        this.orderType = orderType;
        this.placeType = placeType;
    }
}


--- File: play-admin/src/main/java/com/starry/admin/modules/order/module/entity/PlayOrderRefundInfoEntity.java ---
package com.starry.admin.modules.order.module.entity;

import com.baomidou.mybatisplus.annotation.TableName;
import com.starry.common.domain.BaseEntity;
import java.math.BigDecimal;
import java.util.Date;
import lombok.Data;
import lombok.EqualsAndHashCode;

/**
 * 订单退款信息对象 play_order_refund_info
 *
 * @author admin
 * @since 2024-05-09
 */
@Data
@EqualsAndHashCode(callSuper = false)
@TableName("play_order_refund_info")
public class PlayOrderRefundInfoEntity extends BaseEntity<PlayOrderRefundInfoEntity> {

    /**
     * uuid
     */
    private String id;

    /**
     * 租户ID
     */
    private String tenantId;

    /**
     * 订单ID
     */
    private String orderId;

    /**
     * 顾客ID
     */
    private String customId;

    /**
     * 店员ID
     */
    private String clerkId;

    /**
     * 支付方式，[0:余额支付;1:微信支付,2:支付宝支付]
     */
    private String payMethod;

    /**
     * 是否使用优惠券[0:未使用,1:已使用]
     */
    private String useCoupon;

    /**
     * 退款类型【0:部分退款;1:全部退款】
     */
    private String refundType;

    /**
     * 退款金额
     */
    private BigDecimal refundAmount;

    /**
     * 退款原因
     */
    private String refundReason;

    /**
     * 退款人类型[0:顾客;1:店员;2:管理员]
     */
    private String refundByType;

    /**
     * 退款人ID
     */
    private String refundById;

    /**
     * 截图
     */
    private String images;

    /**
     * 退款状态[-1:异常;0:处理中;1:成功;2:关闭]
     */
    private String refundState;

    /**
     * 是否需要审核，0:不需要，1:需要
     */
    private String reviewedRequired;

    /**
     * 审核状态（0:未审核，1:已通过，2:不通过）
     */
    private String reviewedState;

    /**
     * 审核人
     */
    private String reviewedBy;

    /**
     * 审核时间
     */
    private Date reviewedTime;

    /**
     * 审核内容
     */
    private String reviewedRemark;

}


--- File: play-admin/src/main/java/com/starry/admin/modules/order/module/vo/ClerkEstimatedRevenueVo.java ---
package com.starry.admin.modules.order.module.vo;

import java.math.BigDecimal;
import lombok.Data;

/**
 * @author admin
 * @since 2024/7/18 16:36
 **/
@Data
public class ClerkEstimatedRevenueVo {

    /**
     * 收入比例
     *
     * @since 2024/7/18 16:37
     **/
    private Integer revenueRatio;

    /**
     * 收入金额
     *
     * @since 2024/7/18 16:37
     **/
    private BigDecimal revenueAmount;
}


--- File: play-admin/src/main/java/com/starry/admin/modules/order/module/vo/OrderInfoAddVo.java ---
package com.starry.admin.modules.order.module.vo;

import com.baomidou.mybatisplus.annotation.TableName;
import com.starry.common.annotation.EnumValue;
import com.starry.common.domain.BaseEntity;
import java.util.List;
import javax.validation.constraints.NotBlank;
import lombok.Data;
import lombok.EqualsAndHashCode;

/**
 * 陪聊订单对象 order_info
 *
 * @author admin
 * @since 2024-03-20
 */
@Data
@EqualsAndHashCode(callSuper = false)

@TableName("order_info")
public class OrderInfoAddVo extends BaseEntity<OrderInfoAddVo> {

    /**
     * 租户ID
     */
    @NotBlank(message = "租户ID不能为空")
    private String tenantId;

    /**
     * 订单状态【0:1:2:3:4】 0：已下单 1：已接单 2：已开始 3：已完成 4：已取消
     */
    private String orderStatus = String.valueOf(0);

    /**
     * 订单类型【0：充值订单；1：提现订单；2：普通订单】
     */
    private String orderType = String.valueOf(2);

    /**
     * 下单类型（0：指定单，1：随机单。2：打赏单）
     */
    @EnumValue(strValues = {"0", "1"}, message = "退款类型参数异常")
    @NotBlank(message = "下单类型不能为空")
    private String placeType;

    /**
     * 陪聊ID
     */
    @NotBlank(message = "陪聊对象不能为空")
    private String playUserId;

    /**
     * 优惠券ID列表
     */
    private List<String> couponIds;

}


--- File: play-admin/src/main/java/com/starry/admin/modules/order/module/vo/OrderInfoCancellationVo.java ---
package com.starry.admin.modules.order.module.vo;

import javax.validation.constraints.NotNull;
import lombok.Data;

/**
 * 取消订单对象 order_info
 *
 * @author admin
 * @since 2024-03-20
 */
@Data
public class OrderInfoCancellationVo {

    /**
     * uuid
     */
    @NotNull(message = "订单ID不能为空")
    private String id;

    /**
     * 租户ID
     */
    @NotNull(message = "租户ID不能为空")
    private String tenantId;

    /**
     * 操作人ID
     */
    @NotNull(message = "操作人不能为空")
    private String operatorBy;

}


--- File: play-admin/src/main/java/com/starry/admin/modules/order/module/vo/OrderInfoRefundVo.java ---
package com.starry.admin.modules.order.module.vo;

import com.starry.common.annotation.EnumValue;
import javax.validation.constraints.NotNull;
import lombok.Data;

/**
 * 订单信息对象 order_info
 *
 * @author admin
 * @since 2024-03-20
 */
@Data
public class OrderInfoRefundVo {

    /**
     * uuid
     */
    @NotNull(message = "订单ID不能为空")
    private String id;

    /**
     * 租户ID
     */
    // @NotNull(message = "租户ID不能为空")
    private String tenantId;

    /**
     * 退款类型【1：部分退款，2：全部退款】
     */
    @NotNull(message = "退款类型不能为空")
    @EnumValue(strValues = {"1", "2"}, message = "退款类型参数异常")
    private String refundType;

    /**
     * 退款金额，单位分
     */
    private String refundMoney;

}


--- File: play-admin/src/main/java/com/starry/admin/modules/order/module/vo/PlayOrderComplaintQueryVo.java ---
package com.starry.admin.modules.order.module.vo;

import com.starry.common.domain.BasePageEntity;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import java.util.List;
import lombok.Data;
import lombok.EqualsAndHashCode;

/**
 * 订单投诉查询对象
 *
 * @author admin
 */
@EqualsAndHashCode(callSuper = true)
@Data
@ApiModel(value = "订单投诉查询参数", description = "查询订单投诉信息的条件参数")
public class PlayOrderComplaintQueryVo extends BasePageEntity {

    /**
     * 数据ID
     */
    @ApiModelProperty(value = "投诉ID", example = "1", notes = "特定投诉记录的ID")
    private String id;
    /**
     * 订单ID
     */
    @ApiModelProperty(value = "订单ID", example = "1", notes = "被投诉的订单ID")
    private String orderId;

    /**
     * 订单编号
     */
    @ApiModelProperty(value = "订单编号", example = "ORDER20240320001", notes = "被投诉的订单编号，支持模糊查询")
    private String orderNo;

    /**
     * 顾客ID
     */
    @ApiModelProperty(value = "顾客ID", example = "1", notes = "投诉顾客的ID")
    private String customId;

    /**
     * 店员ID
     */
    @ApiModelProperty(value = "店员ID", example = "1", notes = "被投诉店员的ID")
    private String clerkId;

    /**
     * 微信号码
     */
    @ApiModelProperty(value = "微信号码", example = "wx123456", notes = "顾客的微信号码")
    private String weiChatCode;

    /**
     * 投诉时间
     */
    @ApiModelProperty(value = "投诉时间范围", example = "['2024-01-01 00:00:00','2024-12-31 23:59:59']", notes = "投诉提交时间范围，包含开始和结束时间")
    private List<String> complaintTime;

}


--- File: play-admin/src/main/java/com/starry/admin/modules/order/module/vo/PlayOrderComplaintReturnVo.java ---
package com.starry.admin.modules.order.module.vo;

import com.baomidou.mybatisplus.annotation.TableField;
import com.starry.admin.common.conf.StringTypeHandler;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import java.math.BigDecimal;
import java.util.Date;
import java.util.List;
import lombok.Data;

/**
 * 订单投诉查询返回对象
 *
 * @author admin
 */
@Data
@ApiModel(value = "订单投诉返回数据", description = "订单投诉信息的返回数据")
public class PlayOrderComplaintReturnVo {

    /**
     * uuid
     */
    @ApiModelProperty(value = "投诉ID", example = "1")
    private String id;

    /**
     * 订单ID
     */
    @ApiModelProperty(value = "订单ID", example = "1")
    private String orderId;

    /**
     * 顾客ID
     */
    @ApiModelProperty(value = "顾客ID", example = "1")
    private String customId;

    /**
     * 顾客昵称
     */
    @ApiModelProperty(value = "顾客昵称", example = "张三")
    private String customNickname;

    /**
     * 顾客头像
     */
    @ApiModelProperty(value = "顾客头像", example = "https://example.com/avatar.jpg")
    private String customAvatar;

    /**
     * 店员ID
     */
    @ApiModelProperty(value = "店员ID", example = "1")
    private String clerkId;

    /**
     * 顾客昵称
     */
    @ApiModelProperty(value = "店员昵称", example = "小明")
    private String clerkNickname;

    /**
     * 顾客头像
     */
    @ApiModelProperty(value = "店员头像", example = "https://example.com/clerk_avatar.jpg")
    private String clerkAvatar;

    /**
     * 投诉人微信号码
     */
    @ApiModelProperty(value = "微信号码", example = "wx123456")
    private String wxChatCode;

    /**
     * 投诉内容
     */
    @ApiModelProperty(value = "投诉内容", example = "店员服务态度差")
    private String complaintCon;

    /**
     * 投诉时间
     */
    @ApiModelProperty(value = "投诉时间", example = "2024-01-01 12:00:00")
    private Date complaintTime;

    /**
     * 图片列表
     */
    @TableField(typeHandler = StringTypeHandler.class)
    @ApiModelProperty(value = "图片列表", example = "['https://example.com/img1.jpg','https://example.com/img2.jpg']")
    private List<String> images;

    /**
     * 数据是否隐藏（0:显示，1:隐藏）
     */
    @ApiModelProperty(value = "是否隐藏", example = "0", notes = "数据是否隐藏，0:显示，1:隐藏")
    private String hidden;

    /**
     * 订单号
     */
    @ApiModelProperty(value = "订单编号", example = "ORDER20240320001")
    private String orderNo;

    /**
     * 下单类型（0：指定单，1：随机单。2：打赏单）
     */
    @ApiModelProperty(value = "下单类型", example = "0", notes = "0：指定单，1：随机单，2：打赏单")
    private String placeType;

    /**
     * 订单最终金额（支付金额）
     */
    @ApiModelProperty(value = "支付金额", example = "100.00")
    private BigDecimal finalAmount;

    /**
     * 服务项目ID
     */
    @ApiModelProperty(value = "商品ID", example = "1")
    private String commodityId;

    /**
     * 项目类型
     */
    @ApiModelProperty(value = "商品类型", example = "0", notes = "0:礼物，1：服务")
    private String commodityType;

    /**
     * 项目名称
     */
    @ApiModelProperty(value = "商品名称", example = "语音陪聊")
    private String commodityName;

    /**
     * 项目价格
     */
    @ApiModelProperty(value = "商品单价", example = "50.00")
    private BigDecimal commodityPrice;

    /**
     * 商品数量
     */
    @ApiModelProperty(value = "商品数量", example = "1")
    private String commodityNumber;

    /**
     * 服务时长（文字描述信息,不参与订单计算）
     */
    @ApiModelProperty(value = "服务时长", example = "30", notes = "服务的时长，单位分钟")
    private String serviceDuration;
}


--- File: play-admin/src/main/java/com/starry/admin/modules/order/module/vo/PlayOrderContinueQueryVo.java ---
package com.starry.admin.modules.order.module.vo;

import com.starry.common.domain.BasePageEntity;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import java.util.List;
import lombok.Data;
import lombok.EqualsAndHashCode;

/**
 * 续单分页查询对象
 *
 * @author admin
 * @since 2024/5/10 14:59
 **/
@EqualsAndHashCode(callSuper = true)
@Data
@ApiModel(value = "订单续单查询参数", description = "查询订单续单信息的条件参数")
public class PlayOrderContinueQueryVo extends BasePageEntity {

    @ApiModelProperty(value = "昵称", example = "小明", notes = "店员或顾客昵称，支持模糊查询")
    private String nickname;

    /**
     * 店员ID
     */
    @ApiModelProperty(value = "店员ID", example = "1", notes = "申请续单的店员ID")
    private String clerkId;

    /**
     * 顾客ID
     */
    @ApiModelProperty(value = "顾客ID", example = "1", notes = "相关顾客的ID")
    private String customId;

    /**
     * 订单编号
     */
    @ApiModelProperty(value = "订单编号", example = "ORDER20240320001", notes = "相关订单的编号，支持模糊查询")
    private String orderNo;

    /**
     * 审核状态（0:未审核，1:已通过，2:不通过）
     */
    @ApiModelProperty(value = "审核状态", example = "0", notes = "0:未审核，1:已通过，2:不通过")
    private String reviewState;

    /**
     * 申请时间
     */
    @ApiModelProperty(value = "申请时间范围", example = "['2024-01-01 00:00:00','2024-12-31 23:59:59']", notes = "续单申请时间范围，包含开始和结束时间")
    private List<String> continueTime;

}


--- File: play-admin/src/main/java/com/starry/admin/modules/order/module/vo/PlayOrderContinueReturnVo.java ---
package com.starry.admin.modules.order.module.vo;

import com.fasterxml.jackson.annotation.JsonFormat;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.List;
import lombok.Data;
import org.springframework.format.annotation.DateTimeFormat;

/**
 * 续单查询返回对象
 *
 * @author admin
 * @since 2024/5/10 14:59
 **/
@Data
@ApiModel(value = "订单续单返回数据", description = "订单续单信息的返回数据")
public class PlayOrderContinueReturnVo {

    @ApiModelProperty(value = "续单ID", example = "1")
    private String id;

    /**
     * 店员ID
     */
    @ApiModelProperty(value = "店员ID", example = "1")
    private String clerkId;

    /**
     * 店员昵称
     */
    @ApiModelProperty(value = "店员昵称", example = "小明")
    private String clerkNickname;

    /**
     * 店员昵称
     */
    @ApiModelProperty(value = "店员头像", example = "https://example.com/clerk_avatar.jpg")
    private String clerkAvatar;

    /**
     * 店员性别
     */
    @ApiModelProperty(value = "店员性别", example = "1", notes = "0:未知,1:男,2:女")
    private String clerkSex;

    /**
     * 顾客ID
     */
    @ApiModelProperty(value = "顾客ID", example = "1")
    private String customerId;

    /**
     * 用户等级名称
     */
    @ApiModelProperty(value = "等级名称", example = "黄金会员")
    private String levelName;

    /**
     * 订单编号
     */
    @ApiModelProperty(value = "订单编号", example = "ORDER20240320001")
    private String orderNo;

    /**
     * 下单类型（0：指定单，1：随机单。2：打赏单）
     */
    @ApiModelProperty(value = "下单类型", example = "0", notes = "0：指定单，1：随机单，2：打赏单")
    private String placeType;

    /**
     * 商品名称
     */
    @ApiModelProperty(value = "商品名称", example = "语音陪聊")
    private String commodityName;
    /**
     * 商品单价
     */
    @ApiModelProperty(value = "商品单价", example = "50.00")
    private BigDecimal commodityPrice;

    /**
     * 服务时长
     *
     * @since 2024/5/8 16:44
     **/
    @ApiModelProperty(value = "服务时长", example = "30", notes = "服务的时长，单位分钟")
    private String serviceDuration;

    /**
     * 商品数量
     */
    @ApiModelProperty(value = "商品数量", example = "1")
    private int commodityNumber;

    /**
     * 下单金额
     */
    @ApiModelProperty(value = "订单金额", example = "100.00")
    private BigDecimal orderMoney;

    /**
     * 订单最终金额（支付金额）
     */
    @ApiModelProperty(value = "支付金额", example = "100.00")
    private BigDecimal finalAmount;

    /**
     * 申请消息
     */
    @ApiModelProperty(value = "申请消息", example = "顾客需要继续服务")
    private String continueMsg;

    /**
     * 截图
     */
    @ApiModelProperty(value = "截图列表", example = "['https://example.com/img1.jpg','https://example.com/img2.jpg']")
    private List<String> images;

    /**
     * 是否需要审核，0:不需要，1:需要
     */
    @ApiModelProperty(value = "是否需要审核", example = "1", notes = "0:不需要，1:需要")
    private String reviewedRequired;

    /**
     * 审核状态（0:未审核，1:已通过，2:不通过）
     */
    @ApiModelProperty(value = "审核状态", example = "0", notes = "0:未审核，1:已通过，2:不通过")
    private String reviewedState;

    /**
     * 审核内容
     */
    @ApiModelProperty(value = "审核备注", example = "审核通过，可以继续服务")
    private String reviewedRemark;

    @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
    @ApiModelProperty(value = "申请时间", example = "2024-01-01 12:00:00")
    private LocalDateTime continueTime;
}


--- File: play-admin/src/main/java/com/starry/admin/modules/order/module/vo/PlayOrderDetailsReturnVo.java ---
package com.starry.admin.modules.order.module.vo;

import com.fasterxml.jackson.annotation.JsonFormat;
import java.math.BigDecimal;
import java.util.Date;
import java.util.List;
import lombok.Data;

/**
 * 订单查询返回对象
 *
 * @author admin
 * @since 2024-03-20
 */
@Data
public class PlayOrderDetailsReturnVo {

    private String id;

    private String orderNo;

    /**
     * 用户ID
     */
    private String customId;

    /**
     * 用户昵称
     */
    private String customNickname;

    /**
     * 用户头像
     */
    private String customAvatar;

    /**
     * 店员ID
     */
    private String clerkId;

    /**
     * 店员昵称
     */
    private String clerkNickname;

    /**
     * 店员头像
     */
    private String clerkAvatar;

    /**
     * 下单时间
     */
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
    private Date purchaserTime;

    /**
     * 接单时间
     */
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
    private Date acceptTime = new Date();

    /**
     * 开始时间
     */
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
    private Date startTime = new Date();

    /**
     * 结束时间时间
     */
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
    private Date endTime = new Date();

    /**
     * 订单金额
     */
    private BigDecimal orderMoney;

    /**
     * 订单最终金额
     */
    private BigDecimal finalAmount;

    /**
     * 商品ID
     */
    private String commodityId;

    /**
     * 商品类型[0:礼物，1：服务]
     */
    private String commodityType;

    /**
     * 商品名称
     */
    private String commodityName;
    /**
     * 商品单价
     */
    private BigDecimal commodityPrice;

    /**
     * 服务时长
     *
     * @since 2024/5/8 16:44
     **/
    private String serviceDuration;

    /**
     * 商品数量
     */
    private int commodityNumber;

    /**
     * 订单状态【0:1:2:3:4】 0：已下单（待接单） 1：已接单（待开始） 2：已开始（服务中） 3：已完成 4：已取消
     */
    private String orderStatus;

    /**
     * 下单类型（0：指定单，1：随机单。2：打赏单）
     */
    private String placeType;

    /**
     * 微信号码
     */
    private String weiChatCode;

    /**
     * 支付方式，0：余额支付,1:微信支付,2:支付宝支付
     */
    private String payMethod;

    /**
     * 订单备注
     */
    private String remark;

    /**
     * 订单要求
     */
    private List<String> labels;

    /**
     * 是否是首单【0：不是，1：是】
     */
    private String firstOrder;

    /**
     * 是否评价（1：已评价;0:未评价）
     */
    private String evaluate = "0";

    /**
     * 是否投诉（1：已投诉;0:未投诉）
     */
    private String complaint = "0";

    /**
     * 预计收入
     */
    private BigDecimal estimatedRevenue;

    /**
     * 店员收入比例
     */
    private Integer estimatedRevenueRatio;

    /**
     * 优惠金额
     */
    private BigDecimal discountAmount;

}


--- File: play-admin/src/main/java/com/starry/admin/modules/order/module/vo/PlayOrderEditAcceptByVo.java ---
package com.starry.admin.modules.order.module.vo;

import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import javax.validation.constraints.NotNull;
import lombok.Data;

/**
 * 订单查询对象
 *
 * @author admin
 * @since 2024-03-20
 */
@Data
@ApiModel(value = "更换店员参数", description = "更换订单接单店员的请求参数")
public class PlayOrderEditAcceptByVo {

    /**
     * uuid
     */
    @NotNull(message = "orderId不能为空")
    @ApiModelProperty(value = "订单ID", required = true, example = "1", notes = "需要更换店员的订单ID")
    private String orderId;

    /**
     * 店员ID
     */
    @NotNull(message = "店员不能为空")
    @ApiModelProperty(value = "店员ID", required = true, example = "1", notes = "新的接单店员ID")
    private String acceptBy;

    /**
     * 备注
     */
    @ApiModelProperty(value = "备注", example = "店员请假，需要更换", notes = "更换店员的原因说明")
    private String remark;

}


--- File: play-admin/src/main/java/com/starry/admin/modules/order/module/vo/PlayOrderEditBackendRemarkByVo.java ---
package com.starry.admin.modules.order.module.vo;

import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import javax.validation.constraints.NotNull;
import lombok.Data;
import org.hibernate.validator.constraints.Length;

/**
 * 订单查询对象
 *
 * @author admin
 * @since 2024-03-20
 */
@Data
@ApiModel(value = "修改后台备注参数", description = "修改订单后台备注的请求参数")
public class PlayOrderEditBackendRemarkByVo {

    /**
     * uuid
     */
    @NotNull(message = "orderId不能为空")
    @ApiModelProperty(value = "订单ID", required = true, example = "1", notes = "需要修改备注的订单ID")
    private String orderId;

    /**
     * 后台备注不能为空
     */
    @NotNull(message = "后台备注不能为空")
    @Length(min = 1, max = 100, message = "后台备注字符长度在1-100个字符之间")
    @ApiModelProperty(value = "后台备注", required = true, example = "顾客要求特殊服务", notes = "订单的后台备注内容，1-100字符")
    private String backendRemark;

}


--- File: play-admin/src/main/java/com/starry/admin/modules/order/module/vo/PlayOrderEditRandomSingleVo.java ---
package com.starry.admin.modules.order.module.vo;

import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import javax.validation.constraints.NotNull;
import lombok.Data;

/**
 * 重新抢单对象
 *
 * @author admin
 * @since 2024-03-20
 */
@Data
@ApiModel(value = "重新抢单参数", description = "设置订单重新抢单的请求参数")
public class PlayOrderEditRandomSingleVo {

    /**
     * uuid
     */
    @NotNull(message = "orderId不能为空")
    @ApiModelProperty(value = "订单ID", required = true, example = "1", notes = "需要重新抢单的订单ID")
    private String orderId;

    /**
     * 陪聊性别（0:未知;1:男;2:女）
     *
     * @since 2024/5/8 15:31
     **/
    @NotNull(message = "陪聊性别不能为空")
    @ApiModelProperty(value = "店员性别", required = true, example = "2", notes = "抢单店员的性别要求，0:未知;1:男;2:女")
    private String sex;
    /**
     * 陪聊等级
     **/
    @NotNull(message = "陪聊等级不能为空")
    @ApiModelProperty(value = "店员等级", required = true, example = "1", notes = "抢单店员的等级要求")
    private String levelId;

    /**
     * 是否排除下单过的成员（0:不排除;1:排除）
     *
     * @since 2024/5/8 15:28
     **/
    @NotNull(message = "是否排除下单过的成员不能为空")
    @ApiModelProperty(value = "是否排除历史", required = true, example = "1", notes = "是否排除下单过的成员，0:不排除;1:排除")
    private String excludeHistory;

    /**
     * 订单备注
     *
     * @since 2024/5/8 15:28
     **/
    @ApiModelProperty(value = "订单备注", example = "需要擅长聊天的店员", notes = "订单的备注信息")
    private String remark;

}


--- File: play-admin/src/main/java/com/starry/admin/modules/order/module/vo/PlayOrderEvaluateEditStateVo.java ---
package com.starry.admin.modules.order.module.vo;

import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.Pattern;
import lombok.Data;

/**
 * 订单评价状态修改对象
 *
 * @author admin
 */
@Data
@ApiModel(value = "评价状态修改参数", description = "修改订单评价显示状态的请求参数")
public class PlayOrderEvaluateEditStateVo {

    /**
     * UUID
     */
    @NotBlank(message = "id不能为空")
    @ApiModelProperty(value = "评价ID", required = true, example = "1", notes = "需要修改状态的评价ID")
    private String id;

    /**
     * 数据是否隐藏（0:显示，1:隐藏）
     */
    @NotBlank(message = "是否隐藏不能为空")
    @Pattern(regexp = "[01]", message = "是否隐藏数据错误,只能位0或者1")
    @ApiModelProperty(value = "是否隐藏", required = true, example = "1", notes = "数据是否隐藏，0:显示，1:隐藏")
    private String hidden;
}


--- File: play-admin/src/main/java/com/starry/admin/modules/order/module/vo/PlayOrderEvaluateQueryVo.java ---
package com.starry.admin.modules.order.module.vo;

import com.starry.common.domain.BasePageEntity;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import java.util.List;
import lombok.Data;
import lombok.EqualsAndHashCode;

/**
 * 订单评价查询对象
 *
 * @author admin
 */
@EqualsAndHashCode(callSuper = true)
@Data
@ApiModel(value = "订单评价查询参数", description = "查询订单评价信息的条件参数")
public class PlayOrderEvaluateQueryVo extends BasePageEntity {

    /**
     * UUID
     */
    @ApiModelProperty(value = "评价ID", example = "1", notes = "特定评价记录的ID")
    private String id;

    /**
     * 顾客ID
     */
    @ApiModelProperty(value = "顾客ID", example = "1", notes = "评价顾客的ID")
    private String customId;

    /**
     * 店员ID
     */
    @ApiModelProperty(value = "店员ID", example = "1", notes = "被评价店员的ID")
    private String clerkId;

    /**
     * 评价内容
     */
    @ApiModelProperty(value = "评价内容", example = "服务很好", notes = "评价内容，支持模糊查询")
    private String evaluateCon;

    /**
     * 匿名评价(0:匿名，1:非匿名)
     */
    @ApiModelProperty(value = "是否匿名", example = "0", notes = "0:匿名，1:非匿名")
    private String anonymous;

    /**
     * 评价时间
     */
    @ApiModelProperty(value = "评价时间范围", example = "['2024-01-01 00:00:00','2024-12-31 23:59:59']", notes = "评价提交时间范围，包含开始和结束时间")
    private List<String> evaluateTime;

    /**
     * 数据是否隐藏（0:显示，1:隐藏）
     */
    @ApiModelProperty(value = "是否隐藏", example = "0", notes = "数据是否隐藏，0:显示，1:隐藏")
    private String hidden;
}


--- File: play-admin/src/main/java/com/starry/admin/modules/order/module/vo/PlayOrderEvaluateReturnVo.java ---
package com.starry.admin.modules.order.module.vo;

import com.fasterxml.jackson.annotation.JsonFormat;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import java.math.BigDecimal;
import java.util.Date;
import lombok.Data;

/**
 * 订单评价查询返回对象
 *
 * @author admin
 */
@Data
@ApiModel(value = "订单评价返回数据", description = "订单评价信息的返回数据")
public class PlayOrderEvaluateReturnVo {

    /**
     * uuid
     */
    @ApiModelProperty(value = "评价ID", example = "1")
    private String id;

    /**
     * 订单ID
     */
    @ApiModelProperty(value = "订单ID", example = "1")
    private String orderId;

    /**
     * 匿名评价(0:匿名，1:非匿名)
     */
    @ApiModelProperty(value = "是否匿名", example = "0", notes = "0:匿名，1:非匿名")
    private String anonymous;
    /**
     * 评价类型(0:好评，1差评)
     */
    @ApiModelProperty(value = "评价类型", example = "0", notes = "0:好评，1:差评")
    private String evaluateType;

    /**
     * 评价等级【1-5星，最低1星，最高5星】
     */
    @ApiModelProperty(value = "评价等级", example = "5", notes = "1-5星，最低1星，最高5星")
    private Integer evaluateLevel;

    /**
     * 评价内容
     */
    @ApiModelProperty(value = "评价内容", example = "服务很好，店员态度很好")
    private String evaluateCon;

    /**
     * 评价时间
     */
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
    @ApiModelProperty(value = "评价时间", example = "2024-01-01 12:00:00")
    private Date evaluateTime;

    /**
     * 数据是否隐藏（0:显示，1:隐藏）
     */
    @ApiModelProperty(value = "是否隐藏", example = "0", notes = "数据是否隐藏，0:显示，1:隐藏")
    private String hidden;

    /**
     * 顾客ID
     */
    @ApiModelProperty(value = "顾客ID", example = "1")
    private String customId;

    /**
     * 顾客昵称
     */
    @ApiModelProperty(value = "顾客昵称", example = "张三")
    private String customNickname;

    /**
     * 顾客头像
     */
    @ApiModelProperty(value = "顾客头像", example = "https://example.com/avatar.jpg")
    private String customAvatar;

    /**
     * 店员ID
     */
    @ApiModelProperty(value = "店员ID", example = "1")
    private String clerkId;

    /**
     * 顾客昵称
     */
    @ApiModelProperty(value = "店员昵称", example = "小明")
    private String clerkNickname;

    /**
     * 顾客头像
     */
    @ApiModelProperty(value = "店员头像", example = "https://example.com/clerk_avatar.jpg")
    private String clerkAvatar;

    /**
     * 点赞数
     */
    @ApiModelProperty(value = "点赞数", example = "10")
    private int likeCount = 10;

    /**
     * 订单号
     */
    @ApiModelProperty(value = "订单编号", example = "ORDER20240320001")
    private String orderNo;

    /**
     * 下单类型（0：指定单，1：随机单。2：打赏单）
     */
    @ApiModelProperty(value = "下单类型", example = "0", notes = "0：指定单，1：随机单，2：打赏单")
    private String placeType;

    /**
     * 订单最终金额（支付金额）
     */
    @ApiModelProperty(value = "支付金额", example = "100.00")
    private BigDecimal finalAmount;

    /**
     * 服务项目ID
     */
    @ApiModelProperty(value = "商品ID", example = "1")
    private String commodityId;

    /**
     * 项目类型
     */
    @ApiModelProperty(value = "商品类型", example = "0", notes = "0:礼物，1：服务")
    private String commodityType;

    /**
     * 项目名称
     */
    @ApiModelProperty(value = "商品名称", example = "语音陪聊")
    private String commodityName;

    /**
     * 项目价格
     */
    @ApiModelProperty(value = "商品单价", example = "50.00")
    private BigDecimal commodityPrice;

    /**
     * 商品数量
     */
    @ApiModelProperty(value = "商品数量", example = "1")
    private String commodityNumber;

    /**
     * 服务时长（文字描述信息,不参与订单计算）
     */
    @ApiModelProperty(value = "服务时长", example = "30", notes = "服务的时长，单位分钟")
    private String serviceDuration;
}


--- File: play-admin/src/main/java/com/starry/admin/modules/order/module/vo/PlayOrderInfoQueryVo.java ---
package com.starry.admin.modules.order.module.vo;

import com.starry.common.domain.BasePageEntity;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import java.util.List;
import lombok.Data;
import lombok.EqualsAndHashCode;

/**
 * 订单查询对象
 *
 * @author admin
 * @since 2024-03-20
 */
@Data
@EqualsAndHashCode(callSuper = false)
@ApiModel(value = "订单查询参数", description = "查询订单信息的条件参数")
public class PlayOrderInfoQueryVo extends BasePageEntity {

    /**
     * uuid
     */
    @ApiModelProperty(value = "订单ID", example = "1", notes = "特定订单的ID")
    private String id;

    /**
     * 订单编号
     */
    @ApiModelProperty(value = "订单编号", example = "ORDER20240320001", notes = "订单的编号，支持模糊查询")
    private String orderNo;

    /**
     * 订单状态【0:1:2:3:4】 0：已下单（待接单） 1：已接单（待开始） 2：已开始（服务中） 3：已完成 4：已取消
     */
    @ApiModelProperty(value = "订单状态", example = "0", notes = "0：已下单（待接单）;1：已接单（待开始）;2：已开始（服务中）;3：已完成;4：已取消")
    private String orderStatus;

    /**
     * 订单类型【-1:退款订单;0:充值订单；1:提现订单；2:普通订单】
     */
    @ApiModelProperty(value = "订单类型", example = "2", notes = "-1:退款订单;0:充值订单;1:提现订单;2:普通订单")
    private String orderType;

    /**
     * 下单类型（-1:其他类型;0:指定单;1:随机单;2:打赏单）
     */
    @ApiModelProperty(value = "下单类型", example = "0", notes = "-1:其他类型;0:指定单;1:随机单;2:打赏单")
    private String placeType;

    /**
     * 是否是首单【0：不是，1：是】
     */
    @ApiModelProperty(value = "是否首单", example = "1", notes = "0：不是，1：是")
    private String firstOrder;

    /**
     * 是否使用优惠券[0:未使用,1:已使用]
     */
    @ApiModelProperty(value = "是否使用优惠券", example = "1", notes = "0:未使用,1:已使用")
    private String useCoupon;

    /**
     * 是否是客服录入订单[0:不是,1:是]
     */
    @ApiModelProperty(value = "是否客服录入", example = "0", notes = "0:不是,1:是")
    private String backendEntry;

    /**
     * 支付方式，0：余额支付,1:微信支付,2:支付宝支付
     */
    @ApiModelProperty(value = "支付方式", example = "0", notes = "0：余额支付,1:微信支付,2:支付宝支付")
    private String payMethod;

    /**
     * 微信号码
     */
    @ApiModelProperty(value = "微信号码", example = "wx123456", notes = "顾客的微信号码")
    private String weiChatCode;

    /**
     * 下单人
     */
    @ApiModelProperty(value = "下单人ID", example = "1", notes = "下单顾客的ID")
    private String purchaserBy;

    /**
     * 下单时间
     */
    @ApiModelProperty(value = "下单时间范围", example = "['2024-01-01 00:00:00','2024-12-31 23:59:59']", notes = "下单时间范围，包含开始和结束时间")
    private List<String> purchaserTime;

    /**
     * 接单人
     */
    @ApiModelProperty(value = "接单人ID", example = "1", notes = "接单店员的ID")
    private String acceptBy;

    /**
     * 接单时间
     */
    @ApiModelProperty(value = "接单时间范围", example = "['2024-01-01 00:00:00','2024-12-31 23:59:59']", notes = "接单时间范围，包含开始和结束时间")
    private List<String> acceptTime;

    /**
     * 开始订单时间（开始服务时间）
     */
    @ApiModelProperty(value = "开始服务时间范围", example = "['2024-01-01 00:00:00','2024-12-31 23:59:59']", notes = "订单开始服务时间范围，包含开始和结束时间")
    private List<String> startOrderTime;

    /**
     * 完成订单时间（开始服务时间）
     */
    @ApiModelProperty(value = "完成服务时间范围", example = "['2024-01-01 00:00:00','2024-12-31 23:59:59']", notes = "订单完成服务时间范围，包含开始和结束时间")
    private List<String> endOrderTime;

    /**
     * 分组ID
     */
    @ApiModelProperty(value = "分组ID", example = "1", notes = "订单所属分组的ID")
    private String groupId;

    @ApiModelProperty(value = "店员昵称", example = "小明", notes = "接单店员的昵称，支持模糊查询")
    private String clerkNickName;

}


--- File: play-admin/src/main/java/com/starry/admin/modules/order/module/vo/PlayOrderInfoReturnVo.java ---
package com.starry.admin.modules.order.module.vo;

import com.fasterxml.jackson.annotation.JsonFormat;
import java.math.BigDecimal;
import java.util.Date;
import java.util.List;
import lombok.Data;

/**
 * 订单查询返回对象
 *
 * @author admin
 * @since 2024-03-20
 */
@Data
public class PlayOrderInfoReturnVo {

    private String id;

    /**
     * 用户ID
     */
    private String customId;

    /**
     * 用户昵称
     */
    private String customNickname;

    /**
     * 用户头像
     */
    private String customAvatar;

    private String orderNo;

    /**
     * 店员ID
     */
    private String clerkId;

    /**
     * 店员昵称
     */
    private String clerkNickname;

    /**
     * 店员头像
     */
    private String clerkAvatar;

    /**
     * 下单时间
     */
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
    private Date purchaserTime;

    /**
     * 接单时间
     */
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
    private Date acceptTime;

    /**
     * 开始时间
     */
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
    private Date startTime;

    /**
     * 结束时间
     */
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
    private Date endTime;

    /**
     * 取消时间
     */
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
    private Date cancelTime;

    /**
     * 订单金额
     */
    private BigDecimal orderMoney;

    /**
     * 退款类型【0：未退款，1：已退款】
     */
    private String refundType;

    /**
     * 商品ID
     */
    private String commodityId;

    /**
     * 商品类型[0:礼物，1：服务]
     */
    private String commodityType;

    /**
     * 商品类型[0:礼物，1：服务]
     */
    private String commodityName;
    /**
     * 商品单价
     */
    private BigDecimal commodityPrice;

    /**
     * 服务时长
     *
     * @since 2024/5/8 16:44
     **/
    private String serviceDuration;

    /**
     * 商品数量
     */
    private int commodityNumber;

    /**
     * 订单状态【0:1:2:3:4】 0：已下单（待接单） 1：已接单（待开始） 2：已开始（服务中） 3：已完成 4：已取消
     */
    private String orderStatus;

    /**
     * 下单类型（0：指定单，1：随机单。2：打赏单）
     */
    private String placeType;

    /**
     * 支付方式，0：余额支付,1:微信支付,2:支付宝支付
     */
    private String payMethod;

    /**
     * 是否是首单【0：不是，1：是】
     */
    private String firstOrder;

    /**
     * 订单最终金额（支付金额）
     */
    private BigDecimal finalAmount;

    /**
     * 店员收入
     */
    private BigDecimal estimatedRevenue = BigDecimal.ZERO;

    /**
     * 店员收入比例
     */
    private Integer estimatedRevenueRatio;

    /**
     * 微信号码
     */
    private String weiChatCode;

    /**
     * 订单备注
     */
    private String remark;

    /**
     * 后台备注
     */
    private String backendRemark;

    private List<String> labels;

}


--- File: play-admin/src/main/java/com/starry/admin/modules/order/module/vo/PlayOrderRefundAddVo.java ---
package com.starry.admin.modules.order.module.vo;

import com.starry.common.domain.BasePageEntity;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import java.math.BigDecimal;
import javax.validation.constraints.NotNull;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.hibernate.validator.constraints.Length;

/**
 * 订单查询对象
 *
 * @author admin
 * @since 2024-03-20
 */
@Data
@EqualsAndHashCode(callSuper = false)
@ApiModel(value = "订单退款参数", description = "订单退款操作的请求参数")
public class PlayOrderRefundAddVo extends BasePageEntity {

    /**
     * uuid
     */
    @NotNull(message = "orderId不能为空")
    @ApiModelProperty(value = "订单ID", required = true, example = "1", notes = "需要退款的订单ID")
    private String orderId;

    /**
     * 退款金额
     */
    @NotNull(message = "退款金额不能为空")
    @ApiModelProperty(value = "退款金额", required = true, example = "100.00", notes = "退款的金额，不能大于支付金额")
    private BigDecimal refundAmount;

    /**
     * 退款原因
     */
    @NotNull(message = "退款原因不能为空")
    @Length(min = 1, max = 100, message = "退款原因必须在1-100个字符之间")
    @ApiModelProperty(value = "退款原因", required = true, example = "顾客要求退款", notes = "退款的原因说明，1-100字符")
    private String refundReason;

}


--- File: play-admin/src/main/java/com/starry/admin/modules/order/module/vo/PlayOrderRefundQueryVo.java ---
package com.starry.admin.modules.order.module.vo;

import com.starry.common.domain.BasePageEntity;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import lombok.EqualsAndHashCode;

/**
 * @author admin
 * @since 2024/5/9 下午11:10
 **/
@EqualsAndHashCode(callSuper = true)
@Data
@ApiModel(value = "订单退款查询参数", description = "查询订单退款信息的条件参数")
public class PlayOrderRefundQueryVo extends BasePageEntity {

    @ApiModelProperty(value = "订单ID", example = "1", notes = "特定订单的ID")
    private String orderId;

    @ApiModelProperty(value = "退款类型", example = "0", notes = "0:全额退款,1:部分退款")
    private String refundType;

    @ApiModelProperty(value = "退款状态", example = "1", notes = "退款处理状态")
    private String refundStatus;
}


--- File: play-admin/src/main/java/com/starry/admin/modules/order/module/vo/PlayOrderRefundReturnVo.java ---
package com.starry.admin.modules.order.module.vo;

import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import java.math.BigDecimal;
import java.util.Date;
import lombok.Data;

/**
 * @author admin
 * @since 2024/5/9 下午11:10
 **/
@Data
@ApiModel(value = "订单退款返回数据", description = "订单退款信息的返回数据")
public class PlayOrderRefundReturnVo {

    @ApiModelProperty(value = "退款ID", example = "1")
    private String id;

    @ApiModelProperty(value = "订单ID", example = "1")
    private String orderId;

    @ApiModelProperty(value = "订单编号", example = "ORDER20240320001")
    private String orderNo;

    @ApiModelProperty(value = "退款金额", example = "100.00")
    private BigDecimal refundAmount;

    @ApiModelProperty(value = "退款原因", example = "顾客要求退款")
    private String refundReason;

    @ApiModelProperty(value = "退款类型", example = "0", notes = "0:全额退款,1:部分退款")
    private String refundType;

    @ApiModelProperty(value = "退款状态", example = "1", notes = "退款处理状态")
    private String refundStatus;

    @ApiModelProperty(value = "退款时间", example = "2024-01-01 12:00:00")
    private Date refundTime;

    @ApiModelProperty(value = "支付方式", example = "0", notes = "0：余额支付,1:微信支付,2:支付宝支付")
    private String payMethod;

    @ApiModelProperty(value = "操作人ID", example = "1")
    private String operatorId;

    @ApiModelProperty(value = "操作人名称", example = "管理员")
    private String operatorName;
}


--- File: play-admin/src/main/java/com/starry/admin/modules/order/module/vo/PlayOrderReviewStateEditVo.java ---
package com.starry.admin.modules.order.module.vo;

import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.Pattern;
import lombok.Data;

/**
 * 续单申请审批状态修改对象
 *
 * @author admin
 * @since 2024/5/10 14:59
 **/
@Data
@ApiModel(value = "审批状态修改参数", description = "续单申请审批状态修改的请求参数")
public class PlayOrderReviewStateEditVo {

    @NotBlank(message = "ID不能为空")
    @ApiModelProperty(value = "续单ID", required = true, example = "1", notes = "需要审批的续单ID")
    private String id;

    /**
     * 审核状态（0:未审核，1:已通过，2:不通过）
     */
    @NotBlank(message = "审核状态不能为空")
    @Pattern(regexp = "[12]", message = "审核状态必须为1或2")
    @ApiModelProperty(value = "审核状态", required = true, example = "1", notes = "1:已通过，2:不通过")
    private String reviewState;

    @ApiModelProperty(value = "审核备注", example = "审核通过，可以继续服务", notes = "审核意见或备注信息")
    private String remark;
}


--- File: play-admin/src/main/java/com/starry/admin/modules/order/module/vo/PlayOrderRewardQueryVo.java ---
package com.starry.admin.modules.order.module.vo;

import com.starry.common.domain.BasePageEntity;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import java.util.List;
import lombok.Data;
import lombok.EqualsAndHashCode;

/**
 * 打赏订单查询对象
 *
 * @author admin
 */
@EqualsAndHashCode(callSuper = true)
@Data
@ApiModel(value = "打赏订单查询参数", description = "查询打赏订单信息的条件参数")
public class PlayOrderRewardQueryVo extends BasePageEntity {
    /**
     * UUID
     */
    @ApiModelProperty(value = "订单ID", example = "1", notes = "特定订单的ID")
    private String id;

    /**
     * 顾客ID
     */
    @ApiModelProperty(value = "顾客ID", example = "1", notes = "打赏顾客的ID")
    private String purchaserBy;

    /**
     * 店员ID
     */
    @ApiModelProperty(value = "店员ID", example = "1", notes = "接收打赏的店员ID")
    private String acceptBy;

    /**
     * 打赏留言
     */
    @ApiModelProperty(value = "打赏留言", example = "感谢服务", notes = "打赏时的留言内容，支持模糊查询")
    private String rewardCon;

    /**
     * 打赏类型（0:打赏礼物,1:自定义金额）
     */
    @ApiModelProperty(value = "打赏类型", example = "0", notes = "0:打赏礼物,1:自定义金额")
    private String rewardType;

    /**
     * 礼物ID
     */
    @ApiModelProperty(value = "礼物ID", example = "1", notes = "打赏礼物的ID")
    private String commodityId;

    /**
     * 打赏时间
     */
    @ApiModelProperty(value = "打赏时间范围", example = "['2024-01-01 00:00:00','2024-12-31 23:59:59']", notes = "打赏时间范围，包含开始和结束时间")
    private List<String> purchaserTime;

    /**
     * 数据是否隐藏（0:显示，1:隐藏）
     */
    @ApiModelProperty(value = "是否隐藏", example = "0", notes = "数据是否隐藏，0:显示，1:隐藏")
    private String hidden;

}


--- File: play-admin/src/main/java/com/starry/admin/modules/order/module/vo/PlayOrderRewardReturnVo.java ---
package com.starry.admin.modules.order.module.vo;

import com.fasterxml.jackson.annotation.JsonFormat;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import lombok.Data;
import org.springframework.format.annotation.DateTimeFormat;

/**
 * 打赏订单查询返回信息
 *
 * @author admin
 */
@Data
@ApiModel(value = "打赏订单返回数据", description = "打赏订单信息的返回数据")
public class PlayOrderRewardReturnVo {

    /**
     * 订单ID
     */
    @ApiModelProperty(value = "订单ID", example = "1")
    private String id;

    /**
     * 用户ID
     */
    @ApiModelProperty(value = "顾客ID", example = "1")
    private String customId;

    /**
     * 用户昵称
     */
    @ApiModelProperty(value = "顾客昵称", example = "张三")
    private String customNickname;

    /**
     * 用户头像
     */
    @ApiModelProperty(value = "顾客头像", example = "https://example.com/avatar.jpg")
    private String customAvatar;

    /**
     * 店员ID
     */
    @ApiModelProperty(value = "店员ID", example = "1")
    private String clerkId;

    /**
     * 店员昵称
     */
    @ApiModelProperty(value = "店员昵称", example = "小明")
    private String clerkNickname;

    /**
     * 店员头像
     */
    @ApiModelProperty(value = "店员头像", example = "https://example.com/clerk_avatar.jpg")
    private String clerkAvatar;

    /**
     * 打赏类型
     */
    @ApiModelProperty(value = "打赏类型", example = "0", notes = "0:打赏礼物,1:自定义金额")
    private String rewardType;

    /**
     * 订单金额
     */
    @ApiModelProperty(value = "订单金额", example = "100.00")
    private String orderMoney;

    /**
     * 支付金额
     */
    @ApiModelProperty(value = "支付金额", example = "100.00")
    private String finalAmount;

    /**
     * 商品ID
     */
    @ApiModelProperty(value = "商品ID", example = "1")
    private String commodityId;

    /**
     * 商品类型[0:礼物，1：服务]
     */
    @ApiModelProperty(value = "商品类型", example = "0", notes = "0:礼物，1：服务")
    private String commodityType;

    /**
     * 商品名称
     */
    @ApiModelProperty(value = "商品名称", example = "爱心礼物")
    private String commodityName;
    /**
     * 商品单价
     */
    @ApiModelProperty(value = "商品单价", example = "50.00")
    private BigDecimal commodityPrice;

    /**
     * 服务时长
     *
     * @since 2024/5/8 16:44
     **/
    @ApiModelProperty(value = "服务时长", example = "30", notes = "服务的时长，单位分钟")
    private String serviceDuration;

    /**
     * 打赏留言
     */
    @ApiModelProperty(value = "打赏留言", example = "感谢服务")
    private String remark;

    /**
     * 打赏时间
     */
    @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
    @ApiModelProperty(value = "打赏时间", example = "2024-01-01 12:00:00")
    private LocalDateTime purchaserTime;

}


--- File: play-admin/src/main/java/com/starry/admin/modules/order/module/vo/PlayOrderStateEditVo.java ---
package com.starry.admin.modules.order.module.vo;

import java.util.ArrayList;
import java.util.List;
import javax.validation.constraints.NotBlank;
import lombok.Data;

/**
 * 订单状态修改对象
 *
 * @author admin
 * @since 2024-03-20
 */
@Data
public class PlayOrderStateEditVo {

    /**
     * 订单ID
     */
    @NotBlank(message = "订单ID不能为空")
    private String orderId;

    /**
     * 取消原因
     */
    @NotBlank(message = "取消原因不能为空")
    private String refundReason;

    private List<String> images = new ArrayList<>();

}


--- File: play-admin/src/main/java/com/starry/admin/modules/order/service/IPlayOrderComplaintInfoService.java ---
package com.starry.admin.modules.order.service;

import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.service.IService;
import com.starry.admin.modules.order.module.entity.PlayOrderComplaintInfoEntity;
import com.starry.admin.modules.order.module.vo.PlayOrderComplaintQueryVo;
import com.starry.admin.modules.order.module.vo.PlayOrderComplaintReturnVo;
import java.util.List;

/**
 * 订单投诉信息Service接口
 *
 * @author admin
 * @since 2024-05-07
 */
public interface IPlayOrderComplaintInfoService extends IService<PlayOrderComplaintInfoEntity> {
    /**
     * 查询订单投诉信息
     *
     * @param id
     *            订单投诉信息主键
     * @return 订单投诉信息
     */
    PlayOrderComplaintInfoEntity selectPlayOrderComplaintInfoById(String id);

    /**
     * 查询顾客对订单的评价内容
     *
     * @param customId
     *            顾客ID
     * @param orderId
     *            订单ID
     * @return boolean true:已评价，false:未评价
     **/
    PlayOrderComplaintInfoEntity queryCustomToOrderOrderComplaintInfo(String customId, String orderId);

    /**
     * 查询订单评价列表
     *
     * @param entity
     *            订单评价对象
     * @return 订单评价列表
     */
    List<PlayOrderComplaintInfoEntity> selectByList(PlayOrderComplaintInfoEntity entity);

    /**
     * 分页查询订单投诉列表
     *
     * @param vo
     *            订单投诉查询对象
     * @return 订单投诉列表
     */

    IPage<PlayOrderComplaintReturnVo> selectByPage(PlayOrderComplaintQueryVo vo);

    /**
     * 查询订单投诉信息列表
     *
     * @param playOrderComplaintInfo
     *            订单投诉信息
     * @return 订单投诉信息集合
     */
    IPage<PlayOrderComplaintInfoEntity> selectPlayOrderComplaintInfoByPage(
            PlayOrderComplaintInfoEntity playOrderComplaintInfo);

    /**
     * 新增订单投诉信息
     *
     * @param playOrderComplaintInfo
     *            订单投诉信息
     * @return 结果
     */
    boolean create(PlayOrderComplaintInfoEntity playOrderComplaintInfo);

    /**
     * 修改订单投诉信息
     *
     * @param playOrderComplaintInfo
     *            订单投诉信息
     * @return 结果
     */
    boolean update(PlayOrderComplaintInfoEntity playOrderComplaintInfo);

    /**
     * 批量删除订单投诉信息
     *
     * @param ids
     *            需要删除的订单投诉信息主键集合
     * @return 结果
     */
    int deletePlayOrderComplaintInfoByIds(String[] ids);

    /**
     * 删除订单投诉信息信息
     *
     * @param id
     *            订单投诉信息主键
     * @return 结果
     */
    int deletePlayOrderComplaintInfoById(String id);
}


--- File: play-admin/src/main/java/com/starry/admin/modules/order/service/IPlayOrderContinueInfoService.java ---
package com.starry.admin.modules.order.service;

import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.service.IService;
import com.starry.admin.modules.order.module.entity.PlayOrderContinueInfoEntity;
import com.starry.admin.modules.order.module.vo.PlayOrderContinueQueryVo;
import com.starry.admin.modules.order.module.vo.PlayOrderContinueReturnVo;
import com.starry.admin.modules.order.module.vo.PlayOrderReviewStateEditVo;

/**
 * 订单续单信息Service接口
 *
 * @author admin
 * @since 2024-05-10
 */
public interface IPlayOrderContinueInfoService extends IService<PlayOrderContinueInfoEntity> {

    /**
     * 根据订单ID查询订单续单信息
     *
     * @param orderId
     *            订单ID
     * @return 订单续单信息
     */
    PlayOrderContinueInfoEntity selectPlayOrderId(String orderId);

    /**
     * 查询订单续单信息
     *
     * @param id
     *            订单续单信息主键
     * @return 订单续单信息
     */
    PlayOrderContinueInfoEntity selectPlayOrderContinueInfoById(String id);

    /**
     * 分页查询订单续单对象
     *
     * @param vo
     *            订单续单对象查询对象
     * @return 订单续单信息集合
     */
    IPage<PlayOrderContinueReturnVo> selectPlayByPage(PlayOrderContinueQueryVo vo);

    /**
     * 修改续单审核状态
     *
     * @param vo
     *            续单审核申请对象
     */
    void updateReviewState(PlayOrderReviewStateEditVo vo);

    /**
     * 查询订单续单信息列表
     *
     * @param playOrderContinueInfo
     *            订单续单信息
     * @return 订单续单信息集合
     */
    IPage<PlayOrderContinueInfoEntity> selectPlayOrderContinueInfoByPage(
            PlayOrderContinueInfoEntity playOrderContinueInfo);

    /**
     * 新增订单续单信息
     *
     * @param playOrderContinueInfo
     *            订单续单信息
     * @return 结果
     */
    boolean create(PlayOrderContinueInfoEntity playOrderContinueInfo);

    /**
     * 修改订单续单信息
     *
     * @param playOrderContinueInfo
     *            订单续单信息
     * @return 结果
     */
    boolean update(PlayOrderContinueInfoEntity playOrderContinueInfo);

    /**
     * 批量删除订单续单信息
     *
     * @param ids
     *            需要删除的订单续单信息主键集合
     * @return 结果
     */
    int deletePlayOrderContinueInfoByIds(String[] ids);

    /**
     * 删除订单续单信息信息
     *
     * @param id
     *            订单续单信息主键
     * @return 结果
     */
    int deletePlayOrderContinueInfoById(String id);
}


--- File: play-admin/src/main/java/com/starry/admin/modules/order/service/IPlayOrderEvaluateInfoService.java ---
package com.starry.admin.modules.order.service;

import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.service.IService;
import com.starry.admin.modules.order.module.entity.PlayOrderEvaluateInfoEntity;
import com.starry.admin.modules.order.module.vo.PlayOrderEvaluateQueryVo;
import com.starry.admin.modules.order.module.vo.PlayOrderEvaluateReturnVo;
import java.util.List;

/**
 * 订单评价信息Service接口
 *
 * @author admin
 * @since 2024-05-07
 */
public interface IPlayOrderEvaluateInfoService extends IService<PlayOrderEvaluateInfoEntity> {

    /**
     * 查询订单评价信息
     *
     * @param id
     *            订单评价信息主键
     * @return 订单评价信息
     */
    PlayOrderEvaluateInfoEntity selectPlayOrderEvaluateInfoById(String id);

    /**
     * 查询顾客对订单的评价内容
     *
     * @param customId
     *            顾客ID
     * @param orderId
     *            订单ID
     * @return boolean true:已评价，false:未评价
     **/
    PlayOrderEvaluateInfoEntity queryCustomToOrderEvaluateInfo(String customId, String orderId);

    /**
     * 查询订单评价列表
     *
     * @param entity
     *            订单评价对象
     * @return 订单评价列表
     */
    List<PlayOrderEvaluateInfoEntity> selectByList(PlayOrderEvaluateInfoEntity entity);

    /**
     * 分页查询订单评价列表
     *
     * @param vo
     *            订单评价查询对象
     * @return 订单评价列表
     */
    IPage<PlayOrderEvaluateReturnVo> selectByPage(PlayOrderEvaluateQueryVo vo);

    /**
     * 查询订单评价信息列表
     *
     * @param playOrderEvaluateInfo
     *            订单评价信息
     * @return 订单评价信息集合
     */
    IPage<PlayOrderEvaluateInfoEntity> selectPlayOrderEvaluateInfoByPage(
            PlayOrderEvaluateInfoEntity playOrderEvaluateInfo);

    /**
     * 新增订单评价信息
     *
     * @param playOrderEvaluateInfo
     *            订单评价信息
     * @return 结果
     */
    boolean create(PlayOrderEvaluateInfoEntity playOrderEvaluateInfo);

    /**
     * 修改订单评价信息
     *
     * @param playOrderEvaluateInfo
     *            订单评价信息
     * @return 结果
     */
    boolean update(PlayOrderEvaluateInfoEntity playOrderEvaluateInfo);

    /**
     * 批量删除订单评价信息
     *
     * @param ids
     *            需要删除的订单评价信息主键集合
     * @return 结果
     */
    int deletePlayOrderEvaluateInfoByIds(String[] ids);

    /**
     * 删除订单评价信息信息
     *
     * @param id
     *            订单评价信息主键
     * @return 结果
     */
    int deletePlayOrderEvaluateInfoById(String id);
}


--- File: play-admin/src/main/java/com/starry/admin/modules/order/service/IPlayOrderInfoService.java ---
package com.starry.admin.modules.order.service;

import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.service.IService;
import com.starry.admin.modules.order.module.entity.PlayOrderInfoEntity;
import com.starry.admin.modules.order.module.vo.*;
import com.starry.admin.modules.weichat.entity.order.*;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.List;
import org.springframework.stereotype.Service;

/**
 * 订单Service接口
 *
 * @author admin
 * @since 2024-03-20
 */
@Service
public interface IPlayOrderInfoService extends IService<PlayOrderInfoEntity> {

    /**
     * 查询当前租户店员总数
     *
     * @param tenantId
     *            租户ID
     * @return 店员总数
     */
    List<PlayOrderInfoEntity> getTotalOrderInfo(String tenantId);

    /**
     * 新增充值单
     *
     * @param orderNo
     *            订单编号
     * @param orderMoney
     *            订单金额
     * @param finalAmount
     *            订单最终金额（支付金额）
     * @param purchaserBy
     *            下单人ID
     */
    void createRechargeOrder(String orderNo, BigDecimal orderMoney, BigDecimal finalAmount, String purchaserBy);

    /**
     * 新增订单信息
     *
     * @param orderId
     *            订单ID
     * @param orderNo
     *            订单编号
     * @param orderState
     *            订单状态【0:已下单（待接单）;1:已接单（待开始）;2:已开始（服务中）;3;已完成:4:已取消】
     * @param orderType
     *            订单类型【-1:退款订单;0:充值订单；1:提现订单；2:普通订单】
     * @param placeType
     *            下单类型（-1:其他类型;0:指定单;1:随机单;2:打赏单）
     * @param rewardType
     *            （0:余额;1:礼物）
     * @param firstOrder
     *            是否是首单【0：不是，1：是】
     * @param commodityId
     *            商品ID
     * @param commodityType
     *            商品类型[0:礼物，1：服务]
     * @param commodityPrice
     *            商品属性-商品单价
     * @param serviceDuration
     *            商品属性-服务时长
     * @param commodityName
     *            商品名称
     * @param commodityNumber
     *            商品数量
     * @param orderMoney
     *            订单金额
     * @param finalAmount
     *            订单最终金额（支付金额）
     * @param discountAmount
     *            优惠金额
     * @param purchaserBy
     *            下单人
     * @param acceptBy
     *            接单人
     * @param weiChatCode
     *            订单微信号码
     * @param couponIds
     *            优惠券ID列表
     * @param remark
     *            订单备注
     * @param clerkSex
     *            随机单要求-店员性别（0:未知;1:男;2:女）
     * @param clerkLevelId
     *            随机单要求-店员等级ID
     * @param excludeHistory
     *            随机单要求-是否排除下单过的成员（0:不排除;1:排除）
     * @author admin
     * @since 2024/6/3 10:53
     **/
    void createOrderInfo(String orderId, String orderNo, String orderState, String orderType, String placeType,
            String rewardType, String firstOrder, String commodityId, String commodityType, BigDecimal commodityPrice,
            String serviceDuration, String commodityName, String commodityNumber, BigDecimal orderMoney,
            BigDecimal finalAmount, BigDecimal discountAmount, String purchaserBy, String acceptBy, String weiChatCode,
            List<String> couponIds, String remark, String clerkSex, String clerkLevelId, String excludeHistory);

    /**
     * 据店员等级和订单金额，获取店员预计收入
     *
     * @param clerkId
     *            店员ID
     * @param croupIds
     *            优惠券ID列表
     * @param placeType
     *            下单类型（-1:其他类型;0:指定单;1:随机单;2:打赏单）
     * @param firstOrder
     *            是否是首单【0：不是，1：是】
     * @param finalAmount
     *            订单支付金额
     * @return com.starry.admin.modules.order.module.vo.ClerkEstimatedRevenueVo
     * @author admin
     * @since 2024/7/18 16:39
     **/
    ClerkEstimatedRevenueVo getClerkEstimatedRevenue(String clerkId, List<String> croupIds, String placeType,
            String firstOrder, BigDecimal finalAmount);

    /**
     * 根据店员等级和订单金额，获取店员预计收入
     *
     * @param clerkId
     *            店员ID
     * @param placeType
     *            下单类型（-1:其他类型;0:指定单;1:随机单;2:打赏单）
     * @param firstOrder
     *            是否是首单【0：不是，1：是】
     * @param finalAmount
     *            订单支付金额
     * @return math.BigDecimal 店员预计收入
     * @author admin
     * @since 2024/6/3 11:12
     **/
    BigDecimal getEstimatedRevenue(String clerkId, String placeType, String firstOrder, BigDecimal finalAmount);

    /**
     * 根据店员等级，获取店员提成比例
     *
     * @param clerkId
     *            店员ID
     * @param placeType
     *            下单类型（-1:其他类型;0:指定单;1:随机单;2:打赏单）
     * @param firstOrder
     *            是否是首单【0：不是，1：是】
     * @return math.BigDecimal 店员预计收入
     * @author admin
     * @since 2024/6/3 11:12
     **/
    Integer getEstimatedRevenueRatio(String clerkId, String placeType, String firstOrder);

    /**
     * 根据订单结算状态查询订单
     *
     * @param clerkId
     *            店员ID
     * @param orderSettlementState
     *            订单结算状态(0:未结算;1:已结算)
     * @return List<PlayOrderInfoEntity>
     */
    List<PlayOrderInfoEntity> queryBySettlementOrder(String clerkId, String orderSettlementState);

    /**
     * 查询指定店员在指定时间前完成，并且未结算工资的订单
     *
     * @param clerkId
     *            店员ID
     * @param endTime
     *            结束时间
     * @return List<PlayOrderInfoEntity>
     */
    List<PlayOrderInfoEntity> listByEndTime(String clerkId, LocalDateTime endTime);

    /**
     * 查询指定店员在一定时间内的订单
     *
     * @param clerkId
     *            店员ID
     * @param startTime
     *            开始时间
     * @param endTime
     *            结束时间
     * @param orderStatus
     *            店员状态列表
     * @return List<PlayOrderInfoEntity>
     */
    List<PlayOrderInfoEntity> listByTime(String clerkId, String startTime, String endTime, List<String> orderStatus);

    /**
     * 分页查询打赏订单
     *
     * @param vo
     *            打赏订单查询对象
     * @return 打赏订单
     */
    IPage<PlayOrderRewardReturnVo> selectRewardOrderInfoByPage(PlayOrderRewardQueryVo vo);

    /**
     * 根据订单ID查询订单信息
     *
     * @param orderId
     *            订单ID
     * @return 订单信息
     */
    PlayOrderDetailsReturnVo selectById(String orderId);

    /**
     * 查询订单
     *
     * @param id
     *            订单主键
     * @return 订单
     */
    PlayOrderInfoEntity selectOrderInfoById(String id);

    /**
     * 查询订单列表
     *
     * @param orderInfoEntity
     *            订单
     * @return 订单集合
     */
    IPage<PlayOrderInfoReturnVo> selectOrderInfoPage(PlayOrderInfoQueryVo orderInfoEntity);

    /**
     * 店员查询本人订单详情
     *
     * @param clerkId
     *            店员ID
     * @param id
     *            订单ID
     * @return 订单详情
     */

    PlayClerkOrderDetailsReturnVo clerkSelectOrderDetails(String clerkId, String id);

    /**
     * 店员分页查询订单本人列表
     *
     * @param vo
     *            订单列表查询对象
     * @return 订单集合
     */
    IPage<PlayClerkOrderListReturnVo> clerkSelectOrderInfoByPage(PlayClerkOrderInfoQueryVo vo);

    /**
     * 店员查询自己一段时间内的订单
     *
     * @param clerkId
     *            店员ID
     * @param startTime
     *            开始时间
     * @param endTime
     *            结束时间
     * @return 订单列表
     */
    List<PlayOrderInfoEntity> clerkSelectOrderInfoList(String clerkId, String startTime, String endTime);

    /**
     * 顾客查询订单详情
     *
     * @param customId
     *            顾客ID
     * @param orderId
     *            订单ID
     * @return 订单详情
     */
    PlayCustomOrderDetailsReturnVo customSelectOrderDetails(String customId, String orderId);

    /**
     * 顾客分页查询订单列表
     *
     * @param vo
     *            订单列表查询对象
     * @return 订单集合
     */
    IPage<PlayCustomOrderListReturnVo> customSelectOrderInfoByPage(PlayCustomOrderInfoQueryVo vo);

    /**
     * 顾客查询本人订单列表
     *
     * @param customId
     *            顾客ID
     * @return 订单集合
     */
    List<PlayOrderInfoEntity> customSelectOrderInfoByList(String customId);

    /**
     * 修改订单状态为接单 只有管理员或者店员本人才能操作
     *
     * @param operatorByType
     *            操作人类型（0:顾客;1:店员;2:管理员）
     * @param operatorBy
     *            操作人ID
     * @param acceptBy
     *            接单人ID
     * @param orderId
     *            订单Id
     **/
    void updateStateTo1(String operatorByType, String operatorBy, String acceptBy, String orderId);

    /**
     * 修改订单状态为开始订单或者完成订单 只有管理员或者店员本人才能操作
     *
     * @param operatorByType
     *            操作人类型（0:顾客;1:店员;2:管理员）
     * @param operatorBy
     *            操作人ID
     * @param orderState
     *            订单状态
     * @param orderId
     *            订单Id
     **/
    void updateStateTo23(String operatorByType, String operatorBy, String orderState, String orderId);

    /**
     * 修改订单状态为取消订单 管理员、店员、顾客均可操作
     *
     * @param operatorByType
     *            操作人类型（0:顾客;1:店员;2:管理员）
     * @param operatorBy
     *            操作人ID
     * @param orderId
     *            订单Id
     * @param refundReason
     *            订单取消原因
     * @param images
     *            证据图片列表
     **/
    void updateStateTo4(String operatorByType, String operatorBy, String orderId, String refundReason,
            List<String> images);

    /**
     * 修改订单
     *
     * @param orderInfoEntity
     *            订单
     * @return 结果
     */
    boolean update(PlayOrderInfoEntity orderInfoEntity);

    /**
     * 批量删除订单
     *
     * @param ids
     *            需要删除的订单主键集合
     * @return 结果
     */
    int deleteOrderInfoByIds(String[] ids);

    /**
     * 根据订单编号查询订单信息
     *
     * @param orderNo
     *            订单编号
     * @return 订单信息
     */
    PlayOrderInfoEntity queryByOrderNo(String orderNo);

    /**
     * 删除订单信息
     *
     * @param id
     *            订单主键
     * @return 结果
     */
    int deleteOrderInfoById(String id);

    /**
     * 获得订单编号 订单编号组成规则为 YYYYMMHH+4位随机数字+当日的订单顺序
     *
     * @return 订单编号
     */
    String getOrderNo();

    /**
     * 查询未接单的订单
     *
     * @param vo
     *            查询对象
     * @param clerkId
     *            店员ID
     * @return 订单列表
     */
    IPage<PlayOrderInfoEntity> selectRandomOrderByPage(PlayOrderInfoRandomQueryVo vo, String clerkId);

    /**
     * 分页获取打赏动态
     *
     * @param vo
     *            查询对象
     * @return 打赏动态列表
     */
    IPage<PlayRewardInfoReturnVo> selectRewardByPage(PlayRewardOrderQueryVo vo);

}


--- File: play-admin/src/main/java/com/starry/admin/modules/order/service/IPlayOrderRefundInfoService.java ---
package com.starry.admin.modules.order.service;

import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.service.IService;
import com.starry.admin.modules.order.module.entity.PlayOrderRefundInfoEntity;
import com.starry.admin.modules.order.module.vo.PlayOrderRefundQueryVo;
import com.starry.admin.modules.order.module.vo.PlayOrderRefundReturnVo;
import java.math.BigDecimal;

/**
 * 订单退款信息Service接口
 *
 * @author admin
 * @since 2024-05-09
 */
public interface IPlayOrderRefundInfoService extends IService<PlayOrderRefundInfoEntity> {
    /**
     * 查询订单退款信息
     *
     * @param id
     *            订单退款信息主键
     * @return 订单退款信息
     */
    PlayOrderRefundInfoEntity selectPlayOrderRefundInfoById(String id);

    /**
     * 根据订单ID查询订单退款信息
     *
     * @param orderId
     *            订单ID
     * @return 订单退款信息
     */
    PlayOrderRefundInfoEntity selectPlayOrderRefundInfoByOrderId(String orderId);

    /**
     * 分页查询订单退款信息
     *
     * @param vo
     *            订单退款信息
     * @return 订单退款信息集合
     */
    IPage<PlayOrderRefundReturnVo> selectByPage(PlayOrderRefundQueryVo vo);

    /**
     * 查询订单退款信息列表
     *
     * @param playOrderRefundInfo
     *            订单退款信息
     * @return 订单退款信息集合
     */
    IPage<PlayOrderRefundInfoEntity> selectPlayOrderRefundInfoByPage(PlayOrderRefundInfoEntity playOrderRefundInfo);

    /**
     * 新增退款信息
     *
     * @param orderId
     *            订单ID
     * @param customId
     *            顾客ID
     * @param clerkId
     *            店员ID
     * @param payMethod
     *            支付方式，[0:余额支付;1:微信支付,2:支付宝支付]
     * @param refundType
     *            退款类型【0:部分退款;1:全部退款】
     * @param refundAmount
     *            退款金额
     * @param refundReason
     *            退款原因
     * @param refundByType
     *            退款人类型[0:顾客;1:店员;2:管理员]
     * @param refundById
     *            退款人ID
     * @param refundState
     *            退款状态[-1:异常;0:处理中;1:成功;2:关闭]
     * @param reviewedRequired
     *            是否需要审核，0:不需要，1:需要
     * @author xuhq
     * @since 2024/8/23 17:10
     **/
    void add(String orderId, String customId, String clerkId, String payMethod, String refundType,
            BigDecimal refundAmount, String refundReason, String refundByType, String refundById, String refundState,
            String reviewedRequired);

    /**
     * 新增订单退款信息
     *
     * @param playOrderRefundInfo
     *            订单退款信息
     * @return 结果
     */
    boolean create(PlayOrderRefundInfoEntity playOrderRefundInfo);

    /**
     * 修改订单退款信息
     *
     * @param playOrderRefundInfo
     *            订单退款信息
     * @return 结果
     */
    boolean update(PlayOrderRefundInfoEntity playOrderRefundInfo);

    /**
     * 批量删除订单退款信息
     *
     * @param ids
     *            需要删除的订单退款信息主键集合
     * @return 结果
     */
    int deletePlayOrderRefundInfoByIds(String[] ids);

    /**
     * 删除订单退款信息信息
     *
     * @param id
     *            订单退款信息主键
     * @return 结果
     */
    int deletePlayOrderRefundInfoById(String id);
}


--- File: play-admin/src/main/java/com/starry/admin/modules/order/service/impl/PlayOrderComplaintInfoServiceImpl.java ---
package com.starry.admin.modules.order.service.impl;

import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.github.yulichang.wrapper.MPJLambdaWrapper;
import com.starry.admin.common.exception.CustomException;
import com.starry.admin.modules.clerk.module.entity.PlayClerkUserInfoEntity;
import com.starry.admin.modules.custom.module.entity.PlayCustomUserInfoEntity;
import com.starry.admin.modules.order.mapper.PlayOrderComplaintInfoMapper;
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.PlayOrderInfoEntity;
import com.starry.admin.modules.order.module.vo.PlayOrderComplaintQueryVo;
import com.starry.admin.modules.order.module.vo.PlayOrderComplaintReturnVo;
import com.starry.admin.modules.order.service.IPlayOrderComplaintInfoService;
import com.starry.admin.modules.personnel.service.IPlayPersonnelGroupInfoService;
import com.starry.admin.modules.weichat.service.WxCustomMpService;
import com.starry.admin.utils.SecurityUtils;
import com.starry.common.utils.IdUtils;
import java.util.Arrays;
import java.util.List;
import javax.annotation.Resource;
import org.springframework.stereotype.Service;

/**
 * 订单投诉信息Service业务层处理
 *
 * @author admin
 * @since 2024-05-07
 */
@Service
public class PlayOrderComplaintInfoServiceImpl
        extends
            ServiceImpl<PlayOrderComplaintInfoMapper, PlayOrderComplaintInfoEntity>
        implements
            IPlayOrderComplaintInfoService {
    @Resource
    private PlayOrderComplaintInfoMapper playOrderComplaintInfoMapper;

    @Resource
    private PlayOrderInfoServiceImpl playOrderInfoService;

    @Resource
    private IPlayPersonnelGroupInfoService playClerkGroupInfoService;
    @Resource
    private WxCustomMpService wxCustomMpService;

    /**
     * 查询订单投诉信息
     *
     * @param id
     *            订单投诉信息主键
     * @return 订单投诉信息
     */
    @Override
    public PlayOrderComplaintInfoEntity selectPlayOrderComplaintInfoById(String id) {
        return this.baseMapper.selectById(id);
    }

    @Override
    public PlayOrderComplaintInfoEntity queryCustomToOrderOrderComplaintInfo(String customId, String orderId) {
        LambdaQueryWrapper<PlayOrderComplaintInfoEntity> lambdaQueryWrapper = new LambdaQueryWrapper<>();
        lambdaQueryWrapper.eq(PlayOrderComplaintInfoEntity::getCustomId, customId);
        lambdaQueryWrapper.eq(PlayOrderComplaintInfoEntity::getOrderId, orderId);
        return this.baseMapper.selectOne(lambdaQueryWrapper);
    }

    @Override
    public List<PlayOrderComplaintInfoEntity> selectByList(PlayOrderComplaintInfoEntity entity) {
        LambdaQueryWrapper<PlayOrderComplaintInfoEntity> lambdaQueryWrapper = new LambdaQueryWrapper<>();
        if (StrUtil.isNotBlank(entity.getClerkId())) {
            lambdaQueryWrapper.eq(PlayOrderComplaintInfoEntity::getClerkId, entity.getClerkId());
        }
        if (StrUtil.isNotBlank(entity.getCustomId())) {
            lambdaQueryWrapper.eq(PlayOrderComplaintInfoEntity::getCustomId, entity.getCustomId());
        }
        if (StrUtil.isNotBlank(entity.getOrderId())) {
            lambdaQueryWrapper.eq(PlayOrderComplaintInfoEntity::getOrderId, entity.getOrderId());
        }
        return this.baseMapper.selectList(lambdaQueryWrapper);
    }

    @Override
    public IPage<PlayOrderComplaintReturnVo> selectByPage(PlayOrderComplaintQueryVo vo) {
        MPJLambdaWrapper<PlayOrderComplaintInfoEntity> lambdaQueryWrapper = new MPJLambdaWrapper<PlayOrderComplaintInfoEntity>();
        // 查询主表全部字段
        lambdaQueryWrapper.selectAll(PlayOrderComplaintInfoEntity.class);
        // 查询订单表
        lambdaQueryWrapper.selectAs(PlayOrderInfoEntity::getOrderNo, "orderNo")
                .selectAs(PlayOrderInfoEntity::getPlaceType, "placeType")
                .selectAs(PlayOrderInfoEntity::getFinalAmount, "finalAmount")
                .selectAs(PlayOrderInfoEntity::getCommodityPrice, "commodityPrice")
                .selectAs(PlayOrderInfoEntity::getServiceDuration, "serviceDuration")
                .selectAs(PlayOrderInfoEntity::getCommodityName, "commodityName")
                .selectAs(PlayOrderInfoEntity::getCommodityType, "commodityType")
                .selectAs(PlayOrderInfoEntity::getCommodityId, "commodityId")
                .selectAs(PlayOrderInfoEntity::getCommodityNumber, "commodityNumber");
        lambdaQueryWrapper.leftJoin(PlayOrderInfoEntity.class, PlayOrderInfoEntity::getId,
                PlayOrderEvaluateInfoEntity::getOrderId);
        // 查询顾客表字段
        lambdaQueryWrapper.selectAs(PlayCustomUserInfoEntity::getId, "customId")
                .selectAs(PlayCustomUserInfoEntity::getAvatar, "customAvatar")
                .selectAs(PlayCustomUserInfoEntity::getNickname, "customNickname");
        lambdaQueryWrapper.leftJoin(PlayCustomUserInfoEntity.class, PlayCustomUserInfoEntity::getId,
                PlayOrderComplaintInfoEntity::getCustomId);
        // 查询陪聊表
        lambdaQueryWrapper.selectAs(PlayClerkUserInfoEntity::getId, "clerkId")
                .selectAs(PlayClerkUserInfoEntity::getAvatar, "clerkAvatar")
                .selectAs(PlayClerkUserInfoEntity::getNickname, "clerkNickname");
        lambdaQueryWrapper.leftJoin(PlayClerkUserInfoEntity.class, PlayClerkUserInfoEntity::getId,
                PlayOrderComplaintInfoEntity::getClerkId);
        if (StrUtil.isNotBlank(vo.getClerkId())) {
            lambdaQueryWrapper.eq(PlayOrderComplaintInfoEntity::getClerkId, vo.getClerkId());
        }
        if (StrUtil.isNotBlank(vo.getCustomId())) {
            lambdaQueryWrapper.eq(PlayOrderComplaintInfoEntity::getCustomId, vo.getCustomId());
        }
        if (StrUtil.isNotBlank(vo.getOrderNo())) {
            lambdaQueryWrapper.eq(PlayOrderInfoEntity::getOrderNo, vo.getOrderNo());
        }
        if (StrUtil.isNotBlank(vo.getWeiChatCode())) {
            lambdaQueryWrapper.eq(PlayOrderComplaintInfoEntity::getWxChatCode, vo.getWeiChatCode());
        }
        if (vo.getComplaintTime() != null && vo.getComplaintTime().size() == 2) {
            lambdaQueryWrapper.between(PlayOrderComplaintInfoEntity::getComplaintTime, vo.getComplaintTime().get(0),
                    vo.getComplaintTime().get(1));
        }
        // 加入组员的筛选
        List<String> clerkIdList = playClerkGroupInfoService.getValidClerkIdList(SecurityUtils.getLoginUser(), null);
        lambdaQueryWrapper.in(PlayOrderComplaintInfoEntity::getClerkId, clerkIdList);

        return this.baseMapper.selectJoinPage(new Page<>(vo.getPageNum(), vo.getPageSize()),
                PlayOrderComplaintReturnVo.class, lambdaQueryWrapper);
    }

    /**
     * 查询订单投诉信息列表
     *
     * @param playOrderComplaintInfo
     *            订单投诉信息
     * @return 订单投诉信息
     */
    @Override
    public IPage<PlayOrderComplaintInfoEntity> selectPlayOrderComplaintInfoByPage(
            PlayOrderComplaintInfoEntity playOrderComplaintInfo) {
        Page<PlayOrderComplaintInfoEntity> page = new Page<>(1, 10);
        return this.baseMapper.selectPage(page, new LambdaQueryWrapper<>());
    }

    /**
     * 新增订单投诉信息
     *
     * @param playOrderComplaintInfo
     *            订单投诉信息
     * @return 结果
     */
    @Override
    public boolean create(PlayOrderComplaintInfoEntity playOrderComplaintInfo) {
        if (allowComplaint(playOrderComplaintInfo.getCustomId(), playOrderComplaintInfo.getOrderId())) {
            throw new CustomException("一个订单只能投诉一次");
        }
        if (StrUtil.isBlankIfStr(playOrderComplaintInfo.getId())) {
            playOrderComplaintInfo.setId(IdUtils.getUuid());
        }
        PlayOrderInfoEntity orderInfo = playOrderInfoService.selectOrderInfoById(playOrderComplaintInfo.getOrderId());
        playOrderComplaintInfo.setClerkId(orderInfo.getAcceptBy());
        playOrderComplaintInfo.setCommodityId(orderInfo.getCommodityId());
        // 发送投诉消息给管理员以及客服
        wxCustomMpService.sendComplaintMessage(playOrderComplaintInfo, orderInfo);
        return save(playOrderComplaintInfo);
    }

    /**
     * 当前订单是否已投诉
     *
     * @param customerId
     *            顾客ID
     * @param orderId
     *            订单ID
     * @return true:订单已评价，false:订单未评价
     */
    public boolean allowComplaint(String customerId, String orderId) {
        LambdaQueryWrapper<PlayOrderComplaintInfoEntity> lambdaQueryWrapper = new LambdaQueryWrapper<>();
        lambdaQueryWrapper.eq(PlayOrderComplaintInfoEntity::getCustomId, customerId);
        lambdaQueryWrapper.eq(PlayOrderComplaintInfoEntity::getOrderId, orderId);
        return this.baseMapper.selectOne(lambdaQueryWrapper) != null;

    }

    /**
     * 修改订单投诉信息
     *
     * @param playOrderComplaintInfo
     *            订单投诉信息
     * @return 结果
     */
    @Override
    public boolean update(PlayOrderComplaintInfoEntity playOrderComplaintInfo) {
        return updateById(playOrderComplaintInfo);
    }

    /**
     * 批量删除订单投诉信息
     *
     * @param ids
     *            需要删除的订单投诉信息主键
     * @return 结果
     */
    @Override
    public int deletePlayOrderComplaintInfoByIds(String[] ids) {
        return playOrderComplaintInfoMapper.deleteBatchIds(Arrays.asList(ids));
    }

    /**
     * 删除订单投诉信息信息
     *
     * @param id
     *            订单投诉信息主键
     * @return 结果
     */
    @Override
    public int deletePlayOrderComplaintInfoById(String id) {
        return playOrderComplaintInfoMapper.deleteById(id);
    }
}


--- File: play-admin/src/main/java/com/starry/admin/modules/order/service/impl/PlayOrderContinueInfoServiceImpl.java ---
package com.starry.admin.modules.order.service.impl;

import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.github.yulichang.wrapper.MPJLambdaWrapper;
import com.starry.admin.common.exception.CustomException;
import com.starry.admin.modules.clerk.module.entity.PlayClerkUserInfoEntity;
import com.starry.admin.modules.order.mapper.PlayOrderContinueInfoMapper;
import com.starry.admin.modules.order.module.entity.PlayOrderContinueInfoEntity;
import com.starry.admin.modules.order.module.entity.PlayOrderInfoEntity;
import com.starry.admin.modules.order.module.vo.PlayOrderContinueQueryVo;
import com.starry.admin.modules.order.module.vo.PlayOrderContinueReturnVo;
import com.starry.admin.modules.order.module.vo.PlayOrderReviewStateEditVo;
import com.starry.admin.modules.order.service.IPlayOrderContinueInfoService;
import com.starry.admin.modules.personnel.service.IPlayPersonnelGroupInfoService;
import com.starry.admin.utils.SecurityUtils;
import com.starry.common.context.CustomSecurityContextHolder;
import com.starry.common.utils.IdUtils;
import java.time.LocalDateTime;
import java.util.Arrays;
import java.util.List;
import javax.annotation.Resource;
import org.springframework.stereotype.Service;

/**
 * 订单续单信息Service业务层处理
 *
 * @author admin
 * @since 2024-05-10
 */
@Service
public class PlayOrderContinueInfoServiceImpl
        extends
            ServiceImpl<PlayOrderContinueInfoMapper, PlayOrderContinueInfoEntity>
        implements
            IPlayOrderContinueInfoService {
    @Resource
    private PlayOrderContinueInfoMapper playOrderContinueInfoMapper;

    @Resource
    private IPlayPersonnelGroupInfoService playClerkGroupInfoService;

    @Override
    public PlayOrderContinueInfoEntity selectPlayOrderId(String orderId) {
        MPJLambdaWrapper<PlayOrderContinueInfoEntity> lambdaQueryWrapper = new MPJLambdaWrapper<>();
        lambdaQueryWrapper.eq(PlayOrderContinueInfoEntity::getOrderId, orderId);
        return playOrderContinueInfoMapper.selectOne(lambdaQueryWrapper);
    }

    /**
     * 查询订单续单信息
     *
     * @param id
     *            订单续单信息主键
     * @return 订单续单信息
     */
    @Override
    public PlayOrderContinueInfoEntity selectPlayOrderContinueInfoById(String id) {
        PlayOrderContinueInfoEntity entity = this.baseMapper.selectById(id);
        if (entity != null) {
            return entity;
        }
        throw new CustomException("续单申请不存在");
    }

    @Override
    public void updateReviewState(PlayOrderReviewStateEditVo vo) {
        PlayOrderContinueInfoEntity entity = this.selectPlayOrderContinueInfoById(vo.getId());
        entity.setReviewedState(vo.getReviewState());
        entity.setReviewedRemark(vo.getRemark());
        entity.setReviewedBy(CustomSecurityContextHolder.getUserId());
        entity.setReviewedTime(LocalDateTime.now());
        this.baseMapper.updateById(entity);
        // 添加订单信息
        if ("1".equals(vo.getReviewState())) {

        }
    }

    @Override
    public IPage<PlayOrderContinueReturnVo> selectPlayByPage(PlayOrderContinueQueryVo vo) {
        MPJLambdaWrapper<PlayOrderContinueInfoEntity> lambdaQueryWrapper = new MPJLambdaWrapper<>();
        // 主表
        lambdaQueryWrapper.selectAll(PlayOrderContinueInfoEntity.class);
        lambdaQueryWrapper.selectAs(PlayClerkUserInfoEntity::getId, "clerkId")
                .selectAs(PlayClerkUserInfoEntity::getAvatar, "clerkAvatar")
                .selectAs(PlayClerkUserInfoEntity::getNickname, "clerkNickname")
                .selectAs(PlayClerkUserInfoEntity::getSex, "clerkSex");
        lambdaQueryWrapper.leftJoin(PlayClerkUserInfoEntity.class, PlayClerkUserInfoEntity::getId,
                PlayOrderContinueInfoEntity::getClerkId);
        lambdaQueryWrapper.leftJoin(PlayOrderInfoEntity.class, PlayOrderInfoEntity::getId,
                PlayOrderContinueInfoEntity::getOrderId);
        if (StrUtil.isNotBlank(vo.getCustomId())) {
            lambdaQueryWrapper.like(PlayOrderContinueInfoEntity::getCustomId, vo.getCustomId());
        }
        if (StrUtil.isNotBlank(vo.getClerkId())) {
            lambdaQueryWrapper.like(PlayClerkUserInfoEntity::getId, vo.getClerkId());
        }
        if (StrUtil.isNotBlank(vo.getOrderNo())) {
            lambdaQueryWrapper.like(PlayClerkUserInfoEntity::getNickname, vo.getNickname());
        }
        if (StrUtil.isNotBlank(vo.getReviewState())) {
            lambdaQueryWrapper.like(PlayOrderContinueInfoEntity::getReviewedState, vo.getReviewState());
        }
        if (vo.getContinueTime() != null && vo.getContinueTime().size() == 2) {
            lambdaQueryWrapper.between(PlayOrderContinueInfoEntity::getContinueTime, vo.getContinueTime().get(0),
                    vo.getContinueTime().get(1));
        }
        if (vo.getContinueTime() != null && vo.getContinueTime().size() == 2) {
            lambdaQueryWrapper.between(PlayOrderContinueInfoEntity::getContinueTime, vo.getContinueTime().get(0),
                    vo.getContinueTime().get(1));
        }
        // 加入组员的筛选
        List<String> clerkIdList = playClerkGroupInfoService.getValidClerkIdList(SecurityUtils.getLoginUser(), null);
        lambdaQueryWrapper.in(PlayOrderContinueInfoEntity::getClerkId, clerkIdList);

        return this.baseMapper.selectJoinPage(new Page<>(vo.getPageNum(), vo.getPageSize()),
                PlayOrderContinueReturnVo.class, lambdaQueryWrapper);
    }

    /**
     * 查询订单续单信息列表
     *
     * @param playOrderContinueInfo
     *            订单续单信息
     * @return 订单续单信息
     */
    @Override
    public IPage<PlayOrderContinueInfoEntity> selectPlayOrderContinueInfoByPage(
            PlayOrderContinueInfoEntity playOrderContinueInfo) {
        Page<PlayOrderContinueInfoEntity> page = new Page<>(1, 10);
        return this.baseMapper.selectPage(page, new LambdaQueryWrapper<>());
    }

    /**
     * 新增订单续单信息
     *
     * @param playOrderContinueInfo
     *            订单续单信息
     * @return 结果
     */
    @Override
    public boolean create(PlayOrderContinueInfoEntity playOrderContinueInfo) {
        if (StrUtil.isBlankIfStr(playOrderContinueInfo.getId())) {
            playOrderContinueInfo.setId(IdUtils.getUuid());
        }
        return save(playOrderContinueInfo);
    }

    /**
     * 修改订单续单信息
     *
     * @param playOrderContinueInfo
     *            订单续单信息
     * @return 结果
     */
    @Override
    public boolean update(PlayOrderContinueInfoEntity playOrderContinueInfo) {
        return updateById(playOrderContinueInfo);
    }

    /**
     * 批量删除订单续单信息
     *
     * @param ids
     *            需要删除的订单续单信息主键
     * @return 结果
     */
    @Override
    public int deletePlayOrderContinueInfoByIds(String[] ids) {
        return playOrderContinueInfoMapper.deleteBatchIds(Arrays.asList(ids));
    }

    /**
     * 删除订单续单信息信息
     *
     * @param id
     *            订单续单信息主键
     * @return 结果
     */
    @Override
    public int deletePlayOrderContinueInfoById(String id) {
        return playOrderContinueInfoMapper.deleteById(id);
    }
}


--- File: play-admin/src/main/java/com/starry/admin/modules/order/service/impl/PlayOrderEvaluateInfoServiceImpl.java ---
package com.starry.admin.modules.order.service.impl;

import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.github.yulichang.wrapper.MPJLambdaWrapper;
import com.starry.admin.common.exception.CustomException;
import com.starry.admin.modules.clerk.module.entity.PlayClerkUserInfoEntity;
import com.starry.admin.modules.custom.module.entity.PlayCustomUserInfoEntity;
import com.starry.admin.modules.order.mapper.PlayOrderEvaluateInfoMapper;
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.vo.PlayOrderEvaluateQueryVo;
import com.starry.admin.modules.order.module.vo.PlayOrderEvaluateReturnVo;
import com.starry.admin.modules.order.service.IPlayOrderEvaluateInfoService;
import com.starry.admin.modules.personnel.service.IPlayPersonnelGroupInfoService;
import com.starry.admin.utils.SecurityUtils;
import com.starry.common.utils.IdUtils;
import com.starry.common.utils.StringUtils;
import java.util.Arrays;
import java.util.List;
import javax.annotation.Resource;
import org.springframework.stereotype.Service;

/**
 * 订单评价信息Service业务层处理
 *
 * @author admin
 * @since 2024-05-07
 */
@Service
public class PlayOrderEvaluateInfoServiceImpl
        extends
            ServiceImpl<PlayOrderEvaluateInfoMapper, PlayOrderEvaluateInfoEntity>
        implements
            IPlayOrderEvaluateInfoService {
    @Resource
    private PlayOrderEvaluateInfoMapper playOrderEvaluateInfoMapper;

    @Resource
    private PlayOrderInfoServiceImpl playOrderInfoService;

    @Resource
    private IPlayPersonnelGroupInfoService playClerkGroupInfoService;

    @Override
    public PlayOrderEvaluateInfoEntity queryCustomToOrderEvaluateInfo(String customId, String orderId) {
        LambdaQueryWrapper<PlayOrderEvaluateInfoEntity> lambdaQueryWrapper = new LambdaQueryWrapper<>();
        lambdaQueryWrapper.eq(PlayOrderEvaluateInfoEntity::getCustomId, customId);
        lambdaQueryWrapper.eq(PlayOrderEvaluateInfoEntity::getOrderId, orderId);
        return this.baseMapper.selectOne(lambdaQueryWrapper);
    }

    /**
     * 查询订单评价信息
     *
     * @param id
     *            订单评价信息主键
     * @return 订单评价信息
     */
    @Override
    public PlayOrderEvaluateInfoEntity selectPlayOrderEvaluateInfoById(String id) {
        PlayOrderEvaluateInfoEntity entity = this.baseMapper.selectById(id);
        if (entity == null) {
            throw new CustomException("评价信息不存在");
        }
        return this.baseMapper.selectById(id);
    }

    @Override
    public List<PlayOrderEvaluateInfoEntity> selectByList(PlayOrderEvaluateInfoEntity entity) {
        LambdaQueryWrapper<PlayOrderEvaluateInfoEntity> lambdaQueryWrapper = new LambdaQueryWrapper<>();
        if (StrUtil.isNotBlank(entity.getClerkId())) {
            lambdaQueryWrapper.eq(PlayOrderEvaluateInfoEntity::getClerkId, entity.getClerkId());
        }
        if (StrUtil.isNotBlank(entity.getCustomId())) {
            lambdaQueryWrapper.eq(PlayOrderEvaluateInfoEntity::getCustomId, entity.getCustomId());
        }
        if (StrUtil.isNotBlank(entity.getOrderId())) {
            lambdaQueryWrapper.eq(PlayOrderEvaluateInfoEntity::getOrderId, entity.getOrderId());
        }
        return this.baseMapper.selectList(lambdaQueryWrapper);
    }

    @Override
    public IPage<PlayOrderEvaluateReturnVo> selectByPage(PlayOrderEvaluateQueryVo vo) {
        MPJLambdaWrapper<PlayOrderEvaluateInfoEntity> lambdaQueryWrapper = new MPJLambdaWrapper<PlayOrderEvaluateInfoEntity>();
        // 查询主表全部字段
        lambdaQueryWrapper.selectAll(PlayOrderEvaluateInfoEntity.class);

        // 查询顾客表字段
        lambdaQueryWrapper.selectAs(PlayCustomUserInfoEntity::getId, "customId")
                .selectAs(PlayCustomUserInfoEntity::getAvatar, "customAvatar")
                .selectAs(PlayCustomUserInfoEntity::getNickname, "customNickname");
        lambdaQueryWrapper.leftJoin(PlayCustomUserInfoEntity.class, PlayCustomUserInfoEntity::getId,
                PlayOrderEvaluateInfoEntity::getCustomId);
        // 查询店员表字典
        lambdaQueryWrapper.selectAs(PlayClerkUserInfoEntity::getId, "clerkId")
                .selectAs(PlayClerkUserInfoEntity::getAvatar, "clerkAvatar")
                .selectAs(PlayClerkUserInfoEntity::getNickname, "clerkNickname");
        lambdaQueryWrapper.leftJoin(PlayClerkUserInfoEntity.class, PlayClerkUserInfoEntity::getId,
                PlayOrderEvaluateInfoEntity::getClerkId);
        // 查询订单表
        lambdaQueryWrapper.selectAs(PlayOrderInfoEntity::getOrderNo, "orderNo")
                .selectAs(PlayOrderInfoEntity::getPlaceType, "placeType")
                .selectAs(PlayOrderInfoEntity::getFinalAmount, "finalAmount")
                .selectAs(PlayOrderInfoEntity::getCommodityPrice, "commodityPrice")
                .selectAs(PlayOrderInfoEntity::getServiceDuration, "serviceDuration")
                .selectAs(PlayOrderInfoEntity::getCommodityName, "commodityName")
                .selectAs(PlayOrderInfoEntity::getCommodityType, "commodityType")
                .selectAs(PlayOrderInfoEntity::getCommodityId, "commodityId")
                .selectAs(PlayOrderInfoEntity::getCommodityNumber, "commodityNumber");
        lambdaQueryWrapper.leftJoin(PlayOrderInfoEntity.class, PlayOrderInfoEntity::getId,
                PlayOrderEvaluateInfoEntity::getOrderId);

        if (StrUtil.isNotBlank(vo.getClerkId())) {
            lambdaQueryWrapper.eq(PlayOrderEvaluateInfoEntity::getClerkId, vo.getClerkId());
        }
        if (StrUtil.isNotBlank(vo.getCustomId())) {
            lambdaQueryWrapper.eq(PlayOrderEvaluateInfoEntity::getCustomId, vo.getCustomId());
        }
        if (StrUtil.isNotBlank(vo.getId())) {
            lambdaQueryWrapper.eq(PlayOrderEvaluateInfoEntity::getId, vo.getId());
        }
        if (StrUtil.isNotBlank(vo.getEvaluateCon())) {
            lambdaQueryWrapper.like(PlayOrderEvaluateInfoEntity::getEvaluateCon, vo.getEvaluateCon());
        }
        if (StrUtil.isNotBlank(vo.getAnonymous())) {
            lambdaQueryWrapper.eq(PlayOrderEvaluateInfoEntity::getAnonymous, vo.getAnonymous());
        }
        if (StrUtil.isNotBlank(vo.getHidden())) {
            lambdaQueryWrapper.eq(PlayOrderEvaluateInfoEntity::getHidden, vo.getHidden());
        }
        if (vo.getEvaluateTime() != null && vo.getEvaluateTime().size() == 2) {
            lambdaQueryWrapper.between(PlayOrderEvaluateInfoEntity::getEvaluateTime, vo.getEvaluateTime().get(0),
                    vo.getEvaluateTime().get(1));
        }
        // 加入组员的筛选
        if (StringUtils.isEmpty(vo.getClerkId())) {
            List<String> clerkIdList = playClerkGroupInfoService.getValidClerkIdList(SecurityUtils.getLoginUser(),
                    null);
            lambdaQueryWrapper.in(PlayOrderEvaluateInfoEntity::getClerkId, clerkIdList);
        }

        return this.baseMapper.selectJoinPage(new Page<>(vo.getPageNum(), vo.getPageSize()),
                PlayOrderEvaluateReturnVo.class, lambdaQueryWrapper);
    }

    /**
     * 查询订单评价信息列表
     *
     * @param playOrderEvaluateInfo
     *            订单评价信息
     * @return 订单评价信息
     */
    @Override
    public IPage<PlayOrderEvaluateInfoEntity> selectPlayOrderEvaluateInfoByPage(
            PlayOrderEvaluateInfoEntity playOrderEvaluateInfo) {
        Page<PlayOrderEvaluateInfoEntity> page = new Page<>(1, 10);
        return this.baseMapper.selectPage(page, new LambdaQueryWrapper<>());
    }

    /**
     * 新增订单评价信息
     *
     * @param playOrderEvaluateInfo
     *            订单评价信息
     * @return 结果
     */
    @Override
    public boolean create(PlayOrderEvaluateInfoEntity playOrderEvaluateInfo) {
        if (allowComplaint(playOrderEvaluateInfo.getCustomId(), playOrderEvaluateInfo.getOrderId())) {
            throw new CustomException("一个订单只能评价一次");
        }
        if (StrUtil.isBlankIfStr(playOrderEvaluateInfo.getId())) {
            playOrderEvaluateInfo.setId(IdUtils.getUuid());
        }
        PlayOrderInfoEntity orderInfo = playOrderInfoService.selectOrderInfoById(playOrderEvaluateInfo.getOrderId());
        playOrderEvaluateInfo.setClerkId(orderInfo.getAcceptBy());
        playOrderEvaluateInfo.setCommodityId(orderInfo.getCommodityId());
        return save(playOrderEvaluateInfo);
    }

    /**
     * 当前订单是否已评价
     *
     * @param customerId
     *            顾客ID
     * @param orderId
     *            订单ID
     * @return true:订单已评价，false:订单未评价
     */
    public boolean allowComplaint(String customerId, String orderId) {
        LambdaQueryWrapper<PlayOrderEvaluateInfoEntity> lambdaQueryWrapper = new LambdaQueryWrapper<>();
        lambdaQueryWrapper.eq(PlayOrderEvaluateInfoEntity::getCustomId, customerId);
        lambdaQueryWrapper.eq(PlayOrderEvaluateInfoEntity::getOrderId, orderId);
        return this.baseMapper.selectOne(lambdaQueryWrapper) != null;

    }

    /**
     * 修改订单评价信息
     *
     * @param playOrderEvaluateInfo
     *            订单评价信息
     * @return 结果
     */
    @Override
    public boolean update(PlayOrderEvaluateInfoEntity playOrderEvaluateInfo) {
        return updateById(playOrderEvaluateInfo);
    }

    /**
     * 批量删除订单评价信息
     *
     * @param ids
     *            需要删除的订单评价信息主键
     * @return 结果
     */
    @Override
    public int deletePlayOrderEvaluateInfoByIds(String[] ids) {
        return playOrderEvaluateInfoMapper.deleteBatchIds(Arrays.asList(ids));
    }

    /**
     * 删除订单评价信息信息
     *
     * @param id
     *            订单评价信息主键
     * @return 结果
     */
    @Override
    public int deletePlayOrderEvaluateInfoById(String id) {
        return playOrderEvaluateInfoMapper.deleteById(id);
    }
}


--- File: play-admin/src/main/java/com/starry/admin/modules/order/service/impl/PlayOrderInfoServiceImpl.java ---
package com.starry.admin.modules.order.service.impl;

import static com.starry.admin.modules.order.module.constant.OrderConstant.ORDER_STATUS_2;
import static com.starry.admin.modules.order.module.constant.OrderConstant.ORDER_STATUS_3;

import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.github.yulichang.wrapper.MPJLambdaWrapper;
import com.starry.admin.common.exception.CustomException;
import com.starry.admin.modules.clerk.module.entity.PlayClerkLevelInfoEntity;
import com.starry.admin.modules.clerk.module.entity.PlayClerkUserInfoEntity;
import com.starry.admin.modules.clerk.service.IPlayClerkLevelInfoService;
import com.starry.admin.modules.clerk.service.IPlayClerkUserInfoService;
import com.starry.admin.modules.custom.module.entity.PlayCustomLevelInfoEntity;
import com.starry.admin.modules.custom.module.entity.PlayCustomUserInfoEntity;
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.entity.PlayOrderComplaintInfoEntity;
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.PlayOrderRefundInfoEntity;
import com.starry.admin.modules.order.module.vo.*;
import com.starry.admin.modules.order.service.IPlayOrderComplaintInfoService;
import com.starry.admin.modules.order.service.IPlayOrderEvaluateInfoService;
import com.starry.admin.modules.order.service.IPlayOrderInfoService;
import com.starry.admin.modules.order.service.IPlayOrderRefundInfoService;
import com.starry.admin.modules.personnel.service.IPlayPersonnelGroupInfoService;
import com.starry.admin.modules.shop.module.vo.PlayCouponDetailsReturnVo;
import com.starry.admin.modules.shop.service.IPlayCouponDetailsService;
import com.starry.admin.modules.weichat.entity.order.*;
import com.starry.admin.modules.weichat.service.WxCustomMpService;
import com.starry.admin.utils.SecurityUtils;
import com.starry.common.utils.ConvertUtil;
import com.starry.common.utils.IdUtils;
import com.starry.common.utils.StringUtils;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.Random;
import java.util.stream.Collectors;
import javax.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

/**
 * 订单Service业务层处理
 *
 * @author admin
 * @since 2024-03-20
 */
@Slf4j
@Service
public class PlayOrderInfoServiceImpl extends ServiceImpl<PlayOrderInfoMapper, PlayOrderInfoEntity>
        implements
            IPlayOrderInfoService {
    @Resource
    private PlayOrderInfoMapper orderInfoMapper;
    @Resource
    private IPlayClerkUserInfoService playClerkUserInfoService;
    @Resource
    private IPlayCustomUserInfoService playCustomUserInfoService;
    @Resource
    private IPlayCustomUserInfoService userInfoService;
    @Resource
    private IPlayOrderRefundInfoService playOrderRefundInfoService;
    @Resource
    private IPlayOrderEvaluateInfoService playOrderEvaluateInfoService;
    @Resource
    private IPlayOrderComplaintInfoService playOrderComplaintInfoService;
    @Resource
    private IPlayPersonnelGroupInfoService playClerkGroupInfoService;

    @Resource
    private IPlayCouponDetailsService playCouponDetailsService;

    @Resource
    private WxCustomMpService wxCustomMpService;

    @Resource
    private IPlayCustomUserInfoService customUserInfoService;

    @Resource
    private IPlayClerkLevelInfoService playClerkLevelInfoService;

    @Override
    public List<PlayOrderInfoEntity> getTotalOrderInfo(String tenantId) {
        MPJLambdaWrapper<PlayOrderInfoEntity> lambdaWrapper = new MPJLambdaWrapper<>();
        lambdaWrapper.eq(PlayOrderInfoEntity::getTenantId, tenantId);
        return this.baseMapper.selectList(lambdaWrapper);
    }

    @Override
    public void createOrderInfo(String orderId, String orderNo, String orderState, String orderType, String placeType,
            String rewardType, String firstOrder, String commodityId, String commodityType, BigDecimal commodityPrice,
            String serviceDuration, String commodityName, String commodityNumber, BigDecimal orderMoney,
            BigDecimal finalAmount, BigDecimal discountAmount, String purchaserBy, String acceptBy, String weiChatCode,
            List<String> couponIds, String remark, String clerkSex, String clerkLevelId, String excludeHistory) {
        PlayOrderInfoEntity entity = new PlayOrderInfoEntity();
        entity.setId(orderId);
        entity.setOrderNo(orderNo);
        entity.setOrderStatus(orderState);
        entity.setOrderType(orderType);
        entity.setPlaceType(placeType);
        entity.setRewardType(rewardType);
        entity.setFirstOrder(firstOrder);
        entity.setRefundType("0");
        entity.setCommodityId(commodityId);
        entity.setCommodityType(commodityType);
        entity.setCommodityPrice(commodityPrice);
        entity.setServiceDuration(serviceDuration);
        entity.setCommodityName(commodityName);
        entity.setCommodityNumber(commodityNumber);
        entity.setBackendEntry("0");
        entity.setPayMethod("0");
        entity.setOrderMoney(orderMoney);
        entity.setFinalAmount(finalAmount);
        entity.setDiscountAmount(discountAmount);
        entity.setWeiChatCode(weiChatCode);
        entity.setRemark(remark);
        entity.setOrderSettlementState("0");
        entity.setOrdersExpiredState("0");
        entity.setPurchaserBy(purchaserBy);
        entity.setPurchaserTime(LocalDateTime.now());
        entity.setCouponIds(couponIds);
        entity.setUseCoupon(couponIds != null && !couponIds.isEmpty() ? "1" : "0");
        if ("1".equals(placeType)) {
            entity.setSex(clerkSex);
            entity.setLevelId(clerkLevelId);
            entity.setExcludeHistory(excludeHistory);
        }
        if (StrUtil.isNotBlank(acceptBy)) {
            entity.setAcceptBy(acceptBy);
            ClerkEstimatedRevenueVo estimatedRevenueVo = getClerkEstimatedRevenue(acceptBy, couponIds, placeType,
                    firstOrder, finalAmount);
            entity.setEstimatedRevenue(estimatedRevenueVo.getRevenueAmount());
            entity.setEstimatedRevenueRatio(estimatedRevenueVo.getRevenueRatio());
        }
        // 如果订单是打赏单，订单直接完成
        if ("2".equals(placeType)) {
            entity.setAcceptTime(LocalDateTime.now());
            entity.setOrderStartTime(LocalDateTime.now());
            entity.setOrderEndTime(LocalDateTime.now());
        }
        // 修改顾客下单信息
        userInfoService.saveOrderInfo(entity);
        // 保存订单
        this.baseMapper.insert(entity);
        // 修改优惠券状态
        playCouponDetailsService.updateCouponUseStateByIds(couponIds, "2");
    }

    @Override
    public ClerkEstimatedRevenueVo getClerkEstimatedRevenue(String clerkId, List<String> croupIds, String placeType,
            String firstOrder, BigDecimal finalAmount) {
        PlayClerkLevelInfoEntity entity = playClerkUserInfoService.queryLevelCommission(clerkId);
        ClerkEstimatedRevenueVo estimatedRevenueVo = new ClerkEstimatedRevenueVo();
        switch (placeType) {
            case "0" : {
                if ("1".equals(firstOrder)) {
                    estimatedRevenueVo.setRevenueRatio(entity.getFirstRegularRatio());
                    estimatedRevenueVo
                            .setRevenueAmount(
                                    finalAmount
                                            .multiply(new BigDecimal(entity.getFirstRegularRatio())
                                                    .divide(new BigDecimal(100), 4, RoundingMode.HALF_UP))
                                            .setScale(2, RoundingMode.HALF_UP));
                } else {
                    estimatedRevenueVo.setRevenueRatio(entity.getNotFirstRegularRatio());
                    estimatedRevenueVo
                            .setRevenueAmount(finalAmount
                                    .multiply(new BigDecimal(entity.getNotFirstRegularRatio())
                                            .divide(new BigDecimal(100), 4, RoundingMode.HALF_UP))
                                    .setScale(2, RoundingMode.HALF_UP));
                }
                break;
            }
            case "1" : {
                if ("1".equals(firstOrder)) {
                    estimatedRevenueVo.setRevenueRatio(entity.getFirstRandomRadio());
                    estimatedRevenueVo
                            .setRevenueAmount(
                                    finalAmount
                                            .multiply(new BigDecimal(entity.getFirstRandomRadio())
                                                    .divide(new BigDecimal(100), 4, RoundingMode.HALF_UP))
                                            .setScale(2, RoundingMode.HALF_UP));
                } else {
                    estimatedRevenueVo.setRevenueRatio(entity.getNotFirstRandomRadio());
                    estimatedRevenueVo
                            .setRevenueAmount(finalAmount
                                    .multiply(new BigDecimal(entity.getNotFirstRandomRadio())
                                            .divide(new BigDecimal(100), 4, RoundingMode.HALF_UP))
                                    .setScale(2, RoundingMode.HALF_UP));
                }
                break;
            }
            case "2" : {
                if ("1".equals(firstOrder)) {
                    estimatedRevenueVo.setRevenueRatio(entity.getFirstRewardRatio());
                    estimatedRevenueVo
                            .setRevenueAmount(
                                    finalAmount
                                            .multiply(new BigDecimal(entity.getFirstRewardRatio())
                                                    .divide(new BigDecimal(100), 4, RoundingMode.HALF_UP))
                                            .setScale(2, RoundingMode.HALF_UP));
                } else {
                    estimatedRevenueVo.setRevenueRatio(entity.getNotFirstRewardRatio());
                    estimatedRevenueVo
                            .setRevenueAmount(finalAmount
                                    .multiply(new BigDecimal(entity.getNotFirstRewardRatio())
                                            .divide(new BigDecimal(100), 4, RoundingMode.HALF_UP))
                                    .setScale(2, RoundingMode.HALF_UP));
                }
                break;
            }
            case "-1" : {
                log.error("下单类型异常，placeType={}", placeType);
                estimatedRevenueVo.setRevenueAmount(finalAmount);
                estimatedRevenueVo.setRevenueRatio(100);
                break;
            }
            default : {
                log.error("下单类型错误，placeType={}", placeType);
                estimatedRevenueVo.setRevenueAmount(finalAmount);
                estimatedRevenueVo.setRevenueRatio(100);
                break;
            }
        }

        // 如果优惠券不由店铺承担,那么店员预计收入减去优惠金额
        for (String croupId : croupIds) {
            PlayCouponDetailsReturnVo couponInfo = playCouponDetailsService.selectPlayCouponDetailsById(croupId);
            if ("0".equals(couponInfo.getAttributionDiscounts())) {
                BigDecimal revenueAmount = estimatedRevenueVo.getRevenueAmount();
                if ("0".equals(couponInfo.getDiscountType())) {
                    revenueAmount = revenueAmount.subtract(couponInfo.getDiscountAmount());
                } else {
                    revenueAmount = revenueAmount.subtract(revenueAmount.subtract(couponInfo.getDiscountAmount()));
                }
                log.debug("优惠券ID={},优惠券类型={},优惠金额={},优惠前金额={},优惠前金额={}", croupId, couponInfo.getDiscountType(),
                        couponInfo.getDiscountAmount(), estimatedRevenueVo.getRevenueAmount(), revenueAmount);
                estimatedRevenueVo.setRevenueAmount(revenueAmount);
            }
        }
        return estimatedRevenueVo;
    }

    /**
     * 根据店员等级和订单金额，获取店员预计收入
     *
     * @param clerkId
     *            店员ID
     * @param placeType
     *            下单类型（-1:其他类型;0:指定单;1:随机单;2:打赏单）
     * @param firstOrder
     *            是否是首单【0：不是，1：是】
     * @param finalAmount
     *            订单支付金额
     * @return math.BigDecimal 店员预计收入
     * @author admin
     * @since 2024/6/3 11:12
     **/
    @Override
    public BigDecimal getEstimatedRevenue(String clerkId, String placeType, String firstOrder, BigDecimal finalAmount) {
        PlayClerkLevelInfoEntity entity = playClerkUserInfoService.queryLevelCommission(clerkId);
        switch (placeType) {
            case "0" : {
                if ("1".equals(firstOrder)) {
                    return finalAmount.multiply(new BigDecimal(entity.getFirstRegularRatio())
                            .divide(new BigDecimal(100), 4, RoundingMode.HALF_UP)).setScale(2, RoundingMode.HALF_UP);
                } else {
                    return finalAmount.multiply(new BigDecimal(entity.getNotFirstRegularRatio())
                            .divide(new BigDecimal(100), 4, RoundingMode.HALF_UP)).setScale(2, RoundingMode.HALF_UP);
                }
            }
            case "1" : {
                if ("1".equals(firstOrder)) {
                    return finalAmount.multiply(new BigDecimal(entity.getFirstRandomRadio()).divide(new BigDecimal(100),
                            4, RoundingMode.HALF_UP)).setScale(2, RoundingMode.HALF_UP);
                } else {
                    return finalAmount.multiply(new BigDecimal(entity.getNotFirstRandomRadio())
                            .divide(new BigDecimal(100), 4, RoundingMode.HALF_UP)).setScale(2, RoundingMode.HALF_UP);
                }
            }
            case "2" : {
                if ("1".equals(firstOrder)) {
                    return finalAmount.multiply(new BigDecimal(entity.getFirstRewardRatio()).divide(new BigDecimal(100),
                            4, RoundingMode.HALF_UP)).setScale(2, RoundingMode.HALF_UP);
                } else {
                    return finalAmount.multiply(new BigDecimal(entity.getNotFirstRewardRatio())
                            .divide(new BigDecimal(100), 4, RoundingMode.HALF_UP)).setScale(2, RoundingMode.HALF_UP);
                }
            }
            case "-1" : {
                log.error("下单类型异常，placeType={}", placeType);
                return finalAmount;
            }
            default : {
                log.error("下单类型错误，placeType={}", placeType);
                return finalAmount;
            }
        }
    }

    @Override
    public Integer getEstimatedRevenueRatio(String clerkId, String placeType, String firstOrder) {
        PlayClerkLevelInfoEntity entity = playClerkUserInfoService.queryLevelCommission(clerkId);
        switch (placeType) {
            case "0" : {
                if ("1".equals(firstOrder)) {
                    return entity.getFirstRegularRatio();
                } else {
                    return entity.getNotFirstRegularRatio();
                }
            }
            case "1" : {
                if ("1".equals(firstOrder)) {
                    return entity.getFirstRandomRadio();
                } else {
                    return entity.getNotFirstRandomRadio();
                }
            }
            case "2" : {
                if ("1".equals(firstOrder)) {
                    return entity.getFirstRewardRatio();
                } else {
                    return entity.getNotFirstRewardRatio();
                }
            }
            case "-1" : {
                log.error("下单类型异常，placeType={}", placeType);
                return 100;
            }
            default : {
                log.error("下单类型错误，placeType={}", placeType);
                return 100;
            }
        }
    }

    /**
     * 新增充值订单
     *
     * @param orderId
     *            订单编号
     * @param orderMoney
     *            订单金额
     * @param finalAmount
     *            订单最终金额（支付金额）
     * @param purchaserBy
     *            下单人ID
     */

    @Override
    public void createRechargeOrder(String orderId, BigDecimal orderMoney, BigDecimal finalAmount, String purchaserBy) {
        PlayOrderInfoEntity entity = new PlayOrderInfoEntity(IdUtils.getUuid(), ORDER_STATUS_3, "0", "-1");
        entity.setOrderNo(orderId);
        entity.setId(IdUtils.getUuid());
        entity.setOrderMoney(orderMoney);
        entity.setFinalAmount(finalAmount);
        entity.setPurchaserBy(purchaserBy);
        entity.setPayMethod("1");
        entity.setPayState("0");
        this.baseMapper.insert(entity);
    }

    /**
     * 订单编号由2部分组成 第一部分是是时间，长度8位 第二部分是随机数，长度4位
     *
     * @return 订单编号
     */
    @Override
    public String getOrderNo() {
        String one = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMddHHmmss"));
        String two = String.valueOf(new Random().nextInt(9000));
        return one + two;
    }

    @Override
    public List<PlayOrderInfoEntity> queryBySettlementOrder(String clerkId, String orderSettlementState) {
        MPJLambdaWrapper<PlayOrderInfoEntity> lambdaQueryWrapper = new MPJLambdaWrapper<>();
        lambdaQueryWrapper.eq(PlayOrderInfoEntity::getAcceptBy, clerkId);
        if (StrUtil.isNotBlank(orderSettlementState)) {
            lambdaQueryWrapper.eq(PlayOrderInfoEntity::getOrderSettlementState, orderSettlementState);
        }

        return this.baseMapper.selectList(lambdaQueryWrapper);
    }

    @Override
    public List<PlayOrderInfoEntity> listByEndTime(String clerkId, LocalDateTime endTime) {
        MPJLambdaWrapper<PlayOrderInfoEntity> lambdaQueryWrapper = new MPJLambdaWrapper<>();
        lambdaQueryWrapper.eq(PlayOrderInfoEntity::getAcceptBy, clerkId);
        lambdaQueryWrapper.eq(PlayOrderInfoEntity::getOrderStatus, "3");
        lambdaQueryWrapper.lt(PlayOrderInfoEntity::getOrderEndTime, endTime);
        lambdaQueryWrapper.eq(PlayOrderInfoEntity::getOrderSettlementState, "0");
        return this.baseMapper.selectList(lambdaQueryWrapper);
    }

    @Override
    public List<PlayOrderInfoEntity> listByTime(String clerkId, String startTime, String endTime,
            List<String> orderStatus) {
        MPJLambdaWrapper<PlayOrderInfoEntity> lambdaQueryWrapper = new MPJLambdaWrapper<>();
        if (StrUtil.isNotEmpty(clerkId)) {
            lambdaQueryWrapper.eq(PlayOrderInfoEntity::getAcceptBy, clerkId);
        }
        if (orderStatus != null && !orderStatus.isEmpty()) {
            lambdaQueryWrapper.in(PlayOrderInfoEntity::getOrderStatus, orderStatus);
        }
        lambdaQueryWrapper.between(PlayOrderInfoEntity::getPurchaserTime, startTime, endTime);
        return this.baseMapper.selectList(lambdaQueryWrapper);
    }

    @Override
    public IPage<PlayOrderRewardReturnVo> selectRewardOrderInfoByPage(PlayOrderRewardQueryVo vo) {
        MPJLambdaWrapper<PlayOrderInfoEntity> lambdaQueryWrapper = new MPJLambdaWrapper<>();
        // 查询主表全部字段
        lambdaQueryWrapper.selectAll(PlayOrderInfoEntity.class);
        // 查询顾客表
        lambdaQueryWrapper.selectAs(PlayCustomUserInfoEntity::getId, "customId")
                .selectAs(PlayCustomUserInfoEntity::getAvatar, "customAvatar")
                .selectAs(PlayCustomUserInfoEntity::getNickname, "customNickname");
        lambdaQueryWrapper.leftJoin(PlayCustomUserInfoEntity.class, PlayCustomUserInfoEntity::getId,
                PlayOrderInfoEntity::getPurchaserBy).leftJoin(PlayClerkUserInfoEntity.class,
                        PlayClerkUserInfoEntity::getId, PlayOrderInfoEntity::getAcceptBy);
        // 查询陪聊表
        lambdaQueryWrapper.selectAs(PlayClerkUserInfoEntity::getId, "clerkId")
                .selectAs(PlayClerkUserInfoEntity::getAvatar, "clerkAvatar")
                .selectAs(PlayClerkUserInfoEntity::getNickname, "clerkNickname");

        lambdaQueryWrapper.eq(PlayOrderInfoEntity::getPlaceType, "2");
        if (StrUtil.isNotBlank(vo.getRewardType())) {
            lambdaQueryWrapper.eq(PlayOrderInfoEntity::getRewardType, vo.getRewardType());
        }
        if (StrUtil.isNotBlank(vo.getAcceptBy())) {
            lambdaQueryWrapper.eq(PlayOrderInfoEntity::getAcceptBy, vo.getAcceptBy());
        }
        if (StrUtil.isNotBlank(vo.getPurchaserBy())) {
            lambdaQueryWrapper.eq(PlayOrderInfoEntity::getPurchaserBy, vo.getPurchaserBy());
        }
        if (vo.getPurchaserTime() != null && vo.getPurchaserTime().size() == 2) {
            lambdaQueryWrapper.between(PlayOrderInfoEntity::getPurchaserTime, vo.getPurchaserTime().get(0),
                    vo.getPurchaserTime().get(1));
        }
        lambdaQueryWrapper.orderByDesc(PlayOrderInfoEntity::getPurchaserTime);
        // 加入组员的筛选
        List<String> clerkIdList = playClerkGroupInfoService.getValidClerkIdList(SecurityUtils.getLoginUser(), null);
        lambdaQueryWrapper.in(PlayOrderInfoEntity::getAcceptBy, clerkIdList);

        return this.baseMapper.selectJoinPage(new Page<>(vo.getPageNum(), vo.getPageSize()),
                PlayOrderRewardReturnVo.class, lambdaQueryWrapper);

    }

    @Override
    public IPage<PlayOrderInfoEntity> selectRandomOrderByPage(PlayOrderInfoRandomQueryVo vo, String clerkId) {
        PlayClerkUserInfoEntity entity = playClerkUserInfoService.getById(clerkId);
        LambdaQueryWrapper<PlayOrderInfoEntity> lambdaQueryWrapper = new LambdaQueryWrapper<>();
        lambdaQueryWrapper.eq(PlayOrderInfoEntity::getPlaceType, "1");
        lambdaQueryWrapper.eq(PlayOrderInfoEntity::getOrderStatus, "0");
        lambdaQueryWrapper.eq(PlayOrderInfoEntity::getLevelId, entity.getLevelId());
        lambdaQueryWrapper.eq(PlayOrderInfoEntity::getSex, entity.getSex());
        lambdaQueryWrapper.eq(PlayOrderInfoEntity::getExcludeHistory, "0")
                .or(wrapper1 -> wrapper1.ne(PlayOrderInfoEntity::getAcceptBy, clerkId)
                        .and(wrapper2 -> wrapper2.eq(PlayOrderInfoEntity::getExcludeHistory, 1)));
        return this.baseMapper.selectPage(new Page<>(vo.getPageNum(), vo.getPageSize()), lambdaQueryWrapper);
    }

    @Override
    public IPage<PlayRewardInfoReturnVo> selectRewardByPage(PlayRewardOrderQueryVo vo) {
        MPJLambdaWrapper<PlayOrderInfoEntity> lambdaQueryWrapper = new MPJLambdaWrapper<>();
        lambdaQueryWrapper.eq(PlayOrderInfoEntity::getPlaceType, "2");
        lambdaQueryWrapper.eq(PlayOrderInfoEntity::getRefundType, "0");
        lambdaQueryWrapper.selectAll(PlayOrderInfoEntity.class);
        lambdaQueryWrapper.orderByDesc(PlayOrderInfoEntity::getPurchaserTime);
        // 查询陪聊表
        lambdaQueryWrapper.selectAs(PlayClerkUserInfoEntity::getId, "clerkId")
                .selectAs(PlayClerkUserInfoEntity::getAvatar, "clerkAvatar")
                .selectAs(PlayClerkUserInfoEntity::getNickname, "clerkNickname");
        // 子表
        lambdaQueryWrapper.leftJoin(PlayCustomUserInfoEntity.class, PlayCustomUserInfoEntity::getId,
                PlayOrderInfoEntity::getPurchaserBy).leftJoin(PlayClerkUserInfoEntity.class,
                        PlayClerkUserInfoEntity::getId, PlayOrderInfoEntity::getAcceptBy);
        lambdaQueryWrapper.orderByDesc(PlayOrderInfoEntity::getPurchaserTime);
        return this.baseMapper.selectJoinPage(new Page<>(vo.getPageNum(), vo.getPageSize()),
                PlayRewardInfoReturnVo.class, lambdaQueryWrapper);
    }

    /**
     * 查询订单
     *
     * @param id
     *            订单主键
     * @return 订单
     */
    @Override
    public PlayOrderInfoEntity selectOrderInfoById(String id) {
        PlayOrderInfoEntity entity = this.baseMapper.selectById(id);
        if (entity == null) {
            throw new CustomException("订单不存在");
        }
        return entity;
    }

    @Override
    public PlayOrderDetailsReturnVo selectById(String orderId) {
        MPJLambdaWrapper<PlayOrderInfoEntity> lambdaQueryWrapper = new MPJLambdaWrapper<>();
        // 查询主表全部字段
        lambdaQueryWrapper.selectAll(PlayOrderInfoEntity.class);
        // 查询顾客表
        lambdaQueryWrapper.selectAs(PlayCustomUserInfoEntity::getId, "customId")
                .selectAs(PlayCustomUserInfoEntity::getAvatar, "customAvatar")
                .selectAs(PlayCustomUserInfoEntity::getNickname, "customNickname");
        // 查询陪聊表
        lambdaQueryWrapper.selectAs(PlayClerkUserInfoEntity::getId, "clerkId")
                .selectAs(PlayClerkUserInfoEntity::getAvatar, "clerkAvatar")
                .selectAs(PlayClerkUserInfoEntity::getNickname, "clerkNickname");
        // 子表
        lambdaQueryWrapper.leftJoin(PlayCustomUserInfoEntity.class, PlayCustomUserInfoEntity::getId,
                PlayOrderInfoEntity::getPurchaserBy).leftJoin(PlayClerkUserInfoEntity.class,
                        PlayClerkUserInfoEntity::getId, PlayOrderInfoEntity::getAcceptBy);
        lambdaQueryWrapper.eq(PlayOrderInfoEntity::getId, orderId);
        lambdaQueryWrapper.orderByDesc(PlayOrderInfoEntity::getPurchaserTime);
        return this.baseMapper.selectJoinOne(PlayOrderDetailsReturnVo.class, lambdaQueryWrapper);
    }

    /**
     * 分页查询订单信息
     *
     * @param vo
     *            订单分页查询对象
     * @return 订单分页查询结果
     */
    @Override
    public IPage<PlayOrderInfoReturnVo> selectOrderInfoPage(PlayOrderInfoQueryVo vo) {

        MPJLambdaWrapper<PlayOrderInfoEntity> lambdaQueryWrapper = getCommonOrderQueryVo(
                ConvertUtil.entityToVo(vo, PlayOrderInfoEntity.class));
        if (StringUtils.isNotBlank(vo.getGroupId())) {
            lambdaQueryWrapper.eq(PlayOrderInfoEntity::getGroupId, vo.getGroupId());
        }
        if (StringUtils.isNotBlank(vo.getPurchaserBy())) {
            lambdaQueryWrapper.eq(PlayOrderInfoEntity::getPurchaserBy, vo.getPurchaserBy());
        }
        if (StringUtils.isNotBlank(vo.getAcceptBy())) {
            lambdaQueryWrapper.eq(PlayOrderInfoEntity::getAcceptBy, vo.getAcceptBy());
        }
        if (StringUtils.isNotBlank(vo.getOrderStatus())) {
            lambdaQueryWrapper.eq(PlayOrderInfoEntity::getOrderStatus, vo.getOrderStatus());
        }
        if (StringUtils.isNotBlank(vo.getPlaceType())) {
            lambdaQueryWrapper.eq(PlayOrderInfoEntity::getPlaceType, vo.getPlaceType());
        }
        if (StringUtils.isNotBlank(vo.getOrderType())) {
            lambdaQueryWrapper.eq(PlayOrderInfoEntity::getOrderType, vo.getOrderType());
        }
        // 加入组员的筛选，要么acceptBy为空，要么就在in里面
        List<String> clerkIdList = playClerkGroupInfoService.getValidClerkIdList(SecurityUtils.getLoginUser(),
                vo.getClerkNickName());
        lambdaQueryWrapper.and(
                i -> i.isNull(PlayOrderInfoEntity::getAcceptBy).or().in(PlayOrderInfoEntity::getAcceptBy, clerkIdList));
        return this.baseMapper.selectJoinPage(new Page<>(vo.getPageNum(), vo.getPageSize()),
                PlayOrderInfoReturnVo.class, lambdaQueryWrapper);
    }

    @Override
    public PlayClerkOrderDetailsReturnVo clerkSelectOrderDetails(String clerkId, String orderId) {
        PlayOrderInfoEntity entity = new PlayOrderInfoEntity();
        entity.setId(orderId);
        entity.setAcceptBy(clerkId);
        MPJLambdaWrapper<PlayOrderInfoEntity> lambdaQueryWrapper = getCommonOrderQueryVo(entity);
        // 拼接用户等级
        lambdaQueryWrapper.selectAs(PlayCustomLevelInfoEntity::getId, "customLevelId")
                .selectAs(PlayCustomLevelInfoEntity::getName, "customLevelName");
        lambdaQueryWrapper.leftJoin(PlayCustomLevelInfoEntity.class, PlayCustomLevelInfoEntity::getId,
                PlayCustomUserInfoEntity::getLevelId);
        PlayClerkOrderDetailsReturnVo returnVo = this.baseMapper.selectJoinOne(PlayClerkOrderDetailsReturnVo.class,
                lambdaQueryWrapper);
        // 如果订单状态为退款，查询订单退款原因
        if (returnVo.getOrderStatus().equals(OrderConstant.ORDER_STATUS_4)) {
            PlayOrderRefundInfoEntity orderRefundInfoEntity = playOrderRefundInfoService
                    .selectPlayOrderRefundInfoByOrderId(returnVo.getId());
            returnVo.setRefundByType(orderRefundInfoEntity.getRefundByType());
            returnVo.setRefundById(orderRefundInfoEntity.getRefundById());
            returnVo.setRefundReason(orderRefundInfoEntity.getRefundReason());
        }
        returnVo.setEstimatedRevenue(returnVo.getFinalAmount());
        return returnVo;
    }

    @Override
    public List<PlayOrderInfoEntity> clerkSelectOrderInfoList(String clerkId, String startTime, String endTime) {
        LambdaQueryWrapper<PlayOrderInfoEntity> lambdaQueryWrapper = new LambdaQueryWrapper<>();
        lambdaQueryWrapper.eq(PlayOrderInfoEntity::getAcceptBy, clerkId);
        lambdaQueryWrapper.between(PlayOrderInfoEntity::getPurchaserTime, startTime, endTime);
        return this.baseMapper.selectList(lambdaQueryWrapper);
    }

    @Override
    public IPage<PlayClerkOrderListReturnVo> clerkSelectOrderInfoByPage(PlayClerkOrderInfoQueryVo vo) {
        MPJLambdaWrapper<PlayOrderInfoEntity> lambdaQueryWrapper = getCommonOrderQueryVo(
                ConvertUtil.entityToVo(vo, PlayOrderInfoEntity.class));
        // 拼接用户等级
        lambdaQueryWrapper.selectAs(PlayCustomLevelInfoEntity::getId, "customLevelId")
                .selectAs(PlayCustomLevelInfoEntity::getName, "customLevelName");
        lambdaQueryWrapper.leftJoin(PlayCustomLevelInfoEntity.class, PlayCustomLevelInfoEntity::getId,
                PlayCustomUserInfoEntity::getLevelId);
        return this.baseMapper.selectJoinPage(new Page<>(vo.getPageNum(), vo.getPageSize()),
                PlayClerkOrderListReturnVo.class, lambdaQueryWrapper);
    }

    @Override
    public PlayCustomOrderDetailsReturnVo customSelectOrderDetails(String customId, String orderId) {
        PlayOrderInfoEntity entity = new PlayOrderInfoEntity();
        entity.setId(orderId);
        entity.setPurchaserBy(customId);
        MPJLambdaWrapper<PlayOrderInfoEntity> lambdaQueryWrapper = getCommonOrderQueryVo(entity);
        PlayCustomOrderDetailsReturnVo returnVo = this.baseMapper.selectJoinOne(PlayCustomOrderDetailsReturnVo.class,
                lambdaQueryWrapper);
        // 如果订单状态为退款，查询订单退款原因
        if (returnVo.getOrderStatus().equals(OrderConstant.ORDER_STATUS_4)) {
            PlayOrderRefundInfoEntity orderRefundInfoEntity = playOrderRefundInfoService
                    .selectPlayOrderRefundInfoByOrderId(returnVo.getId());
            returnVo.setRefundByType(orderRefundInfoEntity.getRefundByType());
            returnVo.setRefundById(orderRefundInfoEntity.getRefundById());
            returnVo.setRefundReason(orderRefundInfoEntity.getRefundReason());
        }
        // 查询订单是否评价
        PlayOrderEvaluateInfoEntity orderEvaluateInfoEntity = playOrderEvaluateInfoService
                .queryCustomToOrderEvaluateInfo(customId, orderId);
        returnVo.setEvaluate(orderEvaluateInfoEntity != null ? "1" : "0");
        PlayOrderComplaintInfoEntity orderComplaintInfoEntity = playOrderComplaintInfoService
                .queryCustomToOrderOrderComplaintInfo(customId, orderId);
        // 查询订单是否投诉
        returnVo.setComplaint(orderComplaintInfoEntity != null ? "1" : "0");

        return returnVo;
    }

    @Override
    public IPage<PlayCustomOrderListReturnVo> customSelectOrderInfoByPage(PlayCustomOrderInfoQueryVo vo) {
        MPJLambdaWrapper<PlayOrderInfoEntity> lambdaQueryWrapper = getCommonOrderQueryVo(
                ConvertUtil.entityToVo(vo, PlayOrderInfoEntity.class));
        IPage<PlayCustomOrderListReturnVo> page = this.baseMapper.selectJoinPage(
                new Page<>(vo.getPageNum(), vo.getPageSize()), PlayCustomOrderListReturnVo.class, lambdaQueryWrapper);
        // 获取当前顾客所有订单评价信息，将订单评价信息转化为 map<订单ID，订单ID>的结构
        PlayOrderEvaluateInfoEntity orderEvaluateInfoEntity = new PlayOrderEvaluateInfoEntity();
        orderEvaluateInfoEntity.setCustomId(vo.getPurchaserBy());
        Map<String, String> evaluateInfos = playOrderEvaluateInfoService.selectByList(orderEvaluateInfoEntity).stream()
                .collect(Collectors.toMap(PlayOrderEvaluateInfoEntity::getOrderId,
                        PlayOrderEvaluateInfoEntity::getOrderId));
        for (PlayCustomOrderListReturnVo record : page.getRecords()) {
            record.setEvaluate(evaluateInfos.containsKey(record.getId()) ? "1" : "0");
        }
        // 获取当前顾客所有订单投诉信息，将订单评价信息转化为 map<订单ID，订单ID>的结构
        PlayOrderComplaintInfoEntity orderComplaintInfoEntity = new PlayOrderComplaintInfoEntity();
        orderComplaintInfoEntity.setCustomId(vo.getPurchaserBy());
        Map<String, String> complaintInfos = playOrderComplaintInfoService.selectByList(orderComplaintInfoEntity)
                .stream().collect(Collectors.toMap(PlayOrderComplaintInfoEntity::getOrderId,
                        PlayOrderComplaintInfoEntity::getOrderId));
        for (PlayCustomOrderListReturnVo record : page.getRecords()) {
            record.setComplaint(complaintInfos.containsKey(record.getId()) ? "1" : "0");
        }
        return page;
    }

    @Override
    public List<PlayOrderInfoEntity> customSelectOrderInfoByList(String customId) {
        LambdaQueryWrapper<PlayOrderInfoEntity> lambdaQueryWrapper = new LambdaQueryWrapper<>();
        lambdaQueryWrapper.eq(PlayOrderInfoEntity::getPurchaserBy, customId);
        return this.baseMapper.selectList(lambdaQueryWrapper);
    }

    /**
     * 获取通用的订单查询对象 订单作为主表 连接顾客用户表、店员用户表、商品表
     *
     * @return MPJLambdaWrapper<PlayOrderInfoEntity>
     */
    public MPJLambdaWrapper<PlayOrderInfoEntity> getCommonOrderQueryVo(PlayOrderInfoEntity entity) {
        MPJLambdaWrapper<PlayOrderInfoEntity> lambdaQueryWrapper = new MPJLambdaWrapper<>();
        // 查询主表全部字段
        lambdaQueryWrapper.selectAll(PlayOrderInfoEntity.class);
        // 查询顾客表
        lambdaQueryWrapper.selectAs(PlayCustomUserInfoEntity::getId, "customId")
                .selectAs(PlayCustomUserInfoEntity::getAvatar, "customAvatar")
                .selectAs(PlayCustomUserInfoEntity::getNickname, "customNickname");
        // 查询陪聊表
        lambdaQueryWrapper.selectAs(PlayClerkUserInfoEntity::getId, "clerkId")
                .selectAs(PlayClerkUserInfoEntity::getAvatar, "clerkAvatar")
                .selectAs(PlayClerkUserInfoEntity::getNickname, "clerkNickname");
        // 子表-店员服务项目表
        lambdaQueryWrapper.leftJoin(PlayCustomUserInfoEntity.class, PlayCustomUserInfoEntity::getId,
                PlayOrderInfoEntity::getPurchaserBy);
        // 子表-店员表
        lambdaQueryWrapper.leftJoin(PlayClerkUserInfoEntity.class, PlayClerkUserInfoEntity::getId,
                PlayOrderInfoEntity::getAcceptBy);
        if (StringUtils.isNotBlank(entity.getId())) {
            lambdaQueryWrapper.eq(PlayOrderInfoEntity::getId, entity.getId());
        }
        if (StringUtils.isNotBlank(entity.getPurchaserBy())) {
            lambdaQueryWrapper.eq(PlayOrderInfoEntity::getPurchaserBy, entity.getPurchaserBy());
        }
        if (StringUtils.isNotBlank(entity.getAcceptBy())) {
            lambdaQueryWrapper.eq(PlayOrderInfoEntity::getAcceptBy, entity.getAcceptBy());
        }
        if (StringUtils.isNotBlank(entity.getOrderStatus())) {
            lambdaQueryWrapper.eq(PlayOrderInfoEntity::getOrderStatus, entity.getOrderStatus());
        }
        if (StringUtils.isNotBlank(entity.getPlaceType())) {
            lambdaQueryWrapper.eq(PlayOrderInfoEntity::getPlaceType, entity.getPlaceType());
        }
        if (StringUtils.isNotBlank(entity.getOrderType())) {
            lambdaQueryWrapper.eq(PlayOrderInfoEntity::getOrderType, entity.getOrderType());
        }
        lambdaQueryWrapper.orderByDesc(PlayOrderInfoEntity::getCreatedTime);
        return lambdaQueryWrapper;

    }

    /**
     * 修改订单状态为接单 只有管理员或者店员本人才能操作
     *
     * @param operatorByType
     *            操作人类型（0:顾客;1:店员;2:管理员）
     * @param operatorBy
     *            操作人ID
     * @param acceptBy
     *            接单人ID
     * @param orderId
     *            订单Id
     **/
    @Override
    public void updateStateTo1(String operatorByType, String operatorBy, String acceptBy, String orderId) {
        if (!"1".equals(operatorByType) && !"2".equals(operatorByType)) {
            throw new CustomException("禁止操作");
        }
        PlayOrderInfoEntity orderInfo = this.selectOrderInfoById(orderId);
        PlayClerkUserInfoEntity clerkUserInfoEntity = playClerkUserInfoService.selectById(acceptBy);
        // 店员接单时，判断店员是否符合资格
        if ("1".equals(operatorByType) && "1".equals(orderInfo.getPlaceType())) {
            // 判断店员等级是否符合规则
            if (orderInfo.getLevelId().equals(clerkUserInfoEntity.getLevelId())) {
                PlayClerkLevelInfoEntity levelInfo = playClerkLevelInfoService
                        .selectPlayClerkLevelInfoById(orderInfo.getLevelId());
                throw new CustomException("等级为" + levelInfo.getName() + "的店员才可接单");
            }
            // 判断店员性别是否符合规则
            if (orderInfo.getSex().equals(clerkUserInfoEntity.getSex())) {
                String sex = "0".equals(orderInfo.getSex()) ? "未知" : "1".equals(orderInfo.getSex()) ? "男" : "女";
                throw new CustomException("性别为" + sex + "的店员才可接单");
            }
            // 如果排除已下单的店员
            LambdaQueryWrapper<PlayOrderInfoEntity> lambdaQueryWrapper = new LambdaQueryWrapper<>();
            lambdaQueryWrapper.eq(PlayOrderInfoEntity::getAcceptBy, acceptBy);
            if ("1".equals(orderInfo.getExcludeHistory()) && this.baseMapper.selectOne(lambdaQueryWrapper) != null) {
                throw new CustomException("只有未接单的店员才可接单");
            }
        }
        PlayOrderInfoEntity entity = new PlayOrderInfoEntity(orderId, OrderConstant.ORDER_STATUS_1);
        entity.setAcceptBy(acceptBy);
        entity.setAcceptTime(LocalDateTime.now());
        ClerkEstimatedRevenueVo estimatedRevenueVo = this.getClerkEstimatedRevenue(acceptBy, orderInfo.getCouponIds(),
                orderInfo.getPlaceType(), orderInfo.getFirstOrder(), orderInfo.getFinalAmount());
        entity.setEstimatedRevenue(estimatedRevenueVo.getRevenueAmount());
        entity.setEstimatedRevenueRatio(estimatedRevenueVo.getRevenueRatio());
        this.baseMapper.updateById(entity);

        wxCustomMpService.sendOrderMessage(orderInfo);
    }

    /**
     * 修改订单状态为开始订单或者完成订单 只有管理员或者店员本人才能操作
     *
     * @param operatorByType
     *            操作人类型（0:顾客;1:店员;2:管理员）
     * @param operatorBy
     *            操作人ID
     * @param orderState
     *            订单状态
     * @param orderId
     *            订单Id
     **/
    @Override
    public void updateStateTo23(String operatorByType, String operatorBy, String orderState, String orderId) {
        // 开始订单只能店员或者管理员操作
        if (orderState.equals(ORDER_STATUS_2) && "0".equals(operatorByType)) {
            throw new CustomException("禁止操作");
        }
        // 完成订单只能顾客或者管理员操作
        if (orderState.equals(ORDER_STATUS_3) && "1".equals(operatorByType)) {
            throw new CustomException("禁止操作");
        }
        PlayOrderInfoEntity entity = this.selectOrderInfoById(orderId);
        // 如果该接口是顾客调用，判断是否是本人订单
        if ("0".equals(operatorByType) && !entity.getPurchaserBy().equals(operatorBy)) {
            throw new CustomException("只能操作本人订单");
        }
        // 如果该接口是店员调用，判断是否是本人订单
        if ("1".equals(operatorByType) && !entity.getAcceptBy().equals(operatorBy)) {
            throw new CustomException("只能操作本人订单");
        }
        switch (orderState) {
            case ORDER_STATUS_2 : {
                // 开始订单前，订单状态必须为接单状态
                if (!entity.getOrderStatus().equals(OrderConstant.ORDER_STATUS_1)) {
                    log.error("订单状态异常，不能开始接单,orderId={},orderStace={}", orderId, orderState);
                    throw new CustomException("订单状态异常，不能开始订单");
                }
                PlayOrderInfoEntity entity2 = new PlayOrderInfoEntity(orderId, ORDER_STATUS_2);
                entity2.setOrderStartTime(LocalDateTime.now());
                this.baseMapper.updateById(entity2);
                break;
            }
            case ORDER_STATUS_3 : {
                // 完成订单前，订单状态必须为开始状态
                if (!entity.getOrderStatus().equals(ORDER_STATUS_2)) {
                    log.error("订单状态异常，不能完成订单,orderId={},orderStace={}", orderId, orderState);
                    throw new CustomException("订单状态异常，不能开始订单");
                }
                // 完成订单
                PlayOrderInfoEntity entity2 = new PlayOrderInfoEntity(orderId, ORDER_STATUS_3);
                entity2.setOrderEndTime(LocalDateTime.now());
                this.baseMapper.updateById(entity2);
                // 发送消息
                wxCustomMpService.sendOrderFinishMessage(this.selectOrderInfoById(orderId));
                break;
            }
            default : {
                log.error("修改订单状态异常，orderId={},orderStace={}", orderId, orderState);
            }
        }
    }

    /**
     * 修改订单状态为取消订单 管理员、店员、顾客均可操作
     *
     * @param operatorByType
     *            操作人类型（0:顾客;1:店员;2:管理员）
     * @param operatorBy
     *            操作人ID
     * @param orderId
     *            订单Id
     **/

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void updateStateTo4(String operatorByType, String operatorBy, String orderId, String refundReason,
            List<String> images) {
        PlayOrderInfoEntity orderInfo = this.selectOrderInfoById(orderId);
        if ("0".equals(operatorByType) && !operatorBy.equals(orderInfo.getPurchaserBy())) {
            throw new CustomException("只能操作本人订单");
        }
        if ("1".equals(operatorByType) && !operatorBy.equals(orderInfo.getAcceptBy())) {
            throw new CustomException("只能操作本人订单");
        }
        // 取消订单（必须订单未接单或者为开始状态）
        if (!orderInfo.getOrderStatus().equals(OrderConstant.ORDER_STATUS_0)
                && !orderInfo.getOrderStatus().equals(OrderConstant.ORDER_STATUS_1)) {
            throw new CustomException("订单状态异常，无法取消");
        }
        // 修改订单状态
        this.baseMapper.updateById(new PlayOrderInfoEntity(orderId, OrderConstant.ORDER_STATUS_4));
        // 用户增加余额
        PlayCustomUserInfoEntity customUserInfo = customUserInfoService.getById(orderInfo.getPurchaserBy());
        customUserInfoService.updateAccountBalanceById(customUserInfo.getId(), customUserInfo.getAccountBalance(),
                customUserInfo.getAccountBalance().add(orderInfo.getOrderMoney()), "3", "订单退款",
                orderInfo.getOrderMoney(), BigDecimal.ZERO, orderId);
        // 取消订单后，记录退款信息
        playOrderRefundInfoService.add(orderId, orderInfo.getPurchaserBy(), orderInfo.getAcceptBy(),
                orderInfo.getPayMethod(), "0", orderInfo.getFinalAmount(), refundReason, operatorByType, operatorBy,
                "0", "0");
        wxCustomMpService.sendOrderCancelMessage(orderInfo, refundReason);
    }

    @Override
    public PlayOrderInfoEntity queryByOrderNo(String orderNo) {
        LambdaQueryWrapper<PlayOrderInfoEntity> lambdaQueryWrapper = new LambdaQueryWrapper<>();
        lambdaQueryWrapper.eq(PlayOrderInfoEntity::getOrderNo, orderNo);
        return this.baseMapper.selectOne(lambdaQueryWrapper);
    }

    /**
     * 修改订单
     *
     * @param orderInfo
     *            订单
     * @return 结果
     */
    @Override
    public boolean update(PlayOrderInfoEntity orderInfo) {
        return updateById(orderInfo);
    }

    /**
     * 批量删除订单
     *
     * @param ids
     *            需要删除的订单主键
     * @return 结果
     */
    @Override
    public int deleteOrderInfoByIds(String[] ids) {
        return orderInfoMapper.deleteBatchIds(Arrays.asList(ids));
    }

    /**
     * 删除订单信息
     *
     * @param id
     *            订单主键
     * @return 结果
     */
    @Override
    public int deleteOrderInfoById(String id) {
        return orderInfoMapper.deleteById(id);
    }

}


--- File: play-admin/src/main/java/com/starry/admin/modules/order/service/impl/PlayOrderRefundInfoServiceImpl.java ---
package com.starry.admin.modules.order.service.impl;

import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.github.yulichang.wrapper.MPJLambdaWrapper;
import com.starry.admin.modules.order.mapper.PlayOrderRefundInfoMapper;
import com.starry.admin.modules.order.module.entity.PlayOrderRefundInfoEntity;
import com.starry.admin.modules.order.module.vo.PlayOrderRefundQueryVo;
import com.starry.admin.modules.order.module.vo.PlayOrderRefundReturnVo;
import com.starry.admin.modules.order.service.IPlayOrderRefundInfoService;
import com.starry.common.utils.IdUtils;
import java.math.BigDecimal;
import java.util.Arrays;
import javax.annotation.Resource;
import org.springframework.stereotype.Service;

/**
 * 订单退款信息Service业务层处理
 *
 * @author admin
 * @since 2024-05-09
 */
@Service
public class PlayOrderRefundInfoServiceImpl extends ServiceImpl<PlayOrderRefundInfoMapper, PlayOrderRefundInfoEntity>
        implements
            IPlayOrderRefundInfoService {
    @Resource
    private PlayOrderRefundInfoMapper playOrderRefundInfoMapper;

    @Override
    public PlayOrderRefundInfoEntity selectPlayOrderRefundInfoByOrderId(String orderId) {
        LambdaQueryWrapper<PlayOrderRefundInfoEntity> lambdaQueryWrapper = new LambdaQueryWrapper<>();
        lambdaQueryWrapper.eq(PlayOrderRefundInfoEntity::getOrderId, orderId);
        return this.baseMapper.selectOne(lambdaQueryWrapper);
    }

    /**
     * 查询订单退款信息
     *
     * @param id
     *            订单退款信息主键
     * @return 订单退款信息
     */
    @Override
    public PlayOrderRefundInfoEntity selectPlayOrderRefundInfoById(String id) {
        return this.baseMapper.selectById(id);
    }

    @Override
    public IPage<PlayOrderRefundReturnVo> selectByPage(PlayOrderRefundQueryVo vo) {
        MPJLambdaWrapper<PlayOrderRefundInfoEntity> lambdaQueryWrapper = new MPJLambdaWrapper<>();
        return this.baseMapper.selectJoinPage(new Page<>(vo.getPageNum(), vo.getPageSize()),
                PlayOrderRefundReturnVo.class, lambdaQueryWrapper);
    }

    /**
     * 查询订单退款信息列表
     *
     * @param playOrderRefundInfo
     *            订单退款信息
     * @return 订单退款信息
     */
    @Override
    public IPage<PlayOrderRefundInfoEntity> selectPlayOrderRefundInfoByPage(
            PlayOrderRefundInfoEntity playOrderRefundInfo) {
        Page<PlayOrderRefundInfoEntity> page = new Page<>(1, 10);
        return this.baseMapper.selectPage(page, new LambdaQueryWrapper<PlayOrderRefundInfoEntity>());
    }

    @Override
    public void add(String orderId, String customId, String clerkId, String payMethod, String refundType,
            BigDecimal refundAmount, String refundReason, String refundByType, String refundById, String refundState,
            String reviewedRequired) {
        PlayOrderRefundInfoEntity orderRefundInfo = new PlayOrderRefundInfoEntity();
        orderRefundInfo.setOrderId(orderId);
        orderRefundInfo.setCustomId(customId);
        orderRefundInfo.setClerkId(clerkId);
        orderRefundInfo.setPayMethod(payMethod);
        orderRefundInfo.setRefundType(refundType);
        orderRefundInfo.setRefundAmount(refundAmount);
        orderRefundInfo.setRefundReason(refundReason);
        orderRefundInfo.setRefundByType(refundByType);
        orderRefundInfo.setRefundById(refundById);
        orderRefundInfo.setRefundState(refundState);
        orderRefundInfo.setReviewedRequired(reviewedRequired);
        this.save(orderRefundInfo);
    }

    /**
     * 新增订单退款信息
     *
     * @param playOrderRefundInfo
     *            订单退款信息
     * @return 结果
     */
    @Override
    public boolean create(PlayOrderRefundInfoEntity playOrderRefundInfo) {
        if (StrUtil.isBlankIfStr(playOrderRefundInfo.getId())) {
            playOrderRefundInfo.setId(IdUtils.getUuid());
        }
        return save(playOrderRefundInfo);
    }

    /**
     * 修改订单退款信息
     *
     * @param playOrderRefundInfo
     *            订单退款信息
     * @return 结果
     */
    @Override
    public boolean update(PlayOrderRefundInfoEntity playOrderRefundInfo) {
        return updateById(playOrderRefundInfo);
    }

    /**
     * 批量删除订单退款信息
     *
     * @param ids
     *            需要删除的订单退款信息主键
     * @return 结果
     */
    @Override
    public int deletePlayOrderRefundInfoByIds(String[] ids) {
        return playOrderRefundInfoMapper.deleteBatchIds(Arrays.asList(ids));
    }

    /**
     * 删除订单退款信息信息
     *
     * @param id
     *            订单退款信息主键
     * @return 结果
     */
    @Override
    public int deletePlayOrderRefundInfoById(String id) {
        return playOrderRefundInfoMapper.deleteById(id);
    }
}


--- File: play-admin/src/main/java/com/starry/admin/modules/personnel/controller/PlayBalanceDetailsInfoController.java ---
package com.starry.admin.modules.personnel.controller;

import com.starry.admin.modules.personnel.module.entity.PlayBalanceDetailsInfoEntity;
import com.starry.admin.modules.personnel.module.vo.PlayBalanceDetailsQueryVo;
import com.starry.admin.modules.personnel.service.IPlayBalanceDetailsInfoService;
import com.starry.common.annotation.Log;
import com.starry.common.enums.BusinessType;
import com.starry.common.result.R;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiParam;
import io.swagger.annotations.ApiResponse;
import io.swagger.annotations.ApiResponses;
import javax.annotation.Resource;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;

/**
 * 余额明细Controller
 *
 * @author admin
 * @since 2024-04-30
 */
@Api(tags = "余额明细管理", description = "余额明细信息管理相关接口，包括查询、修改和删除等操作")
@RestController
@RequestMapping("/balance/details")
public class PlayBalanceDetailsInfoController {
    @Resource
    private IPlayBalanceDetailsInfoService playBalanceDetailsInfoService;

    /**
     * 查询余额明细列表
     */
    @ApiOperation(value = "分页查询明细列表", notes = "分页查询余额明细信息列表")
    @ApiResponses({@ApiResponse(code = 200, message = "操作成功")})
    @PostMapping("/listByPage")
    public R list(@ApiParam(value = "查询条件", required = true) @Validated @RequestBody PlayBalanceDetailsQueryVo vo) {
        return R.ok(playBalanceDetailsInfoService.selectByPage(vo));
    }

    /**
     * 获取余额明细详细信息
     */
    @ApiOperation(value = "获取明细详情", notes = "根据ID获取余额明细详细信息")
    @ApiImplicitParam(name = "id", value = "明细ID", required = true, paramType = "path", dataType = "String", example = "1")
    @ApiResponses({@ApiResponse(code = 200, message = "操作成功", response = PlayBalanceDetailsInfoEntity.class)})
    @GetMapping(value = "/{id}")
    public R getInfo(@PathVariable("id") String id) {
        return R.ok(playBalanceDetailsInfoService.selectPlayBalanceDetailsInfoById(id));
    }

    /**
     * 修改余额明细
     */
    @ApiOperation(value = "修改明细信息", notes = "修改余额明细信息")
    @ApiImplicitParam(name = "id", value = "明细ID", required = true, paramType = "path", dataType = "String", example = "1")
    @ApiResponses({@ApiResponse(code = 200, message = "操作成功"), @ApiResponse(code = 500, message = "修改失败，包含详细错误信息")})
    // @PreAuthorize("@customSs.hasPermission('balance:details:update')")
    @Log(title = "余额明细", businessType = BusinessType.UPDATE)
    @PostMapping(value = "/update/{id}")
    public R update(@PathVariable String id,
            @ApiParam(value = "明细信息", required = true) @RequestBody PlayBalanceDetailsInfoEntity playBalanceDetailsInfo) {
        playBalanceDetailsInfo.setId(id);
        boolean success = playBalanceDetailsInfoService.update(playBalanceDetailsInfo);
        if (success) {
            return R.ok();
        }
        return R.error("修改失败");
    }

    /**
     * 删除余额明细
     */
    @ApiOperation(value = "删除明细信息", notes = "根据ID批量删除余额明细信息")
    @ApiImplicitParam(name = "ids", value = "明细ID数组", required = true, paramType = "path", dataType = "String[]", example = "1,2,3")
    @ApiResponses({@ApiResponse(code = 200, message = "操作成功")})
    // @PreAuthorize("@customSs.hasPermission('balance:details:delete')")
    @Log(title = "余额明细", businessType = BusinessType.DELETE)
    @DeleteMapping("/{ids}")
    public R remove(@PathVariable String[] ids) {
        return R.ok(playBalanceDetailsInfoService.deletePlayBalanceDetailsInfoByIds(ids));
    }
}


--- File: play-admin/src/main/java/com/starry/admin/modules/personnel/controller/PlayPersonnelAdminInfoController.java ---
package com.starry.admin.modules.personnel.controller;

import com.baomidou.mybatisplus.core.metadata.IPage;
import com.starry.admin.common.exception.CustomException;
import com.starry.admin.modules.personnel.module.entity.PlayPersonnelAdminInfoEntity;
import com.starry.admin.modules.personnel.module.vo.PlayPersonnelAdminInfoEditAddInfoVo;
import com.starry.admin.modules.personnel.module.vo.PlayPersonnelAdminInfoEditBaseInfoVo;
import com.starry.admin.modules.personnel.module.vo.PlayPersonnelAdminInfoQueryVo;
import com.starry.admin.modules.personnel.module.vo.PlayPersonnelAdminInfoReturnVo;
import com.starry.admin.modules.personnel.service.IPlayPersonnelAdminInfoService;
import com.starry.admin.modules.system.module.entity.SysUserEntity;
import com.starry.admin.modules.system.service.SysRoleService;
import com.starry.admin.modules.system.service.SysUserRoleService;
import com.starry.admin.modules.system.service.SysUserService;
import com.starry.common.annotation.Log;
import com.starry.common.enums.BusinessType;
import com.starry.common.result.R;
import com.starry.common.utils.ConvertUtil;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiParam;
import io.swagger.annotations.ApiResponse;
import io.swagger.annotations.ApiResponses;
import java.time.LocalDateTime;
import javax.annotation.Resource;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;

/**
 * 管理员管理Controller
 *
 * @author admin
 * @since 2024-06-14
 */
@Api(tags = "管理员管理", description = "管理员信息管理相关接口，包括查询、新增、修改和删除等操作")
@RestController
@RequestMapping("/personnel/admin")
public class PlayPersonnelAdminInfoController {
    @Resource
    private IPlayPersonnelAdminInfoService playPersonnelAdminInfoService;

    @Resource
    private SysUserService sysUserService;

    @Resource
    private SysRoleService sysRoleService;

    @Resource
    private SysUserRoleService userRoleService;

    /**
     * 查询管理员管理信息列表
     */
    @ApiOperation(value = "分页查询管理员列表", notes = "分页查询管理员信息列表")
    @ApiResponses({
            @ApiResponse(code = 200, message = "操作成功", response = PlayPersonnelAdminInfoReturnVo.class, responseContainer = "Page")})
    @PostMapping("/listByPage")
    public R listByPage(
            @ApiParam(value = "查询条件", required = true) @Validated @RequestBody PlayPersonnelAdminInfoQueryVo vo) {
        IPage<PlayPersonnelAdminInfoReturnVo> list = playPersonnelAdminInfoService.selectByPage(vo);
        return R.ok(list);
    }

    /**
     * 获取管理员管理详细信息
     */
    @ApiOperation(value = "获取管理员详情", notes = "根据ID获取管理员详细信息")
    @ApiImplicitParam(name = "id", value = "管理员ID", required = true, paramType = "path", dataType = "String", example = "1")
    @ApiResponses({@ApiResponse(code = 200, message = "操作成功", response = PlayPersonnelAdminInfoEntity.class)})
    @GetMapping(value = "/{id}")
    public R getInfo(@PathVariable("id") String id) {
        return R.ok(playPersonnelAdminInfoService.selectPlayPersonnelAdminInfoById(id));
    }

    /**
     * 新增管理员管理信息
     */
    @ApiOperation(value = "新增管理员信息", notes = "创建新的管理员信息")
    @ApiResponses({@ApiResponse(code = 200, message = "操作成功"), @ApiResponse(code = 500, message = "添加失败，包含详细错误信息")})
    @Log(title = "管理员管理信息", businessType = BusinessType.INSERT)
    @PostMapping("/createBaseInfo")
    public R createBaseInfo(
            @ApiParam(value = "管理员信息", required = true) @Validated @RequestBody PlayPersonnelAdminInfoEditAddInfoVo vo) {
        // 校验对应用户是否存在
        SysUserEntity sysUserEntity = sysUserService.selectUserById(vo.getSysUserId());
        // 校验当前用户是否已经是组长
        PlayPersonnelAdminInfoEntity groupInfoEntity = playPersonnelAdminInfoService.selectByUserId(vo.getSysUserId());
        if (groupInfoEntity != null) {
            throw new CustomException("当前账号已经是管理员,无法重复添加");
        }
        // 添加管理员
        PlayPersonnelAdminInfoEntity entity = ConvertUtil.entityToVo(vo, PlayPersonnelAdminInfoEntity.class);
        entity.setSysUserCode(sysUserEntity.getUserCode());
        entity.setAddTime(LocalDateTime.now());
        boolean success = playPersonnelAdminInfoService.create(entity);
        if (success) {
            // 新建管理员后，添加对应的用户角色权限
            userRoleService.addUserRole(vo.getSysUserId(), sysRoleService.selectByRoleKey("operator").getRoleId());
            return R.ok();
        }
        return R.error("添加失败");
    }

    /**
     * 修改管理员管理信息
     */
    @ApiOperation(value = "修改管理员信息", notes = "修改管理员基本信息")
    @ApiResponses({@ApiResponse(code = 200, message = "操作成功"), @ApiResponse(code = 500, message = "修改失败，包含详细错误信息")})
    @Log(title = "管理员管理信息", businessType = BusinessType.UPDATE)
    @PostMapping(value = "/updateBaseInfo")
    public R updateBaseInfo(
            @ApiParam(value = "管理员信息", required = true) @Validated @RequestBody PlayPersonnelAdminInfoEditBaseInfoVo vo) {

        // 校验对应用户是否存在
        SysUserEntity sysUserEntity = sysUserService.selectUserById(vo.getSysUserId());
        // 校验当前用户是否已经是组长
        PlayPersonnelAdminInfoEntity adminInfoEntity = playPersonnelAdminInfoService.selectByUserId(vo.getSysUserId());
        if (adminInfoEntity != null && !adminInfoEntity.getId().equals(vo.getId())) {
            throw new CustomException("当前账号已经是管理员,无法重复添加");
        }
        PlayPersonnelAdminInfoEntity entity = ConvertUtil.entityToVo(vo, PlayPersonnelAdminInfoEntity.class);
        entity.setSysUserCode(sysUserEntity.getUserCode());
        boolean success = playPersonnelAdminInfoService.update(entity);
        if (success) {
            // 新建管理员后，添加对应的用户角色权限
            userRoleService.addUserRole(vo.getSysUserId(), sysRoleService.selectByRoleKey("operator").getRoleId());
        }
        return R.error("修改失败");
    }

    /**
     * 删除管理员管理
     */
    @ApiOperation(value = "删除管理员信息", notes = "根据ID批量删除管理员信息")
    @ApiImplicitParam(name = "ids", value = "管理员ID数组", required = true, paramType = "path", dataType = "String[]", example = "1,2,3")
    @ApiResponses({@ApiResponse(code = 200, message = "操作成功")})
    @PreAuthorize("@customSs.hasPermission('play:info:remove')")
    @Log(title = "管理员管理", businessType = BusinessType.DELETE)
    @DeleteMapping("/{ids}")
    public R remove(@PathVariable String[] ids) {
        Long ruleId = sysRoleService.selectByRoleKey("operator").getRoleId();
        for (String id : ids) {
            // 判断数据是否存在
            PlayPersonnelAdminInfoEntity entity = playPersonnelAdminInfoService.selectPlayPersonnelAdminInfoById(id);
            // 删除管理员
            playPersonnelAdminInfoService.deletePlayPersonnelAdminInfoById(id);
            // 删除对应的权限信息
            userRoleService.delUserRole(entity.getSysUserId(), ruleId);
        }
        return R.ok();
    }
}


--- File: play-admin/src/main/java/com/starry/admin/modules/personnel/controller/PlayPersonnelGroupInfoController.java ---
package com.starry.admin.modules.personnel.controller;

import com.baomidou.mybatisplus.core.metadata.IPage;
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.personnel.module.entity.PlayPersonnelGroupInfoEntity;
import com.starry.admin.modules.personnel.module.vo.PlayPersonnelGroupInfoEditAddInfoVo;
import com.starry.admin.modules.personnel.module.vo.PlayPersonnelGroupInfoEditBaseInfoVo;
import com.starry.admin.modules.personnel.module.vo.PlayPersonnelGroupInfoQueryVo;
import com.starry.admin.modules.personnel.module.vo.PlayPersonnelGroupInfoReturnVo;
import com.starry.admin.modules.personnel.service.IPlayPersonnelGroupInfoService;
import com.starry.admin.modules.system.module.entity.SysUserEntity;
import com.starry.admin.modules.system.service.SysRoleService;
import com.starry.admin.modules.system.service.SysUserRoleService;
import com.starry.admin.modules.system.service.SysUserService;
import com.starry.common.annotation.Log;
import com.starry.common.enums.BusinessType;
import com.starry.common.result.R;
import com.starry.common.utils.ConvertUtil;
import com.starry.common.utils.IdUtils;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiParam;
import io.swagger.annotations.ApiResponse;
import io.swagger.annotations.ApiResponses;
import java.time.LocalDateTime;
import java.util.List;
import javax.annotation.Resource;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;

/**
 * 店员分组信息Controller
 *
 * @author admin
 * @since 2024-05-31
 */
@Api(tags = "店员分组管理", description = "店员分组信息管理相关接口，包括查询、新增、修改和删除等操作")
@RestController
@RequestMapping("/personnel/group")
public class PlayPersonnelGroupInfoController {

    @Resource
    private IPlayPersonnelGroupInfoService playClerkGroupInfoService;

    @Resource
    private IPlayClerkUserInfoService playClerkUserInfoService;

    @Resource
    private SysUserService sysUserService;

    @Resource
    private SysRoleService sysRoleService;

    @Resource
    private SysUserRoleService userRoleService;

    /**
     * 查询店员分类信息列表
     */
    @ApiOperation(value = "分页查询分组列表", notes = "分页查询店员分组信息列表")
    @ApiResponses({
            @ApiResponse(code = 200, message = "操作成功", response = PlayPersonnelGroupInfoReturnVo.class, responseContainer = "Page")})
    @PostMapping("/listByPage")
    public R listByPage(
            @ApiParam(value = "查询条件", required = true) @Validated @RequestBody PlayPersonnelGroupInfoQueryVo vo) {
        IPage<PlayPersonnelGroupInfoReturnVo> list = playClerkGroupInfoService.selectByPage(vo);
        for (PlayPersonnelGroupInfoReturnVo record : list.getRecords()) {
            List<PlayClerkUserInfoEntity> clerkUserInfoEntities = playClerkUserInfoService
                    .listAllByGroupId(record.getId());
            record.setTotalEmployeesNumber(clerkUserInfoEntities.size());
            Integer listingEmployeesNumber = 0;
            for (PlayClerkUserInfoEntity clerkUserInfoEntity : clerkUserInfoEntities) {
                if ("1".equals(clerkUserInfoEntity.getListingState())) {
                    listingEmployeesNumber++;
                }
            }
            record.setListingEmployeesNumber(listingEmployeesNumber);
        }
        return R.ok(list);
    }

    /**
     * 新增店员分组信息
     */
    @ApiOperation(value = "新增分组信息", notes = "创建新的店员分组信息")
    @ApiResponses({@ApiResponse(code = 200, message = "操作成功"), @ApiResponse(code = 500, message = "添加失败，包含详细错误信息")})
    @Log(title = "店员分组信息", businessType = BusinessType.INSERT)
    @PostMapping("/createBaseInfo")
    public R createBaseInfo(
            @ApiParam(value = "分组信息", required = true) @Validated @RequestBody PlayPersonnelGroupInfoEditAddInfoVo vo) {
        // 校验对应用户是否存在
        SysUserEntity sysUserEntity = sysUserService.selectUserById(vo.getSysUserId());
        // 校验当前用户是否已经是组长
        PlayPersonnelGroupInfoEntity groupInfoEntity = playClerkGroupInfoService.selectByUserId(vo.getSysUserId());
        if (groupInfoEntity != null) {
            throw new CustomException("当前账号已经是组长,无法重复添加");
        }
        // 添加组长配置
        PlayPersonnelGroupInfoEntity entity = ConvertUtil.entityToVo(vo, PlayPersonnelGroupInfoEntity.class);
        entity.setId(IdUtils.getUuid());
        entity.setSysUserCode(sysUserEntity.getUserCode());
        entity.setAddTime(LocalDateTime.now());
        boolean success = playClerkGroupInfoService.create(entity);
        if (success) {
            // 新建组长后，添加对应的用户角色权限
            userRoleService.addUserRole(vo.getSysUserId(), sysRoleService.selectByRoleKey("leader").getRoleId());
            return R.ok("成功");
        }
        return R.error("添加失败");
    }

    /**
     * 修改店员分组信息
     */
    @ApiOperation(value = "修改分组信息", notes = "修改店员分组基本信息")
    @ApiResponses({@ApiResponse(code = 200, message = "操作成功"), @ApiResponse(code = 500, message = "修改失败，包含详细错误信息")})
    @Log(title = "店员分组信息", businessType = BusinessType.UPDATE)
    @PostMapping(value = "/updateBaseInfo")
    public R updateBaseInfo(
            @ApiParam(value = "分组信息", required = true) @Validated @RequestBody PlayPersonnelGroupInfoEditBaseInfoVo vo) {
        SysUserEntity sysUserEntity = sysUserService.selectUserById(vo.getSysUserId());
        // 校验当前用户是否已经是组长
        PlayPersonnelGroupInfoEntity groupInfoEntity = playClerkGroupInfoService.selectByUserId(vo.getSysUserId());
        // 校验当前用户是否已经是组长，只有当前用户不为组长时
        if (groupInfoEntity != null && !groupInfoEntity.getId().equals(vo.getId())) {
            throw new CustomException("当前账号已经是组长,无法重复添加");
        }
        PlayPersonnelGroupInfoEntity entity = ConvertUtil.entityToVo(vo, PlayPersonnelGroupInfoEntity.class);
        entity.setSysUserCode(sysUserEntity.getUserCode());
        boolean success = playClerkGroupInfoService.update(entity);
        if (success) {
            // 新建组长后，添加对应的用户角色权限
            userRoleService.addUserRole(vo.getSysUserId(), sysRoleService.selectByRoleKey("leader").getRoleId());
            return R.ok("成功");
        }
        return R.error("修改失败");
    }

    /**
     * 删除店员分组信息
     */
    @ApiOperation(value = "删除分组信息", notes = "根据ID批量删除店员分组信息")
    @ApiImplicitParam(name = "ids", value = "分组ID数组", required = true, paramType = "path", dataType = "String[]", example = "1,2,3")
    @ApiResponses({@ApiResponse(code = 200, message = "操作成功"), @ApiResponse(code = 500, message = "删除失败，包含详细错误信息")})
    @Log(title = "店员分组信息", businessType = BusinessType.DELETE)
    @DeleteMapping("/{ids}")
    public R remove(@PathVariable String[] ids) {
        for (String id : ids) {
            List<PlayClerkUserInfoEntity> clerkUserInfoEntities = playClerkUserInfoService.listAllByGroupId(id);
            if (!clerkUserInfoEntities.isEmpty()) {
                throw new CustomException("分组中存在店员,禁止删除");
            }
        }
        Long ruleId = sysRoleService.selectByRoleKey("leader").getRoleId();
        for (String id : ids) {
            // 判断数据是否存在
            PlayPersonnelGroupInfoEntity entity = playClerkGroupInfoService.selectPlayClerkGroupInfoById(id);
            // 删除组长
            playClerkGroupInfoService.deletePlayClerkGroupInfoById(id);
            // 删除对应的权限信息
            userRoleService.delUserRole(entity.getSysUserId(), ruleId);

        }
        return R.ok();
    }
}


--- File: play-admin/src/main/java/com/starry/admin/modules/personnel/controller/PlayPersonnelUserInfoController.java ---
package com.starry.admin.modules.personnel.controller;

import com.baomidou.mybatisplus.core.metadata.IPage;
import com.starry.admin.modules.personnel.module.vo.PlayPersonnelUserInfoQueryVo;
import com.starry.admin.modules.personnel.module.vo.PlayPersonnelUserInfoReturnVo;
import com.starry.admin.modules.system.module.entity.SysUserEntity;
import com.starry.admin.modules.system.service.SysUserService;
import com.starry.common.result.R;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiParam;
import io.swagger.annotations.ApiResponse;
import io.swagger.annotations.ApiResponses;
import java.util.List;
import javax.annotation.Resource;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;

/**
 * 管理员管理Controller
 *
 * @author admin
 * @since 2024-06-14
 */
@Api(tags = "系统用户管理", description = "系统用户信息管理相关接口，包括查询等操作")
@RestController
@RequestMapping("/personnel/user")
public class PlayPersonnelUserInfoController {

    @Resource
    private SysUserService sysUserService;

    /**
     * 分页查询账户信息列表
     */
    @ApiOperation(value = "分页查询用户列表", notes = "分页查询系统用户信息列表")
    @ApiResponses({
            @ApiResponse(code = 200, message = "操作成功", response = PlayPersonnelUserInfoReturnVo.class, responseContainer = "Page")})
    @PostMapping("/listByPage")
    public R listByPage(
            @ApiParam(value = "查询条件", required = true) @Validated @RequestBody PlayPersonnelUserInfoQueryVo vo) {
        IPage<PlayPersonnelUserInfoReturnVo> list = sysUserService.selectByPage(vo);
        return R.ok(list);
    }

    /**
     * 查询所有账户信息列表
     */
    @ApiOperation(value = "查询所有用户", notes = "查询所有系统用户信息")
    @ApiResponses({
            @ApiResponse(code = 200, message = "操作成功", response = SysUserEntity.class, responseContainer = "List")})
    @GetMapping("/listAll")
    public R listAll() {
        List<SysUserEntity> list = sysUserService.selectAll();
        return R.ok(list);
    }

}


--- File: play-admin/src/main/java/com/starry/admin/modules/personnel/controller/PlayPersonnelWaiterInfoController.java ---
package com.starry.admin.modules.personnel.controller;

import com.baomidou.mybatisplus.core.metadata.IPage;
import com.starry.admin.common.exception.CustomException;
import com.starry.admin.modules.personnel.module.entity.PlayPersonnelWaiterInfoEntity;
import com.starry.admin.modules.personnel.module.vo.PlayPersonnelWaiterInfoEditAddInfoVo;
import com.starry.admin.modules.personnel.module.vo.PlayPersonnelWaiterInfoEditBaseInfoVo;
import com.starry.admin.modules.personnel.module.vo.PlayPersonnelWaiterInfoQueryVo;
import com.starry.admin.modules.personnel.module.vo.PlayPersonnelWaiterInfoReturnVo;
import com.starry.admin.modules.personnel.service.IPlayPersonnelWaiterInfoService;
import com.starry.admin.modules.system.module.entity.SysUserEntity;
import com.starry.admin.modules.system.service.SysRoleService;
import com.starry.admin.modules.system.service.SysUserRoleService;
import com.starry.admin.modules.system.service.SysUserService;
import com.starry.common.annotation.Log;
import com.starry.common.enums.BusinessType;
import com.starry.common.result.R;
import com.starry.common.utils.ConvertUtil;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiParam;
import io.swagger.annotations.ApiResponse;
import io.swagger.annotations.ApiResponses;
import java.time.LocalDateTime;
import javax.annotation.Resource;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;

/**
 * 客服信息Controller
 *
 * @author admin
 * @since 2024-06-14
 */
@Api(tags = "客服管理", description = "客服信息管理相关接口，包括查询、新增、修改和删除等操作")
@RestController
@RequestMapping("/personnel/waiter")
public class PlayPersonnelWaiterInfoController {
    @Resource
    private IPlayPersonnelWaiterInfoService playClerkWaiterInfoService;

    @Resource
    private SysUserService sysUserService;

    @Resource
    private SysRoleService sysRoleService;

    @Resource
    private SysUserRoleService userRoleService;

    /**
     * 查询客服信息列表
     */
    @ApiOperation(value = "分页查询客服列表", notes = "分页查询客服信息列表")
    @ApiResponses({
            @ApiResponse(code = 200, message = "操作成功", response = PlayPersonnelWaiterInfoReturnVo.class, responseContainer = "Page")})
    @PostMapping("/listByPage")
    public R listByPage(
            @ApiParam(value = "查询条件", required = true) @Validated @RequestBody PlayPersonnelWaiterInfoQueryVo vo) {
        IPage<PlayPersonnelWaiterInfoReturnVo> list = playClerkWaiterInfoService.selectByPage(vo);
        return R.ok(list);
    }

    /**
     * 新增客服信息列表
     */
    @ApiOperation(value = "新增客服信息", notes = "创建新的客服信息")
    @ApiResponses({@ApiResponse(code = 200, message = "操作成功"), @ApiResponse(code = 500, message = "添加失败，包含详细错误信息")})
    @Log(title = "客服管理信息", businessType = BusinessType.INSERT)
    @PostMapping("/createBaseInfo")
    public R createBaseInfo(
            @ApiParam(value = "客服信息", required = true) @Validated @RequestBody PlayPersonnelWaiterInfoEditAddInfoVo vo) {
        // 校验对应用户是否存在
        SysUserEntity sysUserEntity = sysUserService.selectUserById(vo.getSysUserId());
        // 校验当前用户是否已经是组长
        PlayPersonnelWaiterInfoEntity waiterInfoEntity = playClerkWaiterInfoService.selectByUserId(vo.getSysUserId());
        if (waiterInfoEntity != null) {
            throw new CustomException("当前账号已经是客服,无法重复添加");
        }

        PlayPersonnelWaiterInfoEntity entity = ConvertUtil.entityToVo(vo, PlayPersonnelWaiterInfoEntity.class);
        entity.setSysUserCode(sysUserEntity.getUserCode());
        entity.setAddTime(LocalDateTime.now());
        boolean success = playClerkWaiterInfoService.create(entity);
        if (success) {
            // 新建客服后，添加对应的用户角色权限
            userRoleService.addUserRole(vo.getSysUserId(), sysRoleService.selectByRoleKey("waiter").getRoleId());
            return R.ok();
        }
        return R.error("添加失败");
    }

    /**
     * 修改客服信息列表
     */
    @ApiOperation(value = "修改客服信息", notes = "修改客服基本信息")
    @ApiResponses({@ApiResponse(code = 200, message = "操作成功"), @ApiResponse(code = 500, message = "修改失败，包含详细错误信息")})
    @Log(title = "客服管理信息", businessType = BusinessType.UPDATE)
    @PostMapping(value = "/updateBaseInfo")
    public R updateBaseInfo(
            @ApiParam(value = "客服信息", required = true) @Validated @RequestBody PlayPersonnelWaiterInfoEditBaseInfoVo vo) {

        // 校验对应用户是否存在
        SysUserEntity sysUserEntity = sysUserService.selectUserById(vo.getSysUserId());
        // 校验当前用户是否已经是组长
        PlayPersonnelWaiterInfoEntity waiterInfoEntity = playClerkWaiterInfoService.selectByUserId(vo.getSysUserId());
        if (waiterInfoEntity != null && !waiterInfoEntity.getId().equals(vo.getId())) {
            throw new CustomException("当前账号已经是客服,无法重复添加");
        }

        PlayPersonnelWaiterInfoEntity entity = ConvertUtil.entityToVo(vo, PlayPersonnelWaiterInfoEntity.class);
        entity.setSysUserCode(sysUserEntity.getUserCode());
        boolean success = playClerkWaiterInfoService.update(entity);
        if (success) {
            // 新建客服后，添加对应的用户角色权限
            userRoleService.addUserRole(vo.getSysUserId(), sysRoleService.selectByRoleKey("waiter").getRoleId());
            return R.ok();
        }
        return R.error("修改失败");
    }

    /**
     * 删除客服信息
     */
    @ApiOperation(value = "删除客服信息", notes = "根据ID批量删除客服信息")
    @ApiImplicitParam(name = "ids", value = "客服ID数组", required = true, paramType = "path", dataType = "String[]", example = "1,2,3")
    @ApiResponses({@ApiResponse(code = 200, message = "操作成功"), @ApiResponse(code = 500, message = "删除失败，包含详细错误信息")})
    @Log(title = "客服信息", businessType = BusinessType.DELETE)
    @DeleteMapping("/{ids}")
    public R remove(@PathVariable String[] ids) {
        Long ruleId = sysRoleService.selectByRoleKey("operator").getRoleId();
        for (String id : ids) {
            // 判断数据是否存在
            PlayPersonnelWaiterInfoEntity entity = playClerkWaiterInfoService.selectPlayClerkWaiterInfoById(id);
            // 删除客服
            playClerkWaiterInfoService.deletePlayClerkWaiterInfoById(id);
            // 删除对应的权限信息
            userRoleService.delUserRole(entity.getSysUserId(), ruleId);

        }
        return R.ok();
    }
}


--- File: play-admin/src/main/java/com/starry/admin/modules/personnel/mapper/PlayBalanceDetailsInfoMapper.java ---
package com.starry.admin.modules.personnel.mapper;

import com.github.yulichang.base.MPJBaseMapper;
import com.starry.admin.modules.personnel.module.entity.PlayBalanceDetailsInfoEntity;

/**
 * 余额明细Mapper接口
 *
 * @author admin
 * @since 2024-04-30
 */
public interface PlayBalanceDetailsInfoMapper extends MPJBaseMapper<PlayBalanceDetailsInfoEntity> {

}


--- File: play-admin/src/main/java/com/starry/admin/modules/personnel/mapper/PlayPersonnelAdminInfoMapper.java ---
package com.starry.admin.modules.personnel.mapper;

import com.github.yulichang.base.MPJBaseMapper;
import com.starry.admin.modules.personnel.module.entity.PlayPersonnelAdminInfoEntity;

/**
 * 管理员管理Mapper接口
 *
 * @author admin
 * @since 2024-06-14
 */
public interface PlayPersonnelAdminInfoMapper extends MPJBaseMapper<PlayPersonnelAdminInfoEntity> {

}


--- File: play-admin/src/main/java/com/starry/admin/modules/personnel/mapper/PlayPersonnelGroupInfoMapper.java ---
package com.starry.admin.modules.personnel.mapper;

import com.github.yulichang.base.MPJBaseMapper;
import com.starry.admin.modules.personnel.module.entity.PlayPersonnelGroupInfoEntity;

/**
 * 店员分组信息Mapper接口
 *
 * @author admin
 * @since 2024-05-31
 */
public interface PlayPersonnelGroupInfoMapper extends MPJBaseMapper<PlayPersonnelGroupInfoEntity> {

}


--- File: play-admin/src/main/java/com/starry/admin/modules/personnel/mapper/PlayPersonnelWaiterInfoMapper.java ---
package com.starry.admin.modules.personnel.mapper;

import com.github.yulichang.base.MPJBaseMapper;
import com.starry.admin.modules.personnel.module.entity.PlayPersonnelWaiterInfoEntity;

/**
 * 客服信息Mapper接口
 *
 * @author admin
 * @since 2024-06-14
 */
public interface PlayPersonnelWaiterInfoMapper extends MPJBaseMapper<PlayPersonnelWaiterInfoEntity> {

}


--- File: play-admin/src/main/java/com/starry/admin/modules/personnel/module/entity/PlayBalanceDetailsInfoEntity.java ---
package com.starry.admin.modules.personnel.module.entity;

import com.baomidou.mybatisplus.annotation.TableName;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.starry.common.domain.BaseEntity;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.springframework.format.annotation.DateTimeFormat;

/**
 * 余额明细对象 play_balance_details_info
 *
 * @author admin
 * @since 2024-04-30
 */
@Data
@EqualsAndHashCode(callSuper = false)
@TableName("play_balance_details_info")
public class PlayBalanceDetailsInfoEntity extends BaseEntity<PlayBalanceDetailsInfoEntity> {

    /**
     * UUID
     */
    private String id;

    /**
     * 租户ID
     */
    private String tenantId;

    /**
     * 用户ID
     */
    private String userId;

    /**
     * 用户类型[0:陪聊;1:顾客]
     */
    private String userType;

    /**
     * 操作前余额
     */
    private BigDecimal balanceBeforeOperation;

    /**
     * 操作后余额
     */
    private BigDecimal balanceAfterOperation;

    /**
     * 操作类型（0:充值;1:消费;2:服务）
     */
    private String operationType;

    /**
     * 操作动作
     */
    private String operationAction;

    /**
     * 操作时间
     */
    @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
    private LocalDateTime operationTime;

    /**
     * 操作金额
     */
    private BigDecimal balanceMoney;

    /**
     * 赠送金额
     */
    private BigDecimal giftAmount;

    /**
     * 支付金额
     */
    private BigDecimal paymentAmount;

    /**
     * 订单ID
     */
    private String orderId;
}


--- File: play-admin/src/main/java/com/starry/admin/modules/personnel/module/entity/PlayPersonnelAdminInfoEntity.java ---
package com.starry.admin.modules.personnel.module.entity;

import com.baomidou.mybatisplus.annotation.TableName;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.starry.common.domain.BaseEntity;
import java.time.LocalDateTime;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.springframework.format.annotation.DateTimeFormat;

/**
 * 管理员管理对象 play_personnel_admin_info
 *
 * @author admin
 * @since 2024-06-14
 */
@Data
@EqualsAndHashCode(callSuper = false)
@TableName("play_personnel_admin_info")
public class PlayPersonnelAdminInfoEntity extends BaseEntity<PlayPersonnelAdminInfoEntity> {

    /**
     * UUID
     */
    private String id;

    /**
     * 租户ID
     */
    private String tenantId;

    /**
     * 账号ID
     */
    private String sysUserId;

    /**
     * 用户账号
     */
    private String sysUserCode;

    /**
     * 分组名称
     */
    private String adminName;

    /**
     * 责任人
     */
    private String leaderName;

    @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
    private LocalDateTime addTime;

}


--- File: play-admin/src/main/java/com/starry/admin/modules/personnel/module/entity/PlayPersonnelGroupInfoEntity.java ---
package com.starry.admin.modules.personnel.module.entity;

import com.baomidou.mybatisplus.annotation.TableName;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.starry.common.domain.BaseEntity;
import java.time.LocalDateTime;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.springframework.format.annotation.DateTimeFormat;

/**
 * 店员分组信息对象 play_clerk_group_info
 *
 * @author admin
 * @since 2024-05-31
 */
@Data
@EqualsAndHashCode(callSuper = false)
@TableName("play_personnel_group_info")
public class PlayPersonnelGroupInfoEntity extends BaseEntity<PlayPersonnelGroupInfoEntity> {

    /**
     * UUID
     */
    private String id;

    /**
     * 租户ID
     */
    private String tenantId;

    /**
     * 用户ID
     **/
    private String sysUserId;

    /**
     * 用户账号
     **/
    private String sysUserCode;

    /**
     * 分组名称
     */
    private String groupName;

    /**
     * 组长名称
     **/
    private String leaderName;

    @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
    private LocalDateTime addTime;

}


--- File: play-admin/src/main/java/com/starry/admin/modules/personnel/module/entity/PlayPersonnelWaiterInfoEntity.java ---
package com.starry.admin.modules.personnel.module.entity;

import com.baomidou.mybatisplus.annotation.TableName;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.starry.common.domain.BaseEntity;
import java.time.LocalDateTime;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.springframework.format.annotation.DateTimeFormat;

/**
 * 客服信息对象 play_clerk_waiter_info
 *
 * @author admin
 * @since 2024-06-14
 */
@Data
@EqualsAndHashCode(callSuper = false)
@TableName("play_personnel_waiter_info")
public class PlayPersonnelWaiterInfoEntity extends BaseEntity<PlayPersonnelWaiterInfoEntity> {

    /**
     * UUID
     */
    private String id;

    /**
     * 租户ID
     */
    private String tenantId;

    /**
     * 用户账号ID
     */
    private String sysUserId;

    /**
     * 用户账号
     */
    private String sysUserCode;

    /**
     * 客服名称
     */
    private String waiterName;

    @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
    private LocalDateTime addTime;

}


--- File: play-admin/src/main/java/com/starry/admin/modules/personnel/module/vo/PlayBalanceDetailsQueryVo.java ---
package com.starry.admin.modules.personnel.module.vo;

import com.starry.common.domain.BasePageEntity;
import java.util.List;
import lombok.Data;
import lombok.EqualsAndHashCode;

/**
 * 分页查询余额明细
 *
 * @author admin
 */
@EqualsAndHashCode(callSuper = true)
@Data
public class PlayBalanceDetailsQueryVo extends BasePageEntity {

    /**
     * 店员昵称
     */
    private String nickname;

    /**
     * 操作类型（0:充值;1:消费;2:服务）
     */
    private String operationType;

    /**
     * 支付方式，0：余额支付,1:微信支付,2:支付宝支付
     */
    private String payMethod;

    private String userType = "1";

    private List<String> operationTime;

    private String userId;
}


--- File: play-admin/src/main/java/com/starry/admin/modules/personnel/module/vo/PlayBalanceDetailsReturnVo.java ---
package com.starry.admin.modules.personnel.module.vo;

import java.math.BigDecimal;
import java.util.Date;
import lombok.Data;

/**
 * 余额明细查询对象
 *
 * @author admin
 */
@Data
public class PlayBalanceDetailsReturnVo {

    /**
     * UUID
     */
    private String id;

    /**
     * 操作前余额
     */
    private BigDecimal balanceBeforeOperation;

    /**
     * 操作后余额
     */
    private BigDecimal balanceAfterOperation;

    /**
     * 操作类型（0:充值;1:消费;2:服务）
     */
    private String operationType;

    /**
     * 操作动作
     */
    private String operationAction;

    /**
     * 操作时间
     */
    private Date operationTime;

    /**
     * 操作金额
     */
    private BigDecimal balanceMoney;

    /**
     * 支付方式，0：余额支付,1:微信支付,2:支付宝支付
     */
    private String payMethod;

    /**
     * 订单ID
     */
    private String orderId;

    /**
     * 订单ID
     */
    private String orderNo;

    /**
     * 订单金额
     */
    private BigDecimal orderMoney;

    /**
     * 赠送金额
     */
    private BigDecimal giftAmount;

    /**
     * 订单最终金额
     */
    private BigDecimal finalAmount;

    /**
     * 订单备注
     */
    private String orderRemark;

    /**
     * 顾客Id
     */
    private String customId;

    /**
     * 顾客昵称
     */
    private String customNickname;

    /**
     * 顾客头像
     */
    private String customAvatar;
}


--- File: play-admin/src/main/java/com/starry/admin/modules/personnel/module/vo/PlayPersonnelAdminInfoEditAddInfoVo.java ---
package com.starry.admin.modules.personnel.module.vo;

import javax.validation.constraints.NotBlank;
import lombok.Data;
import org.hibernate.validator.constraints.Length;

/**
 * 管理员管理信息新增
 *
 * @author admin
 * @since 2024/6/14 14:45
 **/
@Data
public class PlayPersonnelAdminInfoEditAddInfoVo {

    /**
     * 分组名称
     */
    @NotBlank(message = "用户ID不能为空")
    private String sysUserId;

    /**
     * 管理员名称
     */
    @NotBlank(message = "管理员名称不能为空")
    @Length(min = 1, max = 100, message = "字符长度在1-100之间")
    private String adminName;

}


--- File: play-admin/src/main/java/com/starry/admin/modules/personnel/module/vo/PlayPersonnelAdminInfoEditBaseInfoVo.java ---
package com.starry.admin.modules.personnel.module.vo;

import javax.validation.constraints.NotBlank;
import lombok.Data;
import org.hibernate.validator.constraints.Length;

/**
 * 管理员管理信息修改
 *
 * @author admin
 * @since 2024/6/14 14:45
 **/
@Data
public class PlayPersonnelAdminInfoEditBaseInfoVo {
    /**
     * UUID
     *
     * @since 2024/6/14 16:08
     **/
    @NotBlank(message = "ID不能为空")
    private String id;

    /**
     * 分组名称
     */
    @NotBlank(message = "用户ID不能为空")
    private String sysUserId;

    /**
     * 管理员名称
     */
    @NotBlank(message = "管理员名称不能为空")
    @Length(min = 1, max = 100, message = "字符长度在1-100之间")
    private String adminName;

}


--- File: play-admin/src/main/java/com/starry/admin/modules/personnel/module/vo/PlayPersonnelAdminInfoQueryVo.java ---
package com.starry.admin.modules.personnel.module.vo;

import com.starry.common.domain.BasePageEntity;
import lombok.Data;
import lombok.EqualsAndHashCode;

/**
 * 管理员管理信息
 *
 * @author admin
 * @since 2024/6/14 14:45
 **/
@EqualsAndHashCode(callSuper = true)
@Data
public class PlayPersonnelAdminInfoQueryVo extends BasePageEntity {

    /**
     * 管理员名称
     */
    private String adminName;

}


--- File: play-admin/src/main/java/com/starry/admin/modules/personnel/module/vo/PlayPersonnelAdminInfoReturnVo.java ---
package com.starry.admin.modules.personnel.module.vo;

import com.fasterxml.jackson.annotation.JsonFormat;
import java.time.LocalDateTime;
import javax.validation.constraints.NotBlank;
import lombok.Data;
import org.hibernate.validator.constraints.Length;
import org.springframework.format.annotation.DateTimeFormat;

/**
 * 管理员管理信息
 *
 * @author admin
 * @since 2024/6/14 14:45
 **/
@Data
public class PlayPersonnelAdminInfoReturnVo {

    /**
     * UUID
     */
    private String id;

    /**
     * 租户ID
     */
    private String tenantId;

    /**
     * 用户ID
     **/
    private String sysUserId;

    /**
     * 用户账号
     **/
    private String sysUserCode;

    /**
     * 管理员名称
     */
    @NotBlank(message = "管理员名称不能为空")
    @Length(min = 1, max = 100, message = "字符长度在1-100之间")
    private String adminName;

    @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
    private LocalDateTime addTime;

}


--- File: play-admin/src/main/java/com/starry/admin/modules/personnel/module/vo/PlayPersonnelGroupInfoEditAddInfoVo.java ---
package com.starry.admin.modules.personnel.module.vo;

import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import javax.validation.constraints.NotBlank;
import lombok.Data;
import org.hibernate.validator.constraints.Length;

/**
 * 店员分组基本信息修改
 *
 * @author admin
 * @since 2024/6/14 14:45
 **/
@Data
@ApiModel(value = "新增分组参数", description = "新增店员分组信息的请求参数")
public class PlayPersonnelGroupInfoEditAddInfoVo {

    /**
     * 分组名称
     */
    @NotBlank(message = "用户ID不能为空")
    @ApiModelProperty(value = "用户ID", required = true, example = "1", notes = "系统用户ID")
    private String sysUserId;

    /**
     * 分组名称
     */
    @NotBlank(message = "分组名称不能为空")
    @Length(min = 1, max = 100, message = "字符长度在1-100之间")
    @ApiModelProperty(value = "分组名称", required = true, example = "销售组", notes = "分组的名称，1-100字符")
    private String groupName;

    /**
     * 分组名称
     */
    @NotBlank(message = "组长名称不能为空")
    @Length(min = 1, max = 100, message = "字符长度在1-100之间")
    @ApiModelProperty(value = "组长名称", required = true, example = "张三", notes = "组长的名称，1-100字符")
    private String leaderName;

}


--- File: play-admin/src/main/java/com/starry/admin/modules/personnel/module/vo/PlayPersonnelGroupInfoEditBaseInfoVo.java ---
package com.starry.admin.modules.personnel.module.vo;

import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import javax.validation.constraints.NotBlank;
import lombok.Data;
import org.hibernate.validator.constraints.Length;

/**
 * 店员分组基本信息修改
 *
 * @author admin
 * @since 2024/6/14 14:45
 **/
@Data
@ApiModel(value = "修改分组参数", description = "修改店员分组信息的请求参数")
public class PlayPersonnelGroupInfoEditBaseInfoVo {
    /**
     * UUID
     *
     * @since 2024/6/14 16:08
     **/
    @NotBlank(message = "ID不能为空")
    @ApiModelProperty(value = "分组ID", required = true, example = "1", notes = "需要修改的分组ID")
    private String id;

    /**
     * 分组名称
     */
    @NotBlank(message = "用户ID不能为空")
    @ApiModelProperty(value = "用户ID", required = true, example = "1", notes = "系统用户ID")
    private String sysUserId;

    /**
     * 分组名称
     */
    @NotBlank(message = "分组名称不能为空")
    @Length(min = 1, max = 100, message = "字符长度在1-100之间")
    @ApiModelProperty(value = "分组名称", required = true, example = "销售组", notes = "分组的名称，1-100字符")
    private String groupName;

    /**
     * 分组名称
     */
    @NotBlank(message = "组长名称不能为空")
    @Length(min = 1, max = 100, message = "字符长度在1-100之间")
    @ApiModelProperty(value = "组长名称", required = true, example = "张三", notes = "组长的名称，1-100字符")
    private String leaderName;

}


--- File: play-admin/src/main/java/com/starry/admin/modules/personnel/module/vo/PlayPersonnelGroupInfoQueryVo.java ---
package com.starry.admin.modules.personnel.module.vo;

import com.starry.common.domain.BasePageEntity;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import lombok.EqualsAndHashCode;

/**
 * 店员分组管理信息
 *
 * @author admin
 * @since 2024/6/14 14:45
 **/
@EqualsAndHashCode(callSuper = true)
@Data
@ApiModel(value = "分组查询参数", description = "查询店员分组信息的条件参数")
public class PlayPersonnelGroupInfoQueryVo extends BasePageEntity {

    /**
     * 分组名称
     */
    @ApiModelProperty(value = "组长名称", example = "张三", notes = "组长的名称，支持模糊查询")
    private String leaderName;

}


--- File: play-admin/src/main/java/com/starry/admin/modules/personnel/module/vo/PlayPersonnelGroupInfoReturnVo.java ---
package com.starry.admin.modules.personnel.module.vo;

import com.fasterxml.jackson.annotation.JsonFormat;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import java.time.LocalDateTime;
import lombok.Data;
import org.springframework.format.annotation.DateTimeFormat;

/**
 * 店员分组管理信息
 *
 * @author admin
 * @since 2024/6/14 14:45
 **/
@Data
@ApiModel(value = "分组返回数据", description = "店员分组信息的返回数据")
public class PlayPersonnelGroupInfoReturnVo {

    /**
     * UUID
     */
    @ApiModelProperty(value = "分组ID", example = "1")
    private String id;

    /**
     * 租户ID
     */
    @ApiModelProperty(value = "租户ID", example = "1")
    private String tenantId;

    /**
     * 用户ID
     **/
    @ApiModelProperty(value = "用户ID", example = "1")
    private String sysUserId;

    /**
     * 用户账号
     **/
    @ApiModelProperty(value = "用户账号", example = "admin")
    private String sysUserCode;

    /**
     * 分组名称
     */
    @ApiModelProperty(value = "分组名称", example = "销售组")
    private String groupName;

    /**
     * 组长名称
     **/
    @ApiModelProperty(value = "组长名称", example = "张三")
    private String leaderName;

    /**
     * 排序
     */
    @ApiModelProperty(value = "排序", example = "1")
    private Long sort;

    /**
     * 员工总数量
     **/
    @ApiModelProperty(value = "员工总数", example = "10")
    private Integer totalEmployeesNumber;

    /**
     * 上架员工数量
     **/
    @ApiModelProperty(value = "上架员工数", example = "5")
    private Integer listingEmployeesNumber;

    @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
    @ApiModelProperty(value = "添加时间", example = "2024-01-01 12:00:00")
    private LocalDateTime addTime;

}


--- File: play-admin/src/main/java/com/starry/admin/modules/personnel/module/vo/PlayPersonnelUserInfoQueryVo.java ---
package com.starry.admin.modules.personnel.module.vo;

import com.starry.common.domain.BasePageEntity;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import java.util.List;
import lombok.Data;
import lombok.EqualsAndHashCode;

/**
 * @author admin
 * @since 2024/6/15 下午9:42
 **/
@EqualsAndHashCode(callSuper = true)
@Data
@ApiModel(value = "用户查询参数", description = "查询系统用户信息的条件参数")
public class PlayPersonnelUserInfoQueryVo extends BasePageEntity {

    /**
     * 用户ID
     */
    @ApiModelProperty(value = "系统用户ID", example = "1")
    private String sysUserId;

    /**
     * 用户名称
     */
    @ApiModelProperty(value = "用户账号", example = "admin", notes = "用户登录账号")
    private String userCode;

    /**
     * 角色类型[0:用户;1:店员;2:组长]
     */
    @ApiModelProperty(value = "角色类型", example = "0", notes = "角色类型[0:用户;1:店员;2:组长]")
    private String ruleType;

    /**
     * 用户ID
     */
    @ApiModelProperty(value = "用户ID", example = "1")
    private String userId;

    @ApiModelProperty(value = "添加时间范围", example = "['2024-01-01 00:00:00','2024-12-31 23:59:59']", notes = "用户添加时间范围，包含开始和结束时间")
    private List<String> addTime;
}


--- File: play-admin/src/main/java/com/starry/admin/modules/personnel/module/vo/PlayPersonnelUserInfoReturnVo.java ---
package com.starry.admin.modules.personnel.module.vo;

import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;

/**
 * @author admin
 * @since 2024/6/15 下午9:42
 **/
@Data
@ApiModel(value = "用户返回数据", description = "系统用户信息的返回数据")
public class PlayPersonnelUserInfoReturnVo {
    /**
     * 店员ID
     */
    @ApiModelProperty(value = "店员ID", example = "1")
    private String clerkId;

    /**
     * 店员昵称
     */
    @ApiModelProperty(value = "店员昵称", example = "小明")
    private String clerkNickname;

    /**
     * 店员头像
     */
    @ApiModelProperty(value = "店员头像", example = "https://example.com/avatar.jpg")
    private String clerkAvatar;

    @ApiModelProperty(value = "用户ID", example = "1")
    private String userId;

    @ApiModelProperty(value = "用户账号", example = "admin")
    private String userCode;

    /**
     * 创建时间
     */
    @ApiModelProperty(value = "创建时间", example = "2024-01-01 12:00:00")
    private String addTime;
}


--- File: play-admin/src/main/java/com/starry/admin/modules/personnel/module/vo/PlayPersonnelWaiterInfoEditAddInfoVo.java ---
package com.starry.admin.modules.personnel.module.vo;

import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import javax.validation.constraints.NotBlank;
import lombok.Data;
import org.hibernate.validator.constraints.Length;

/**
 * 客户基本信息修改
 *
 * @author admin
 * @since 2024/6/14 14:45
 **/
@Data
@ApiModel(value = "新增客服参数", description = "新增客服信息的请求参数")
public class PlayPersonnelWaiterInfoEditAddInfoVo {

    /**
     * 分组名称
     */
    @NotBlank(message = "用户ID不能为空")
    @ApiModelProperty(value = "用户ID", required = true, example = "1", notes = "系统用户ID")
    private String sysUserId;

    /**
     * 客服名称
     */
    @NotBlank(message = "客服名称不能为空")
    @Length(min = 1, max = 100, message = "字符长度在1-100之间")
    @ApiModelProperty(value = "客服名称", required = true, example = "张三", notes = "客服的名称，1-100字符")
    private String waiterName;

}


--- File: play-admin/src/main/java/com/starry/admin/modules/personnel/module/vo/PlayPersonnelWaiterInfoEditBaseInfoVo.java ---
package com.starry.admin.modules.personnel.module.vo;

import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import javax.validation.constraints.NotBlank;
import lombok.Data;
import org.hibernate.validator.constraints.Length;

/**
 * 客户基本信息修改
 *
 * @author admin
 * @since 2024/6/14 14:45
 **/
@Data
@ApiModel(value = "修改客服参数", description = "修改客服信息的请求参数")
public class PlayPersonnelWaiterInfoEditBaseInfoVo {
    /**
     * UUID
     *
     * @since 2024/6/14 16:08
     **/
    @NotBlank(message = "ID不能为空")
    @ApiModelProperty(value = "客服ID", required = true, example = "1", notes = "需要修改的客服ID")
    private String id;

    /**
     * 分组名称
     */
    @NotBlank(message = "用户ID不能为空")
    @ApiModelProperty(value = "用户ID", required = true, example = "1", notes = "系统用户ID")
    private String sysUserId;

    /**
     * 客服名称
     */
    @NotBlank(message = "客服名称不能为空")
    @Length(min = 1, max = 100, message = "字符长度在1-100之间")
    @ApiModelProperty(value = "客服名称", required = true, example = "张三", notes = "客服的名称，1-100字符")
    private String waiterName;

}


--- File: play-admin/src/main/java/com/starry/admin/modules/personnel/module/vo/PlayPersonnelWaiterInfoQueryVo.java ---
package com.starry.admin.modules.personnel.module.vo;

import com.starry.common.domain.BasePageEntity;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import lombok.EqualsAndHashCode;

/**
 * 客户管理信息
 *
 * @author admin
 * @since 2024/6/14 14:45
 **/
@EqualsAndHashCode(callSuper = true)
@Data
@ApiModel(value = "客服查询参数", description = "查询客服信息的条件参数")
public class PlayPersonnelWaiterInfoQueryVo extends BasePageEntity {

    /**
     * 分组名称
     */
    @ApiModelProperty(value = "客服名称", example = "张三", notes = "客服的名称，支持模糊查询")
    private String waiterName;

}


--- File: play-admin/src/main/java/com/starry/admin/modules/personnel/module/vo/PlayPersonnelWaiterInfoReturnVo.java ---
package com.starry.admin.modules.personnel.module.vo;

import com.fasterxml.jackson.annotation.JsonFormat;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import java.time.LocalDateTime;
import lombok.Data;
import org.springframework.format.annotation.DateTimeFormat;

/**
 * 客户管理信息
 *
 * @author admin
 * @since 2024/6/14 14:45
 **/
@Data
@ApiModel(value = "客服返回数据", description = "客服信息的返回数据")
public class PlayPersonnelWaiterInfoReturnVo {

    /**
     * UUID
     */
    @ApiModelProperty(value = "客服ID", example = "1")
    private String id;

    /**
     * 租户ID
     */
    @ApiModelProperty(value = "租户ID", example = "1")
    private String tenantId;

    /**
     * 用户ID
     **/
    @ApiModelProperty(value = "用户ID", example = "1")
    private String sysUserId;

    /**
     * 用户账号
     **/
    @ApiModelProperty(value = "用户账号", example = "admin")
    private String sysUserCode;

    /**
     * 客户名称
     */
    @ApiModelProperty(value = "客服名称", example = "张三")
    private String waiterName;

    @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
    @ApiModelProperty(value = "添加时间", example = "2024-01-01 12:00:00")
    private LocalDateTime addTime;

}


--- File: play-admin/src/main/java/com/starry/admin/modules/personnel/service/IPlayBalanceDetailsInfoService.java ---
package com.starry.admin.modules.personnel.service;

import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.service.IService;
import com.starry.admin.modules.personnel.module.entity.PlayBalanceDetailsInfoEntity;
import com.starry.admin.modules.personnel.module.vo.PlayBalanceDetailsQueryVo;
import com.starry.admin.modules.personnel.module.vo.PlayBalanceDetailsReturnVo;
import java.math.BigDecimal;

/**
 * 余额明细Service接口
 *
 * @author admin
 * @since 2024-04-30
 */
public interface IPlayBalanceDetailsInfoService extends IService<PlayBalanceDetailsInfoEntity> {

    /**
     * 查询余额明细
     *
     * @param id
     *            余额明细主键
     * @return 余额明细
     */
    PlayBalanceDetailsInfoEntity selectPlayBalanceDetailsInfoById(String id);

    /**
     * 查询余额明细列表
     *
     * @param vo
     *            余额明细查询对象
     * @return 余额明细集合
     */
    IPage<PlayBalanceDetailsReturnVo> selectByPage(PlayBalanceDetailsQueryVo vo);

    /**
     * 查询余额明细列表
     *
     * @param playBalanceDetailsInfo
     *            余额明细
     * @return 余额明细集合
     */
    IPage<PlayBalanceDetailsInfoEntity> selectPlayBalanceDetailsInfoByPage(
            PlayBalanceDetailsInfoEntity playBalanceDetailsInfo);

    /**
     * 新增余额明细信息
     *
     * @param userType
     *            用户类型[0:陪聊;1:顾客]
     * @param userId
     *            用户ID
     * @param balanceBeforeOperation
     *            操作前余额
     * @param balanceAfterOperation
     *            操作后余额
     * @param operationType
     *            操作类型（0:充值;1:消费,2:服务;3:退款）
     * @param operationAction
     *            操作动作
     * @param balanceMoney
     *            操作金额
     * @param giftAmount
     *            赠送金额
     * @param orderId
     *            订单ID
     */
    void insertBalanceDetailsInfo(String userType, String userId, BigDecimal balanceBeforeOperation,
            BigDecimal balanceAfterOperation, String operationType, String operationAction, BigDecimal balanceMoney,
            BigDecimal giftAmount, String orderId);

    /**
     * 新增余额明细
     *
     * @param playBalanceDetailsInfo
     *            余额明细
     * @return 结果
     */
    boolean create(PlayBalanceDetailsInfoEntity playBalanceDetailsInfo);

    /**
     * 修改余额明细
     *
     * @param playBalanceDetailsInfo
     *            余额明细
     * @return 结果
     */
    boolean update(PlayBalanceDetailsInfoEntity playBalanceDetailsInfo);

    /**
     * 批量删除余额明细
     *
     * @param ids
     *            需要删除的余额明细主键集合
     * @return 结果
     */
    int deletePlayBalanceDetailsInfoByIds(String[] ids);

    /**
     * 删除余额明细信息
     *
     * @param id
     *            余额明细主键
     * @return 结果
     */
    int deletePlayBalanceDetailsInfoById(String id);
}


--- File: play-admin/src/main/java/com/starry/admin/modules/personnel/service/IPlayPersonnelAdminInfoService.java ---
package com.starry.admin.modules.personnel.service;

import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.service.IService;
import com.starry.admin.modules.personnel.module.entity.PlayPersonnelAdminInfoEntity;
import com.starry.admin.modules.personnel.module.vo.PlayPersonnelAdminInfoQueryVo;
import com.starry.admin.modules.personnel.module.vo.PlayPersonnelAdminInfoReturnVo;

/**
 * 管理员管理Service接口
 *
 * @author admin
 * @since 2024-06-14
 */
public interface IPlayPersonnelAdminInfoService extends IService<PlayPersonnelAdminInfoEntity> {

    /**
     * 查询店员分组信息
     *
     * @param userId
     *            用户ID
     * @return 店员分组信息
     */
    PlayPersonnelAdminInfoEntity selectByUserId(String userId);

    /**
     * 查询管理员管理
     *
     * @param id
     *            管理员管理主键
     * @return 管理员管理
     */
    PlayPersonnelAdminInfoEntity selectPlayPersonnelAdminInfoById(String id);

    /**
     * 查询管理员管理列表
     *
     * @param vo
     *            管理员管理
     * @return 管理员管理集合
     */
    IPage<PlayPersonnelAdminInfoReturnVo> selectByPage(PlayPersonnelAdminInfoQueryVo vo);

    /**
     * 新增管理员管理
     *
     * @param playPersonnelAdminInfo
     *            管理员管理
     * @return 结果
     */
    boolean create(PlayPersonnelAdminInfoEntity playPersonnelAdminInfo);

    /**
     * 修改管理员管理
     *
     * @param playPersonnelAdminInfo
     *            管理员管理
     * @return 结果
     */
    boolean update(PlayPersonnelAdminInfoEntity playPersonnelAdminInfo);

    /**
     * 批量删除管理员管理
     *
     * @param ids
     *            需要删除的管理员管理主键集合
     * @return 结果
     */
    int deletePlayPersonnelAdminInfoByIds(String[] ids);

    /**
     * 删除管理员管理信息
     *
     * @param id
     *            管理员管理主键
     */
    void deletePlayPersonnelAdminInfoById(String id);
}


--- File: play-admin/src/main/java/com/starry/admin/modules/personnel/service/IPlayPersonnelGroupInfoService.java ---
package com.starry.admin.modules.personnel.service;

import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.service.IService;
import com.starry.admin.common.domain.LoginUser;
import com.starry.admin.modules.personnel.module.entity.PlayPersonnelGroupInfoEntity;
import com.starry.admin.modules.personnel.module.vo.PlayPersonnelGroupInfoQueryVo;
import com.starry.admin.modules.personnel.module.vo.PlayPersonnelGroupInfoReturnVo;
import java.util.List;

/**
 * 店员分组信息Service接口
 *
 * @author admin
 * @since 2024-05-31
 */
public interface IPlayPersonnelGroupInfoService extends IService<PlayPersonnelGroupInfoEntity> {
    /**
     * 查询店员分组信息
     *
     * @param id
     *            店员分组信息主键
     * @return 店员分组信息
     */
    PlayPersonnelGroupInfoEntity selectPlayClerkGroupInfoById(String id);

    /**
     * 查询店员分组信息
     *
     * @param userId
     *            用户ID
     * @return 店员分组信息
     */
    PlayPersonnelGroupInfoEntity selectByUserId(String userId);

    /**
     * 查询店员分组信息列表
     *
     * @return PlayPersonnelGroupInfoReturnVo
     * @author admin
     * @since 2024/6/14 15:46
     **/
    List<PlayPersonnelGroupInfoEntity> selectAll();

    /**
     * 分页查询店员分组信息列表
     *
     * @param vo
     *            查询店员分组信息查询对象
     * @return PlayPersonnelGroupInfoReturnVo
     * @author admin
     * @since 2024/6/14 15:46
     **/
    IPage<PlayPersonnelGroupInfoReturnVo> selectByPage(PlayPersonnelGroupInfoQueryVo vo);

    /**
     * 新增店员分组信息
     *
     * @param playClerkGroupInfo
     *            店员分组信息
     * @return 结果
     */
    boolean create(PlayPersonnelGroupInfoEntity playClerkGroupInfo);

    /**
     * 修改店员分组信息
     *
     * @param playClerkGroupInfo
     *            店员分组信息
     * @return 结果
     */
    boolean update(PlayPersonnelGroupInfoEntity playClerkGroupInfo);

    /**
     * 批量删除店员分组信息
     *
     * @param ids
     *            需要删除的店员分组信息主键集合
     * @return 结果
     */
    int deletePlayClerkGroupInfoByIds(String[] ids);

    /**
     * 删除店员分组信息信息
     *
     * @param id
     *            店员分组信息主键
     */
    void deletePlayClerkGroupInfoById(String id);

    /**
     * 筛选所有的组员
     *
     * @param loginUser
     *            登录用户
     * @param clerkNickName
     * @return 组员ID列表
     */
    List<String> getValidClerkIdList(LoginUser loginUser, String clerkNickName);
}


--- File: play-admin/src/main/java/com/starry/admin/modules/personnel/service/IPlayPersonnelWaiterInfoService.java ---
package com.starry.admin.modules.personnel.service;

import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.service.IService;
import com.starry.admin.modules.personnel.module.entity.PlayPersonnelWaiterInfoEntity;
import com.starry.admin.modules.personnel.module.vo.PlayPersonnelWaiterInfoQueryVo;
import com.starry.admin.modules.personnel.module.vo.PlayPersonnelWaiterInfoReturnVo;

/**
 * 客服信息Service接口
 *
 * @author admin
 * @since 2024-06-14
 */
public interface IPlayPersonnelWaiterInfoService extends IService<PlayPersonnelWaiterInfoEntity> {

    /**
     * 查询店员客服信息
     *
     * @param userId
     *            用户ID
     * @return 店员分组信息
     */
    PlayPersonnelWaiterInfoEntity selectByUserId(String userId);

    /**
     * 查询客服信息
     *
     * @param id
     *            客服信息主键
     * @return 客服信息
     */
    PlayPersonnelWaiterInfoEntity selectPlayClerkWaiterInfoById(String id);

    /**
     * 查询客服信息列表
     *
     * @param vo
     *            客服信息
     * @return 客服信息集合
     */
    IPage<PlayPersonnelWaiterInfoReturnVo> selectByPage(PlayPersonnelWaiterInfoQueryVo vo);

    /**
     * 新增客服信息
     *
     * @param playClerkWaiterInfo
     *            客服信息
     * @return 结果
     */
    boolean create(PlayPersonnelWaiterInfoEntity playClerkWaiterInfo);

    /**
     * 修改客服信息
     *
     * @param playClerkWaiterInfo
     *            客服信息
     * @return 结果
     */
    boolean update(PlayPersonnelWaiterInfoEntity playClerkWaiterInfo);

    /**
     * 批量删除客服信息
     *
     * @param ids
     *            需要删除的客服信息主键集合
     * @return 结果
     */
    int deletePlayClerkWaiterInfoByIds(String[] ids);

    /**
     * 删除客服信息信息
     *
     * @param id
     *            客服信息主键
     */
    void deletePlayClerkWaiterInfoById(String id);
}


--- File: play-admin/src/main/java/com/starry/admin/modules/personnel/service/impl/PlayBalanceDetailsInfoServiceImpl.java ---
package com.starry.admin.modules.personnel.service.impl;

import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.github.yulichang.wrapper.MPJLambdaWrapper;
import com.starry.admin.modules.custom.module.entity.PlayCustomUserInfoEntity;
import com.starry.admin.modules.order.module.entity.PlayOrderInfoEntity;
import com.starry.admin.modules.order.service.IPlayOrderInfoService;
import com.starry.admin.modules.personnel.mapper.PlayBalanceDetailsInfoMapper;
import com.starry.admin.modules.personnel.module.entity.PlayBalanceDetailsInfoEntity;
import com.starry.admin.modules.personnel.module.vo.PlayBalanceDetailsQueryVo;
import com.starry.admin.modules.personnel.module.vo.PlayBalanceDetailsReturnVo;
import com.starry.admin.modules.personnel.service.IPlayBalanceDetailsInfoService;
import com.starry.common.utils.IdUtils;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.Arrays;
import javax.annotation.Resource;
import org.springframework.stereotype.Service;

/**
 * 余额明细Service业务层处理
 *
 * @author admin
 * @since 2024-04-30
 */
@Service
public class PlayBalanceDetailsInfoServiceImpl
        extends
            ServiceImpl<PlayBalanceDetailsInfoMapper, PlayBalanceDetailsInfoEntity>
        implements
            IPlayBalanceDetailsInfoService {
    @Resource
    private PlayBalanceDetailsInfoMapper playBalanceDetailsInfoMapper;

    @Resource
    private IPlayOrderInfoService playOrderInfoService;

    /**
     * 查询余额明细
     *
     * @param id
     *            余额明细主键
     * @return 余额明细
     */
    @Override
    public PlayBalanceDetailsInfoEntity selectPlayBalanceDetailsInfoById(String id) {
        return this.baseMapper.selectById(id);
    }

    @Override
    public IPage<PlayBalanceDetailsReturnVo> selectByPage(PlayBalanceDetailsQueryVo vo) {
        MPJLambdaWrapper<PlayBalanceDetailsInfoEntity> lambdaQueryWrapper = new MPJLambdaWrapper<PlayBalanceDetailsInfoEntity>()
                // 查询主表全部字段
                .selectAll(PlayBalanceDetailsInfoEntity.class)
                // 查询顾客表
                .selectAs(PlayCustomUserInfoEntity::getId, "customId")
                .selectAs(PlayCustomUserInfoEntity::getNickname, "customNickname")
                .selectAs(PlayCustomUserInfoEntity::getAvatar, "customAvatar")
                // 查询订单表
                // .selectAs(PlayOrderInfoEntity::getId,
                // "orderId").selectAs(PlayOrderInfoEntity::getOrderNo,
                // "orderNo").selectAs(PlayOrderInfoEntity::getPayMethod,"payMethod")
                .selectAll(PlayOrderInfoEntity.class)
                .leftJoin(PlayOrderInfoEntity.class, PlayOrderInfoEntity::getId,
                        PlayBalanceDetailsInfoEntity::getOrderId)
                .leftJoin(PlayCustomUserInfoEntity.class, PlayCustomUserInfoEntity::getId,
                        PlayBalanceDetailsInfoEntity::getUserId);

        if (StrUtil.isNotBlank(vo.getPayMethod())) {
            lambdaQueryWrapper.eq(PlayOrderInfoEntity::getPayMethod, vo.getPayMethod());
        }
        if (StrUtil.isNotBlank(vo.getOperationType())) {
            lambdaQueryWrapper.eq(PlayBalanceDetailsInfoEntity::getOperationType, vo.getOperationType());
        }
        if (StrUtil.isNotBlank(vo.getUserType())) {
            lambdaQueryWrapper.eq(PlayBalanceDetailsInfoEntity::getUserType, vo.getUserType());
        }
        if (StrUtil.isNotBlank(vo.getNickname())) {
            lambdaQueryWrapper.like(PlayCustomUserInfoEntity::getNickname, vo.getNickname());
        }
        if (StrUtil.isNotBlank(vo.getUserId())) {
            lambdaQueryWrapper.like(PlayBalanceDetailsInfoEntity::getUserId, vo.getUserId());
        }

        if (!vo.getOperationTime().isEmpty()) {
            lambdaQueryWrapper.between(PlayBalanceDetailsInfoEntity::getOperationTime, vo.getOperationTime().get(0),
                    vo.getOperationTime().get(1));
        }
        lambdaQueryWrapper.orderByDesc(PlayBalanceDetailsInfoEntity::getOperationTime);
        return this.baseMapper.selectJoinPage(new Page<>(vo.getPageNum(), vo.getPageSize()),
                PlayBalanceDetailsReturnVo.class, lambdaQueryWrapper);

    }

    /**
     * 查询余额明细列表
     *
     * @param playBalanceDetailsInfo
     *            余额明细
     * @return 余额明细
     */
    @Override
    public IPage<PlayBalanceDetailsInfoEntity> selectPlayBalanceDetailsInfoByPage(
            PlayBalanceDetailsInfoEntity playBalanceDetailsInfo) {
        Page<PlayBalanceDetailsInfoEntity> page = new Page<>(1, 10);
        return this.baseMapper.selectPage(page, new LambdaQueryWrapper<>());
    }

    @Override
    public void insertBalanceDetailsInfo(String userType, String userId, BigDecimal balanceBeforeOperation,
            BigDecimal balanceAfterOperation, String operationType, String operationAction, BigDecimal balanceMoney,
            BigDecimal giftAmount, String orderId) {
        PlayOrderInfoEntity orderInfo = playOrderInfoService.selectOrderInfoById(orderId);
        PlayBalanceDetailsInfoEntity entity = new PlayBalanceDetailsInfoEntity();
        entity.setId(IdUtils.getUuid());
        entity.setUserType(userType);
        entity.setUserId(userId);
        entity.setBalanceBeforeOperation(balanceBeforeOperation);
        entity.setBalanceAfterOperation(balanceAfterOperation);
        entity.setOperationType(operationType);
        entity.setBalanceMoney(balanceMoney);
        entity.setGiftAmount(giftAmount);
        entity.setPaymentAmount(orderInfo == null ? balanceMoney : orderInfo.getFinalAmount());
        entity.setOperationTime(LocalDateTime.now());
        entity.setOrderId(orderId);
        this.baseMapper.insert(entity);
    }

    /**
     * 新增余额明细
     *
     * @param playBalanceDetailsInfo
     *            余额明细
     * @return 结果
     */
    @Override
    public boolean create(PlayBalanceDetailsInfoEntity playBalanceDetailsInfo) {
        if (StrUtil.isBlankIfStr(playBalanceDetailsInfo.getId())) {
            playBalanceDetailsInfo.setId(IdUtils.getUuid());
        }
        return save(playBalanceDetailsInfo);
    }

    /**
     * 修改余额明细
     *
     * @param playBalanceDetailsInfo
     *            余额明细
     * @return 结果
     */
    @Override
    public boolean update(PlayBalanceDetailsInfoEntity playBalanceDetailsInfo) {
        return updateById(playBalanceDetailsInfo);
    }

    /**
     * 批量删除余额明细
     *
     * @param ids
     *            需要删除的余额明细主键
     * @return 结果
     */
    @Override
    public int deletePlayBalanceDetailsInfoByIds(String[] ids) {
        return playBalanceDetailsInfoMapper.deleteBatchIds(Arrays.asList(ids));
    }

    /**
     * 删除余额明细信息
     *
     * @param id
     *            余额明细主键
     * @return 结果
     */
    @Override
    public int deletePlayBalanceDetailsInfoById(String id) {
        return playBalanceDetailsInfoMapper.deleteById(id);
    }
}


--- File: play-admin/src/main/java/com/starry/admin/modules/personnel/service/impl/PlayPersonnelAdminInfoServiceImpl.java ---
package com.starry.admin.modules.personnel.service.impl;

import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.github.yulichang.wrapper.MPJLambdaWrapper;
import com.starry.admin.modules.personnel.mapper.PlayPersonnelAdminInfoMapper;
import com.starry.admin.modules.personnel.module.entity.PlayPersonnelAdminInfoEntity;
import com.starry.admin.modules.personnel.module.vo.PlayPersonnelAdminInfoQueryVo;
import com.starry.admin.modules.personnel.module.vo.PlayPersonnelAdminInfoReturnVo;
import com.starry.admin.modules.personnel.service.IPlayPersonnelAdminInfoService;
import com.starry.common.utils.IdUtils;
import java.util.Arrays;
import javax.annotation.Resource;
import org.springframework.stereotype.Service;

/**
 * 管理员管理Service业务层处理
 *
 * @author admin
 * @since 2024-06-14
 */
@Service
public class PlayPersonnelAdminInfoServiceImpl
        extends
            ServiceImpl<PlayPersonnelAdminInfoMapper, PlayPersonnelAdminInfoEntity>
        implements
            IPlayPersonnelAdminInfoService {
    @Resource
    private PlayPersonnelAdminInfoMapper playPersonnelAdminInfoMapper;

    @Override
    public PlayPersonnelAdminInfoEntity selectByUserId(String userId) {
        LambdaQueryWrapper<PlayPersonnelAdminInfoEntity> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(PlayPersonnelAdminInfoEntity::getSysUserId, userId);
        return this.baseMapper.selectOne(queryWrapper);
    }

    /**
     * 查询管理员管理
     *
     * @param id
     *            管理员管理主键
     * @return 管理员管理
     */
    @Override
    public PlayPersonnelAdminInfoEntity selectPlayPersonnelAdminInfoById(String id) {
        return this.baseMapper.selectById(id);
    }

    @Override
    public IPage<PlayPersonnelAdminInfoReturnVo> selectByPage(PlayPersonnelAdminInfoQueryVo vo) {
        MPJLambdaWrapper<PlayPersonnelAdminInfoEntity> lambdaWrapper = new MPJLambdaWrapper<>();
        if (StrUtil.isNotBlank(vo.getAdminName())) {
            lambdaWrapper.eq(PlayPersonnelAdminInfoEntity::getAdminName, vo.getAdminName());
        }
        return this.baseMapper.selectJoinPage(new Page<>(vo.getPageNum(), vo.getPageSize()),
                PlayPersonnelAdminInfoReturnVo.class, lambdaWrapper);
    }

    /**
     * 新增管理员管理
     *
     * @param playPersonnelAdminInfo
     *            管理员管理
     * @return 结果
     */
    @Override
    public boolean create(PlayPersonnelAdminInfoEntity playPersonnelAdminInfo) {
        if (StrUtil.isBlankIfStr(playPersonnelAdminInfo.getId())) {
            playPersonnelAdminInfo.setId(IdUtils.getUuid());
        }
        return save(playPersonnelAdminInfo);
    }

    /**
     * 修改管理员管理
     *
     * @param playPersonnelAdminInfo
     *            管理员管理
     * @return 结果
     */
    @Override
    public boolean update(PlayPersonnelAdminInfoEntity playPersonnelAdminInfo) {
        return updateById(playPersonnelAdminInfo);
    }

    /**
     * 批量删除管理员管理
     *
     * @param ids
     *            需要删除的管理员管理主键
     * @return 结果
     */
    @Override
    public int deletePlayPersonnelAdminInfoByIds(String[] ids) {
        return playPersonnelAdminInfoMapper.deleteBatchIds(Arrays.asList(ids));
    }

    /**
     * 删除管理员管理信息
     *
     * @param id
     *            管理员管理主键
     */
    @Override
    public void deletePlayPersonnelAdminInfoById(String id) {
        playPersonnelAdminInfoMapper.deleteById(id);
    }
}


--- File: play-admin/src/main/java/com/starry/admin/modules/personnel/service/impl/PlayPersonnelGroupInfoServiceImpl.java ---
package com.starry.admin.modules.personnel.service.impl;

import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.github.yulichang.wrapper.MPJLambdaWrapper;
import com.starry.admin.common.domain.LoginUser;
import com.starry.admin.modules.clerk.module.entity.PlayClerkUserInfoEntity;
import com.starry.admin.modules.clerk.service.IPlayClerkUserInfoService;
import com.starry.admin.modules.personnel.mapper.PlayPersonnelGroupInfoMapper;
import com.starry.admin.modules.personnel.module.entity.PlayPersonnelGroupInfoEntity;
import com.starry.admin.modules.personnel.module.vo.PlayPersonnelGroupInfoQueryVo;
import com.starry.admin.modules.personnel.module.vo.PlayPersonnelGroupInfoReturnVo;
import com.starry.admin.modules.personnel.service.IPlayPersonnelGroupInfoService;
import com.starry.common.utils.IdUtils;
import com.starry.common.utils.StringUtils;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;
import javax.annotation.Resource;
import org.springframework.stereotype.Service;

/**
 * 店员分组信息Service业务层处理
 *
 * @author admin
 * @since 2024-05-31
 */
@Service
public class PlayPersonnelGroupInfoServiceImpl
        extends
            ServiceImpl<PlayPersonnelGroupInfoMapper, PlayPersonnelGroupInfoEntity>
        implements
            IPlayPersonnelGroupInfoService {
    @Resource
    private PlayPersonnelGroupInfoMapper playClerkGroupInfoMapper;
    @Resource
    private IPlayClerkUserInfoService clerkUserInfoService;

    /**
     * 查询店员分组信息
     *
     * @param id
     *            店员分组信息主键
     * @return 店员分组信息
     */
    @Override
    public PlayPersonnelGroupInfoEntity selectPlayClerkGroupInfoById(String id) {
        return this.baseMapper.selectById(id);
    }

    @Override
    public PlayPersonnelGroupInfoEntity selectByUserId(String userId) {
        LambdaQueryWrapper<PlayPersonnelGroupInfoEntity> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(PlayPersonnelGroupInfoEntity::getSysUserId, userId);
        return this.baseMapper.selectOne(queryWrapper);
    }

    @Override
    public List<PlayPersonnelGroupInfoEntity> selectAll() {
        return this.baseMapper.selectList(new LambdaQueryWrapper<>());
    }

    @Override
    public IPage<PlayPersonnelGroupInfoReturnVo> selectByPage(PlayPersonnelGroupInfoQueryVo vo) {
        MPJLambdaWrapper<PlayPersonnelGroupInfoEntity> lambdaQueryWrapper = new MPJLambdaWrapper<>();
        if (StrUtil.isNotBlank(vo.getLeaderName())) {
            lambdaQueryWrapper.eq(PlayPersonnelGroupInfoEntity::getLeaderName, vo.getLeaderName());
        }
        return this.baseMapper.selectJoinPage(new Page<>(vo.getPageNum(), vo.getPageSize()),
                PlayPersonnelGroupInfoReturnVo.class, lambdaQueryWrapper);
    }

    /**
     * 新增店员分组信息
     *
     * @param playClerkGroupInfo
     *            店员分组信息
     * @return 结果
     */
    @Override
    public boolean create(PlayPersonnelGroupInfoEntity playClerkGroupInfo) {
        if (StrUtil.isBlankIfStr(playClerkGroupInfo.getId())) {
            playClerkGroupInfo.setId(IdUtils.getUuid());
        }
        return save(playClerkGroupInfo);
    }

    /**
     * 修改店员分组信息
     *
     * @param playClerkGroupInfo
     *            店员分组信息
     * @return 结果
     */
    @Override
    public boolean update(PlayPersonnelGroupInfoEntity playClerkGroupInfo) {
        return updateById(playClerkGroupInfo);
    }

    /**
     * 批量删除店员分组信息
     *
     * @param ids
     *            需要删除的店员分组信息主键
     * @return 结果
     */
    @Override
    public int deletePlayClerkGroupInfoByIds(String[] ids) {
        return playClerkGroupInfoMapper.deleteBatchIds(Arrays.asList(ids));
    }

    /**
     * 删除店员分组信息信息
     *
     * @param id
     *            店员分组信息主键
     */
    @Override
    public void deletePlayClerkGroupInfoById(String id) {
        playClerkGroupInfoMapper.deleteById(id);
    }

    @Override
    public List<String> getValidClerkIdList(LoginUser loginUser, String clerkNickName) {
        List<String> idList;
        PlayPersonnelGroupInfoEntity groupInfoEntity = this.selectByUserId(loginUser.getUserId());
        if (Objects.nonNull(groupInfoEntity)) {
            List<PlayClerkUserInfoEntity> list = clerkUserInfoService
                    .list(Wrappers.lambdaQuery(PlayClerkUserInfoEntity.class)
                            .eq(PlayClerkUserInfoEntity::getGroupId, groupInfoEntity.getId())
                            .select(PlayClerkUserInfoEntity::getId));
            idList = list.stream().map(PlayClerkUserInfoEntity::getId).collect(Collectors.toList());
            PlayClerkUserInfoEntity entity = clerkUserInfoService
                    .getOne(Wrappers.lambdaQuery(PlayClerkUserInfoEntity.class)
                            .eq(PlayClerkUserInfoEntity::getSysUserId, loginUser.getUserId()), false);
            if (Objects.nonNull(entity)) {
                idList.add(entity.getId());
            }
        } else {
            // 返回所有的clerkId
            idList = clerkUserInfoService
                    .list(Wrappers.lambdaQuery(PlayClerkUserInfoEntity.class).select(PlayClerkUserInfoEntity::getId)
                            .eq(PlayClerkUserInfoEntity::getClerkState, "1"))
                    .stream().map(PlayClerkUserInfoEntity::getId).collect(Collectors.toList());
        }

        if (CollectionUtil.isEmpty(idList)) {
            idList = Collections.singletonList("-999");
        }
        // 加入nickName的筛选
        if (StringUtils.isNotEmpty(clerkNickName)) {
            LambdaQueryWrapper<PlayClerkUserInfoEntity> wrapper = Wrappers.lambdaQuery(PlayClerkUserInfoEntity.class)
                    .in(PlayClerkUserInfoEntity::getId, idList)
                    .like(PlayClerkUserInfoEntity::getNickname, clerkNickName).select(PlayClerkUserInfoEntity::getId);
            idList = clerkUserInfoService.list(wrapper).stream().map(PlayClerkUserInfoEntity::getId)
                    .collect(Collectors.toList());
        }
        if (CollectionUtil.isEmpty(idList)) {
            idList = Collections.singletonList("-999");
        }
        return idList;
    }
}


--- File: play-admin/src/main/java/com/starry/admin/modules/personnel/service/impl/PlayPersonnelWaiterInfoServiceImpl.java ---
package com.starry.admin.modules.personnel.service.impl;

import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.github.yulichang.wrapper.MPJLambdaWrapper;
import com.starry.admin.modules.personnel.mapper.PlayPersonnelWaiterInfoMapper;
import com.starry.admin.modules.personnel.module.entity.PlayPersonnelWaiterInfoEntity;
import com.starry.admin.modules.personnel.module.vo.PlayPersonnelWaiterInfoQueryVo;
import com.starry.admin.modules.personnel.module.vo.PlayPersonnelWaiterInfoReturnVo;
import com.starry.admin.modules.personnel.service.IPlayPersonnelWaiterInfoService;
import com.starry.common.utils.IdUtils;
import java.util.Arrays;
import javax.annotation.Resource;
import org.springframework.stereotype.Service;

/**
 * 客服信息Service业务层处理
 *
 * @author admin
 * @since 2024-06-14
 */
@Service
public class PlayPersonnelWaiterInfoServiceImpl
        extends
            ServiceImpl<PlayPersonnelWaiterInfoMapper, PlayPersonnelWaiterInfoEntity>
        implements
            IPlayPersonnelWaiterInfoService {
    @Resource
    private PlayPersonnelWaiterInfoMapper playClerkWaiterInfoMapper;

    @Override
    public PlayPersonnelWaiterInfoEntity selectByUserId(String userId) {
        LambdaQueryWrapper<PlayPersonnelWaiterInfoEntity> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(PlayPersonnelWaiterInfoEntity::getSysUserId, userId);
        return this.baseMapper.selectOne(queryWrapper);
    }

    /**
     * 查询客服信息
     *
     * @param id
     *            客服信息主键
     * @return 客服信息
     */
    @Override
    public PlayPersonnelWaiterInfoEntity selectPlayClerkWaiterInfoById(String id) {
        return this.baseMapper.selectById(id);
    }

    @Override
    public IPage<PlayPersonnelWaiterInfoReturnVo> selectByPage(PlayPersonnelWaiterInfoQueryVo vo) {
        MPJLambdaWrapper<PlayPersonnelWaiterInfoEntity> lambdaWrapper = new MPJLambdaWrapper<>();
        if (StrUtil.isNotBlank(vo.getWaiterName())) {
            lambdaWrapper.eq(PlayPersonnelWaiterInfoEntity::getWaiterName, vo.getWaiterName());
        }
        return this.baseMapper.selectJoinPage(new Page<>(vo.getPageNum(), vo.getPageSize()),
                PlayPersonnelWaiterInfoReturnVo.class, lambdaWrapper);
    }

    /**
     * 新增客服信息
     *
     * @param playClerkWaiterInfo
     *            客服信息
     * @return 结果
     */
    @Override
    public boolean create(PlayPersonnelWaiterInfoEntity playClerkWaiterInfo) {
        if (StrUtil.isBlankIfStr(playClerkWaiterInfo.getId())) {
            playClerkWaiterInfo.setId(IdUtils.getUuid());
        }
        return save(playClerkWaiterInfo);
    }

    /**
     * 修改客服信息
     *
     * @param playClerkWaiterInfo
     *            客服信息
     * @return 结果
     */
    @Override
    public boolean update(PlayPersonnelWaiterInfoEntity playClerkWaiterInfo) {
        return updateById(playClerkWaiterInfo);
    }

    /**
     * 批量删除客服信息
     *
     * @param ids
     *            需要删除的客服信息主键
     * @return 结果
     */
    @Override
    public int deletePlayClerkWaiterInfoByIds(String[] ids) {
        return playClerkWaiterInfoMapper.deleteBatchIds(Arrays.asList(ids));
    }

    /**
     * 删除客服信息信息
     *
     * @param id
     *            客服信息主键
     */
    @Override
    public void deletePlayClerkWaiterInfoById(String id) {
        playClerkWaiterInfoMapper.deleteById(id);
    }
}


--- File: play-admin/src/main/java/com/starry/admin/modules/shop/controller/PlayClerkGiftInfoController.java ---
package com.starry.admin.modules.shop.controller;

import com.baomidou.mybatisplus.core.metadata.IPage;
import com.starry.admin.modules.shop.module.entity.PlayClerkGiftInfoEntity;
import com.starry.admin.modules.shop.service.IPlayClerkGiftInfoService;
import com.starry.common.annotation.Log;
import com.starry.common.enums.BusinessType;
import com.starry.common.result.R;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiParam;
import io.swagger.annotations.ApiResponse;
import io.swagger.annotations.ApiResponses;
import javax.annotation.Resource;
import org.springframework.web.bind.annotation.*;

/**
 * 店员和礼物关系Controller
 *
 * @author admin
 * @since 2024-05-25
 */
@Api(tags = "店员礼物关联管理", description = "店员和礼物关系管理相关接口，包括查询、新增、修改和删除等操作")
@RestController
@RequestMapping("/play/clerkGift")
public class PlayClerkGiftInfoController {
    @Resource
    private IPlayClerkGiftInfoService playClerkGiftInfoService;

    /**
     * 查询店员和礼物关系列表
     */
    @ApiOperation(value = "查询店员礼物关联列表", notes = "分页查询店员和礼物关系列表")
    @ApiResponses({
            @ApiResponse(code = 200, message = "操作成功", response = PlayClerkGiftInfoEntity.class, responseContainer = "Page")})
    @GetMapping("/list")
    public R list(@ApiParam(value = "查询条件") PlayClerkGiftInfoEntity playClerkGiftInfo) {
        IPage<PlayClerkGiftInfoEntity> list = playClerkGiftInfoService.selectPlayClerkGiftInfoByPage(playClerkGiftInfo);
        return R.ok(list);
    }

    /**
     * 获取店员和礼物关系详细信息
     */
    @ApiOperation(value = "获取关联详情", notes = "根据ID获取店员和礼物关系详细信息")
    @ApiImplicitParam(name = "id", value = "关联ID", required = true, paramType = "path", dataType = "String", example = "1")
    @ApiResponses({@ApiResponse(code = 200, message = "操作成功", response = PlayClerkGiftInfoEntity.class)})
    @GetMapping(value = "/{id}")
    public R getInfo(@PathVariable("id") String id) {
        return R.ok(playClerkGiftInfoService.selectPlayClerkGiftInfoById(id));
    }

    /**
     * 新增店员和礼物关系
     */
    @ApiOperation(value = "新增关联信息", notes = "新增店员和礼物关系信息")
    @ApiResponses({@ApiResponse(code = 200, message = "操作成功"), @ApiResponse(code = 500, message = "添加失败，包含详细错误信息")})
    @Log(title = "店员和礼物关系", businessType = BusinessType.INSERT)
    @PostMapping("/create")
    public R create(@ApiParam(value = "关联信息", required = true) @RequestBody PlayClerkGiftInfoEntity playClerkGiftInfo) {
        boolean success = playClerkGiftInfoService.create(playClerkGiftInfo);
        if (success) {
            return R.ok();
        }
        return R.error("添加失败");
    }

    /**
     * 修改店员和礼物关系
     */
    @ApiOperation(value = "修改关联信息", notes = "修改店员和礼物关系信息")
    @ApiImplicitParam(name = "id", value = "关联ID", required = true, paramType = "path", dataType = "String", example = "1")
    @ApiResponses({@ApiResponse(code = 200, message = "操作成功"), @ApiResponse(code = 500, message = "修改失败，包含详细错误信息")})
    @Log(title = "店员和礼物关系", businessType = BusinessType.UPDATE)
    @PostMapping(value = "/update/{id}")
    public R update(@PathVariable String id,
            @ApiParam(value = "关联信息", required = true) @RequestBody PlayClerkGiftInfoEntity playClerkGiftInfo) {
        playClerkGiftInfo.setId(id);
        boolean success = playClerkGiftInfoService.update(playClerkGiftInfo);
        if (success) {
            return R.ok();
        }
        return R.error("修改失败");
    }

    /**
     * 删除店员和礼物关系
     */
    @ApiOperation(value = "删除关联信息", notes = "根据ID批量删除店员和礼物关系信息")
    @ApiImplicitParam(name = "ids", value = "关联ID数组", required = true, paramType = "path", dataType = "String[]", example = "1,2,3")
    @ApiResponses({@ApiResponse(code = 200, message = "操作成功")})
    @Log(title = "店员和礼物关系", businessType = BusinessType.DELETE)
    @DeleteMapping("/{ids}")
    public R remove(@PathVariable String[] ids) {
        return R.ok(playClerkGiftInfoService.deletePlayClerkGiftInfoByIds(ids));
    }
}


--- File: play-admin/src/main/java/com/starry/admin/modules/shop/controller/PlayCommodityInfoController.java ---
package com.starry.admin.modules.shop.controller;

import cn.hutool.core.bean.BeanUtil;
import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONObject;
import com.starry.admin.common.exception.CustomException;
import com.starry.admin.modules.clerk.module.entity.PlayClerkLevelInfoEntity;
import com.starry.admin.modules.clerk.service.IPlayClerkLevelInfoService;
import com.starry.admin.modules.shop.module.entity.PlayCommodityAndLevelInfoEntity;
import com.starry.admin.modules.shop.module.entity.PlayCommodityInfoEntity;
import com.starry.admin.modules.shop.module.vo.PlayCommodityInfoAddVo;
import com.starry.admin.modules.shop.module.vo.PlayCommodityInfoReturnVo;
import com.starry.admin.modules.shop.service.IPlayCommodityAndLevelInfoService;
import com.starry.admin.modules.shop.service.IPlayCommodityInfoService;
import com.starry.common.annotation.Log;
import com.starry.common.enums.BusinessType;
import com.starry.common.result.R;
import com.starry.common.utils.ConvertUtil;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiParam;
import io.swagger.annotations.ApiResponse;
import io.swagger.annotations.ApiResponses;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.annotation.Resource;
import org.springframework.web.bind.annotation.*;

/**
 * 服务项目Controller
 *
 * @author admin
 * @since 2024-03-31
 */
@Api(tags = "服务项目管理", description = "服务项目信息管理相关接口，包括查询、新增、修改和删除等操作")
@RestController
@RequestMapping("/shop/commodity")
public class PlayCommodityInfoController {
    @Resource
    private IPlayCommodityInfoService playCommodityInfoService;

    @Resource
    private IPlayClerkLevelInfoService playClerkLevelInfoService;

    @Resource
    private IPlayCommodityAndLevelInfoService playCommodityAndLevelInfoService;

    @ApiOperation(value = "获取表头信息", notes = "获取店员等级表头信息")
    @ApiResponses({@ApiResponse(code = 200, message = "操作成功")})
    @GetMapping("/getTableName")
    public R getTableName() {
        List<Map<String, String>> result = new ArrayList<>();
        List<PlayClerkLevelInfoEntity> levelInfoEntities = playClerkLevelInfoService.selectAll();
        for (PlayClerkLevelInfoEntity levelInfoEntity : levelInfoEntities) {
            HashMap<String, String> item = new HashMap<>();
            item.put("prop", levelInfoEntity.getId());
            item.put("name", levelInfoEntity.getName());
            result.add(item);
        }
        return R.ok(result);
    }

    /**
     * 查询服务项目类型
     */
    @ApiOperation(value = "查询所有服务项目", notes = "查询所有服务项目类型及其子项目")
    @ApiResponses({@ApiResponse(code = 200, message = "操作成功")})
    @GetMapping("/listAll")
    public R queryCommodityType() {
        List<Map<String, Object>> result = new ArrayList<>();
        List<PlayCommodityInfoEntity> list = playCommodityInfoService.selectByType();
        for (PlayCommodityInfoEntity entity : list) {
            List<Map<String, Object>> itemCommodityInfo = new ArrayList<>();
            Map<String, Object> map = BeanUtil.beanToMap(entity);
            List<PlayCommodityInfoReturnVo> commodityInfoEntities = ConvertUtil.entityToVoList(
                    playCommodityInfoService.selectByPId(entity.getId()), PlayCommodityInfoReturnVo.class);
            for (PlayCommodityInfoReturnVo commodityInfoEntity : commodityInfoEntities) {
                Map<String, Object> item = BeanUtil.beanToMap(commodityInfoEntity);
                for (PlayClerkLevelInfoEntity playClerkLevelInfoEntity : playClerkLevelInfoService.selectAll()) {
                    PlayCommodityAndLevelInfoEntity entity1 = playCommodityAndLevelInfoService
                            .queryById(commodityInfoEntity.getId(), playClerkLevelInfoEntity.getId());
                    item.put(playClerkLevelInfoEntity.getId(), entity1 == null ? "未设置价格" : entity1.getPrice());
                }
                itemCommodityInfo.add(item);
            }
            map.put("children", itemCommodityInfo);
            result.add(map);
        }
        return R.ok(result);
    }

    /**
     * 修改服务项目类型
     */
    @ApiOperation(value = "更新服务项目价格", notes = "根据不同店员等级修改服务项目价格")
    @ApiResponses({@ApiResponse(code = 200, message = "操作成功"), @ApiResponse(code = 500, message = "更新失败，包含详细错误信息")})
    @PostMapping("/updateInfo")
    public R updateInfo(@ApiParam(value = "价格信息JSON", required = true) @RequestBody String s) {
        JSONObject jsonObject = JSON.parseObject(s);
        if (!jsonObject.containsKey("id")) {
            throw new CustomException("请求参数错误,id不能为空");
        }
        System.out.println(s);
        for (PlayClerkLevelInfoEntity playClerkLevelInfoEntity : playClerkLevelInfoService.selectAll()) {
            if (!jsonObject.containsKey(playClerkLevelInfoEntity.getId())) {
                throw new CustomException("请求参数错误");
            }
            double price = 0.0;
            try {
                price = Double.parseDouble(jsonObject.getString(playClerkLevelInfoEntity.getId()));
            } catch (RuntimeException e) {
                throw new CustomException("请求参数错误,价格格式为空");
            }
            PlayCommodityAndLevelInfoEntity entity = playCommodityAndLevelInfoService
                    .queryById(jsonObject.getString("id"), playClerkLevelInfoEntity.getId());
            if (entity == null) {
                entity = new PlayCommodityAndLevelInfoEntity();
                entity.setCommodityId(jsonObject.getString("id"));
                entity.setLevelId(playClerkLevelInfoEntity.getId());
            }
            entity.setPrice(new BigDecimal(price));
            this.playCommodityAndLevelInfoService.saveOrUpdate(entity);
        }
        return R.ok("成功");
    }

    /**
     * 获取服务项目详细信息
     */
    @ApiOperation(value = "获取服务项目详情", notes = "根据ID获取服务项目详细信息")
    @ApiImplicitParam(name = "id", value = "服务项目ID", required = true, paramType = "path", dataType = "String", example = "1")
    @ApiResponses({@ApiResponse(code = 200, message = "操作成功", response = PlayCommodityInfoEntity.class)})
    @GetMapping(value = "/{id}")
    public R getInfo(@PathVariable("id") String id) {
        return R.ok(playCommodityInfoService.selectPlayCommodityInfoById(id));
    }

    /**
     * 新增服务项目
     */
    @ApiOperation(value = "新增服务项目", notes = "新增服务项目信息")
    @ApiResponses({@ApiResponse(code = 200, message = "操作成功"), @ApiResponse(code = 500, message = "添加失败，包含详细错误信息")})
    @Log(title = "服务项目", businessType = BusinessType.INSERT)
    @PostMapping("/create")
    public R create(@ApiParam(value = "服务项目信息", required = true) @RequestBody PlayCommodityInfoAddVo vo) {
        PlayCommodityInfoEntity entity = ConvertUtil.entityToVo(vo, PlayCommodityInfoEntity.class);
        boolean success = playCommodityInfoService.create(entity);
        if (success) {
            return R.ok();
        }
        return R.error("添加失败");
    }

    /**
     * 修改服务项目
     */
    @ApiOperation(value = "修改服务项目", notes = "修改服务项目信息")
    @ApiImplicitParam(name = "id", value = "服务项目ID", required = true, paramType = "path", dataType = "String", example = "1")
    @ApiResponses({@ApiResponse(code = 200, message = "操作成功"), @ApiResponse(code = 500, message = "修改失败，包含详细错误信息")})
    @Log(title = "服务项目", businessType = BusinessType.UPDATE)
    @PostMapping(value = "/update/{id}")
    public R update(@PathVariable String id,
            @ApiParam(value = "服务项目信息", required = true) @RequestBody PlayCommodityInfoEntity playCommodityInfo) {
        playCommodityInfo.setId(id);
        boolean success = playCommodityInfoService.update(playCommodityInfo);
        if (success) {
            return R.ok();
        }
        return R.error("修改失败");
    }

    /**
     * 删除服务项目
     */
    @ApiOperation(value = "删除服务项目", notes = "根据ID批量删除服务项目信息")
    @ApiImplicitParam(name = "ids", value = "服务项目ID数组", required = true, paramType = "path", dataType = "String[]", example = "1,2,3")
    @ApiResponses({@ApiResponse(code = 200, message = "操作成功")})
    @Log(title = "服务项目", businessType = BusinessType.DELETE)
    @DeleteMapping("/{ids}")
    public R remove(@PathVariable String[] ids) {
        return R.ok(playCommodityInfoService.deletePlayCommodityInfoByIds(ids));
    }
}


--- File: play-admin/src/main/java/com/starry/admin/modules/shop/controller/PlayCouponDetailsController.java ---
package com.starry.admin.modules.shop.controller;

import com.baomidou.mybatisplus.core.metadata.IPage;
import com.starry.admin.modules.shop.module.entity.PlayCouponDetailsEntity;
import com.starry.admin.modules.shop.module.vo.PlayCouponDetailsQueryVo;
import com.starry.admin.modules.shop.module.vo.PlayCouponDetailsReturnVo;
import com.starry.admin.modules.shop.module.vo.PlayCouponDetailsUpdateUseStateVo;
import com.starry.admin.modules.shop.service.IPlayCouponDetailsService;
import com.starry.common.annotation.Log;
import com.starry.common.enums.BusinessType;
import com.starry.common.result.R;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiParam;
import io.swagger.annotations.ApiResponse;
import io.swagger.annotations.ApiResponses;
import javax.annotation.Resource;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;

/**
 * 优惠券详情Controller
 *
 * @author admin
 * @since 2024-07-04
 */
@Api(tags = "优惠券记录管理", description = "优惠券使用记录管理相关接口，包括查询、新增、修改和删除等操作")
@RestController
@RequestMapping("/shop/couponRecord")
public class PlayCouponDetailsController {

    @Resource
    private IPlayCouponDetailsService playCouponDetailsService;

    /**
     * 查询优惠券详情列表
     */
    @ApiOperation(value = "分页查询优惠券记录", notes = "分页查询优惠券使用记录列表")
    @ApiResponses({
            @ApiResponse(code = 200, message = "操作成功", response = PlayCouponDetailsReturnVo.class, responseContainer = "Page")})
    // @PreAuthorize("@customSs.hasPermission('shop:couponDetails:list')")
    @PostMapping("/listByPage")
    public R listByPage(
            @ApiParam(value = "查询条件", required = true) @Validated @RequestBody PlayCouponDetailsQueryVo vo) {
        IPage<PlayCouponDetailsReturnVo> list = playCouponDetailsService.selectByPage(vo);
        return R.ok(list);
    }

    /**
     * 获取优惠券详情详细信息
     */
    @ApiOperation(value = "获取优惠券记录详情", notes = "根据ID获取优惠券使用记录详细信息")
    @ApiImplicitParam(name = "id", value = "记录ID", required = true, paramType = "path", dataType = "String", example = "1")
    @ApiResponses({@ApiResponse(code = 200, message = "操作成功", response = PlayCouponDetailsEntity.class)})
    // @PreAuthorize("@customSs.hasPermission('shop:couponDetails:query')")
    @GetMapping(value = "/{id}")
    public R getInfo(@PathVariable("id") String id) {
        return R.ok(playCouponDetailsService.selectPlayCouponDetailsById(id));
    }

    /**
     * 新增优惠券详情
     */
    @ApiOperation(value = "新增优惠券记录", notes = "新增优惠券使用记录")
    @ApiResponses({@ApiResponse(code = 200, message = "操作成功"), @ApiResponse(code = 500, message = "添加失败，包含详细错误信息")})
    // @PreAuthorize("@customSs.hasPermission('shop:couponDetails:create')")
    @Log(title = "优惠券详情", businessType = BusinessType.INSERT)
    @PostMapping("/create")
    public R create(
            @ApiParam(value = "优惠券记录信息", required = true) @RequestBody PlayCouponDetailsEntity playCouponDetails) {
        boolean success = playCouponDetailsService.create(playCouponDetails);
        if (success) {
            return R.ok();
        }
        return R.error("添加失败");
    }

    /**
     * 更新优惠券使用状态
     *
     * @param vo
     *            优惠券实体信息
     * @since 2024/7/4 17:15
     **/
    @ApiOperation(value = "更新优惠券使用状态", notes = "批量更新优惠券使用状态")
    @ApiResponses({@ApiResponse(code = 200, message = "操作成功"), @ApiResponse(code = 500, message = "更新失败，包含详细错误信息")})
    @PostMapping("/updateUseState")
    public R update(@ApiParam(value = "更新状态信息", required = true) PlayCouponDetailsUpdateUseStateVo vo) {
        for (String couponId : vo.getCouponIds()) {
            playCouponDetailsService.selectPlayCouponDetailsById(couponId);
            PlayCouponDetailsEntity entity = new PlayCouponDetailsEntity();
            entity.setId(couponId);
            entity.setUseState(vo.getUseState());
            playCouponDetailsService.update(entity);
        }
        return R.ok();
    }

    /**
     * 修改优惠券详情
     */
    @ApiOperation(value = "修改优惠券记录", notes = "修改优惠券使用记录信息")
    @ApiImplicitParam(name = "id", value = "记录ID", required = true, paramType = "path", dataType = "String", example = "1")
    @ApiResponses({@ApiResponse(code = 200, message = "操作成功"), @ApiResponse(code = 500, message = "修改失败，包含详细错误信息")})
    // @PreAuthorize("@customSs.hasPermission('shop:couponDetails:edit')")
    @Log(title = "优惠券详情", businessType = BusinessType.UPDATE)
    @PostMapping(value = "/update/{id}")
    public R update(@PathVariable String id,
            @ApiParam(value = "优惠券记录信息", required = true) @RequestBody PlayCouponDetailsEntity playCouponDetails) {
        playCouponDetails.setId(id);
        boolean success = playCouponDetailsService.update(playCouponDetails);
        if (success) {
            return R.ok();
        }
        return R.error("修改失败");
    }

    /**
     * 删除优惠券详情
     */
    @ApiOperation(value = "删除优惠券记录", notes = "批量删除优惠券使用记录（逻辑删除）")
    @ApiImplicitParam(name = "ids", value = "记录ID数组", required = true, paramType = "path", dataType = "String[]", example = "1,2,3")
    @ApiResponses({@ApiResponse(code = 200, message = "操作成功")})
    // @PreAuthorize("@customSs.hasPermission('shop:couponDetails:remove')")
    @Log(title = "优惠券详情", businessType = BusinessType.DELETE)
    @DeleteMapping("/{ids}")
    public R remove(@PathVariable String[] ids) {
        for (String id : ids) {
            PlayCouponDetailsEntity entity = new PlayCouponDetailsEntity();
            entity.setId(id);
            entity.setUseState("3");
            playCouponDetailsService.update(entity);
        }
        return R.ok();
    }
}


--- File: play-admin/src/main/java/com/starry/admin/modules/shop/controller/PlayCouponInfoController.java ---
package com.starry.admin.modules.shop.controller;

import com.baomidou.mybatisplus.core.metadata.IPage;
import com.starry.admin.common.exception.CustomException;
import com.starry.admin.modules.custom.module.entity.PlayCustomUserInfoEntity;
import com.starry.admin.modules.custom.service.IPlayCustomUserInfoService;
import com.starry.admin.modules.shop.module.entity.PlayCouponInfoEntity;
import com.starry.admin.modules.shop.module.vo.*;
import com.starry.admin.modules.shop.service.IPlayCouponDetailsService;
import com.starry.admin.modules.shop.service.IPlayCouponInfoService;
import com.starry.common.annotation.Log;
import com.starry.common.enums.BusinessType;
import com.starry.common.result.R;
import com.starry.common.utils.ConvertUtil;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiParam;
import io.swagger.annotations.ApiResponse;
import io.swagger.annotations.ApiResponses;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import javax.annotation.Resource;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;

/**
 * 优惠券信息Controller
 *
 * @author admin
 * @since 2024-07-04
 */
@Api(tags = "优惠券管理", description = "优惠券信息管理相关接口，包括查询、新增、修改、发放和删除等操作")
@RestController
@RequestMapping("/shop/coupon")
public class PlayCouponInfoController {
    @Resource
    private IPlayCouponInfoService playCouponInfoService;

    @Resource
    private IPlayCouponDetailsService playCouponDetailsService;

    @Resource
    private IPlayCustomUserInfoService playCustomUserInfoService;

    /**
     * 查询优惠券信息列表
     */
    @ApiOperation(value = "分页查询优惠券列表", notes = "分页查询优惠券信息列表")
    @ApiResponses({
            @ApiResponse(code = 200, message = "操作成功", response = PlayCouponInfoReturnVo.class, responseContainer = "Page")})
    // @PreAuthorize("@customSs.hasPermission('shop:coupon:list')")
    @PostMapping("/listByPage")
    public R list(@ApiParam(value = "查询条件") PlayCouponInfoQueryVo vo) {
        IPage<PlayCouponInfoReturnVo> list = playCouponInfoService.selectByPage(vo);
        return R.ok(list);
    }

    /**
     * 修改优惠券上下架状态
     *
     * @param vo
     *            优惠券上下架对象
     */
    @ApiOperation(value = "更新优惠券上下架状态", notes = "修改优惠券上下架状态")
    @ApiResponses({@ApiResponse(code = 200, message = "操作成功"), @ApiResponse(code = 500, message = "更新失败，包含详细错误信息")})
    @PostMapping("/updateOnLineState")
    public R updateOnLineState(
            @ApiParam(value = "状态更新信息", required = true) @Validated @RequestBody PlayCouponInfoOnLineStateUpdateVo vo) {
        playCouponInfoService.selectPlayCouponInfoById(vo.getId());
        PlayCouponInfoEntity entity = new PlayCouponInfoEntity();
        entity.setId(vo.getId());
        entity.setCouponOnLineState(vo.getCouponOnLineState());
        playCouponInfoService.update(entity);
        return R.ok();
    }

    /**
     * 发放优惠券
     *
     * @param vo
     *            优惠券发放对象
     */
    @ApiOperation(value = "发放优惠券", notes = "向指定用户发放优惠券")
    @ApiResponses({@ApiResponse(code = 200, message = "操作成功"), @ApiResponse(code = 500, message = "发放失败，包含详细错误信息")})
    @PostMapping("/sendCoupon")
    public R sendCoupon(@ApiParam(value = "优惠券发放信息", required = true) @Validated @RequestBody PlayCouponInfoSendVo vo) {
        PlayCustomUserInfoEntity customUserInfo = playCustomUserInfoService.selectById(vo.getCustomId());
        playCouponDetailsService.create(customUserInfo.getId(), customUserInfo.getNickname(),
                customUserInfo.getLevelId(), vo.getId(), "2", "1");
        return R.ok();
    }

    /**
     * 获取优惠券信息详细信息
     */
    @ApiOperation(value = "获取优惠券详情", notes = "根据ID获取优惠券详细信息")
    @ApiImplicitParam(name = "id", value = "优惠券ID", required = true, paramType = "path", dataType = "String", example = "1")
    @ApiResponses({@ApiResponse(code = 200, message = "操作成功", response = PlayCouponInfoEntity.class)})
    // @PreAuthorize("@customSs.hasPermission('shop:coupon:query')")
    @GetMapping(value = "/{id}")
    public R getInfo(@PathVariable("id") String id) {
        return R.ok(playCouponInfoService.selectPlayCouponInfoById(id));
    }

    /**
     * 新增优惠券信息
     */
    @ApiOperation(value = "新增优惠券", notes = "新增优惠券信息")
    @ApiResponses({@ApiResponse(code = 200, message = "操作成功"), @ApiResponse(code = 500, message = "添加失败，包含详细错误信息")})
    // @PreAuthorize("@customSs.hasPermission('shop:coupon:create')")
    @Log(title = "优惠券信息", businessType = BusinessType.INSERT)
    @PostMapping("/create")
    public R create(@ApiParam(value = "优惠券信息", required = true) @Validated @RequestBody PlayCouponInfoAddVo vo) {
        PlayCouponInfoEntity entity = ConvertUtil.entityToVo(vo, PlayCouponInfoEntity.class);
        if ("1".equals(vo.getValidityPeriodType())) {
            if (vo.getProductiveTime() == null || vo.getProductiveTime().size() != 2) {
                throw new CustomException("用券时间输入错误");
            }
            LocalDateTime startTime = LocalDateTime.parse(vo.getProductiveTime().get(0),
                    DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
            LocalDateTime endTime = LocalDateTime.parse(vo.getProductiveTime().get(1),
                    DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
            if (endTime.isBefore(startTime)) {
                throw new CustomException("用券时间输入错误，结束时间大于开始时间");
            }
            entity.setProductiveTime(startTime);
            entity.setExpirationTime(endTime);
        }
        if ("2".equals(vo.getValidityPeriodType()) && vo.getEffectiveDay() <= 0) {
            throw new CustomException("用券时间输入错误，有效天数不能为0");
        }
        String discountContent = "";
        if (BigDecimal.ZERO.compareTo(vo.getUseMinAmount()) == 0) {
            discountContent = "无门槛";
        } else {
            discountContent = "满" + vo.getUseMinAmount() + "船票";
        }
        if ("0".equals(vo.getDiscountType())) {
            discountContent += ("减" + vo.getDiscountAmount() + "船票");
        } else {
            discountContent += ("打" + vo.getDiscountAmount() + "折");
        }
        entity.setDiscountContent(discountContent);
        entity.setCouponOnLineState("1");
        boolean success = playCouponInfoService.create(entity);
        if (success) {
            return R.ok();
        }
        return R.error("添加失败");
    }

    /**
     * 修改优惠券信息
     */
    @ApiOperation(value = "修改优惠券", notes = "修改优惠券信息")
    @ApiImplicitParam(name = "id", value = "优惠券ID", required = true, paramType = "path", dataType = "String", example = "1")
    @ApiResponses({@ApiResponse(code = 200, message = "操作成功"), @ApiResponse(code = 500, message = "修改失败，包含详细错误信息")})
    // @PreAuthorize("@customSs.hasPermission('shop:coupon:edit')")
    @Log(title = "优惠券信息", businessType = BusinessType.UPDATE)
    @PostMapping(value = "/update/{id}")
    public R update(@PathVariable String id,
            @ApiParam(value = "优惠券信息", required = true) @RequestBody PlayCouponInfoEntity playCouponInfo) {
        playCouponInfo.setId(id);
        boolean success = playCouponInfoService.update(playCouponInfo);
        if (success) {
            return R.ok();
        }
        return R.error("修改失败");
    }

    /**
     * 删除优惠券信息
     */
    @ApiOperation(value = "删除优惠券", notes = "根据ID批量删除优惠券信息")
    @ApiImplicitParam(name = "ids", value = "优惠券ID数组", required = true, paramType = "path", dataType = "String[]", example = "1,2,3")
    @ApiResponses({@ApiResponse(code = 200, message = "操作成功")})
    // @PreAuthorize("@customSs.hasPermission('shop:coupon:remove')")
    @Log(title = "优惠券信息", businessType = BusinessType.DELETE)
    @DeleteMapping("/{ids}")
    public R remove(@PathVariable String[] ids) {
        return R.ok(playCouponInfoService.deletePlayCouponInfoByIds(ids));
    }
}


--- File: play-admin/src/main/java/com/starry/admin/modules/shop/controller/PlayGiftInfoController.java ---
package com.starry.admin.modules.shop.controller;

import com.baomidou.mybatisplus.core.metadata.IPage;
import com.starry.admin.modules.shop.module.entity.PlayGiftInfoEntity;
import com.starry.admin.modules.shop.service.IPlayGiftInfoService;
import com.starry.common.annotation.Log;
import com.starry.common.enums.BusinessType;
import com.starry.common.result.R;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiParam;
import io.swagger.annotations.ApiResponse;
import io.swagger.annotations.ApiResponses;
import javax.annotation.Resource;
import org.springframework.web.bind.annotation.*;

/**
 * 礼物Controller
 *
 * @author admin
 * @since 2024-04-25
 */
@Api(tags = "礼物管理", description = "礼物信息管理相关接口，包括查询、新增、修改和删除等操作")
@RestController
@RequestMapping("/play/giff")
public class PlayGiftInfoController {
    @Resource
    private IPlayGiftInfoService playGiftInfoService;

    /**
     * 查询礼物列表
     */
    @ApiOperation(value = "查询礼物列表", notes = "分页查询礼物信息列表")
    @ApiResponses({
            @ApiResponse(code = 200, message = "操作成功", response = PlayGiftInfoEntity.class, responseContainer = "Page")})
    @GetMapping("/list")
    public R list(@ApiParam(value = "查询条件") PlayGiftInfoEntity playGiftInfo) {
        IPage<PlayGiftInfoEntity> list = playGiftInfoService.selectPlayGiftInfoByPage(playGiftInfo);
        return R.ok(list);
    }

    /**
     * 获取礼物详细信息
     */
    @ApiOperation(value = "获取礼物详情", notes = "根据ID获取礼物详细信息")
    @ApiImplicitParam(name = "id", value = "礼物ID", required = true, paramType = "path", dataType = "String", example = "1")
    @ApiResponses({@ApiResponse(code = 200, message = "操作成功", response = PlayGiftInfoEntity.class)})
    @GetMapping(value = "/{id}")
    public R getInfo(@PathVariable("id") String id) {
        return R.ok(playGiftInfoService.selectPlayGiftInfoById(id));
    }

    /**
     * 新增礼物
     */
    @ApiOperation(value = "新增礼物", notes = "新增礼物信息")
    @ApiResponses({@ApiResponse(code = 200, message = "操作成功"), @ApiResponse(code = 500, message = "添加失败，包含详细错误信息")})
    @Log(title = "礼物", businessType = BusinessType.INSERT)
    @PostMapping("/create")
    public R create(@ApiParam(value = "礼物信息", required = true) @RequestBody PlayGiftInfoEntity playGiftInfo) {
        boolean success = playGiftInfoService.create(playGiftInfo);
        if (success) {
            return R.ok();
        }
        return R.error("添加失败");
    }

    /**
     * 修改礼物
     */
    @ApiOperation(value = "修改礼物", notes = "修改礼物信息")
    @ApiImplicitParam(name = "id", value = "礼物ID", required = true, paramType = "path", dataType = "String", example = "1")
    @ApiResponses({@ApiResponse(code = 200, message = "操作成功"), @ApiResponse(code = 500, message = "修改失败，包含详细错误信息")})
    @Log(title = "礼物", businessType = BusinessType.UPDATE)
    @PostMapping(value = "/update/{id}")
    public R update(@PathVariable String id,
            @ApiParam(value = "礼物信息", required = true) @RequestBody PlayGiftInfoEntity playGiftInfo) {
        playGiftInfo.setId(id);
        boolean success = playGiftInfoService.update(playGiftInfo);
        if (success) {
            return R.ok();
        }
        return R.error("修改失败");
    }

    /**
     * 删除礼物
     */
    @ApiOperation(value = "删除礼物", notes = "根据ID批量删除礼物信息")
    @ApiImplicitParam(name = "ids", value = "礼物ID数组", required = true, paramType = "path", dataType = "String[]", example = "1,2,3")
    @ApiResponses({@ApiResponse(code = 200, message = "操作成功")})
    @Log(title = "礼物", businessType = BusinessType.DELETE)
    @DeleteMapping("/{ids}")
    public R remove(@PathVariable String[] ids) {
        return R.ok(playGiftInfoService.deletePlayGiftInfoByIds(ids));
    }
}


--- File: play-admin/src/main/java/com/starry/admin/modules/shop/controller/PlayShopArticleInfoController.java ---
package com.starry.admin.modules.shop.controller;

import com.baomidou.mybatisplus.core.metadata.IPage;
import com.starry.admin.common.exception.CustomException;
import com.starry.admin.modules.shop.module.entity.PlayShopArticleInfoEntity;
import com.starry.admin.modules.shop.module.vo.PlayShopArticleInfoAddVo;
import com.starry.admin.modules.shop.module.vo.PlayShopArticleInfoQueryVo;
import com.starry.admin.modules.shop.module.vo.PlayShopArticleInfoUpdateVo;
import com.starry.admin.modules.shop.service.IPlayShopArticleInfoService;
import com.starry.common.annotation.Log;
import com.starry.common.enums.BusinessType;
import com.starry.common.result.R;
import com.starry.common.utils.ConvertUtil;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiParam;
import io.swagger.annotations.ApiResponse;
import io.swagger.annotations.ApiResponses;
import java.util.Date;
import javax.annotation.Resource;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;

/**
 * 店铺文章信息Controller
 *
 * @author admin
 * @since 2024-06-05
 */
@Api(tags = "店铺文章管理", description = "店铺文章信息管理相关接口，包括查询、新增、修改和删除等操作")
@RestController
@RequestMapping("/shop/articleInfo")
public class PlayShopArticleInfoController {

    @Resource
    private IPlayShopArticleInfoService playShopArticleInfoService;

    /**
     * 查询店铺文章信息列表
     */
    @ApiOperation(value = "分页查询文章列表", notes = "分页查询店铺文章信息列表")
    @ApiResponses({
            @ApiResponse(code = 200, message = "操作成功", response = PlayShopArticleInfoEntity.class, responseContainer = "Page")})
    // @PreAuthorize("@customSs.hasPermission('shop:articleInfo:list')")
    @PostMapping("/listByPage")
    public R listByPage(
            @ApiParam(value = "查询条件", required = true) @Validated @RequestBody PlayShopArticleInfoQueryVo vo) {
        IPage<PlayShopArticleInfoEntity> list = playShopArticleInfoService.selectByPage(vo);
        return R.ok(list);
    }

    /**
     * 新增店铺文章信息
     */
    @ApiOperation(value = "新增或修改文章", notes = "新增或修改店铺文章信息")
    @ApiResponses({@ApiResponse(code = 200, message = "操作成功"), @ApiResponse(code = 500, message = "添加失败，包含详细错误信息")})
    // @PreAuthorize("@customSs.hasPermission('shop:articleInfo:add')")
    @PostMapping("/create")
    public R create(@ApiParam(value = "文章信息", required = true) @Validated @RequestBody PlayShopArticleInfoAddVo vo) {
        PlayShopArticleInfoEntity entity = ConvertUtil.entityToVo(vo, PlayShopArticleInfoEntity.class);
        PlayShopArticleInfoEntity entity1 = playShopArticleInfoService.selectByType(entity.getArticleType());
        if (entity.getId() == null) {
            if (entity1 != null) {
                throw new CustomException("同类型的文章只能存在一个");
            }
            playShopArticleInfoService.create(entity);
        } else {
            if (entity1 != null && !entity1.getId().equals(entity.getId())) {
                throw new CustomException("同类型的文章只能存在一个");
            }
            entity.setUpdatedTime(new Date());
            playShopArticleInfoService.update(entity);
        }

        return R.ok();
    }

    /**
     * 修改店铺文章信息
     */
    @ApiOperation(value = "更新文章状态", notes = "修改店铺文章状态")
    @ApiResponses({@ApiResponse(code = 200, message = "操作成功"), @ApiResponse(code = 500, message = "修改失败，包含详细错误信息")})
    // @PreAuthorize("@customSs.hasPermission('shop:articleInfo:update')")
    @PostMapping("/handleUpdateState")
    public R listByPage(
            @ApiParam(value = "更新信息", required = true) @Validated @RequestBody PlayShopArticleInfoUpdateVo vo) {
        PlayShopArticleInfoEntity entity = ConvertUtil.entityToVo(vo, PlayShopArticleInfoEntity.class);
        playShopArticleInfoService.update(entity);
        return R.ok();
    }

    /**
     * 删除店铺文章信息
     */
    @ApiOperation(value = "删除文章", notes = "根据ID批量删除店铺文章信息")
    @ApiImplicitParam(name = "ids", value = "文章ID数组", required = true, paramType = "path", dataType = "String[]", example = "1,2,3")
    @ApiResponses({@ApiResponse(code = 200, message = "操作成功")})
    // @PreAuthorize("@customSs.hasPermission('shop:articleInfo:delete')")
    @Log(title = "店铺文章信息", businessType = BusinessType.DELETE)
    @DeleteMapping("/{ids}")
    public R remove(@PathVariable String[] ids) {
        return R.ok(playShopArticleInfoService.deleteByIds(ids));
    }

}


--- File: play-admin/src/main/java/com/starry/admin/modules/shop/controller/PlayShopCarouselInfoController.java ---
package com.starry.admin.modules.shop.controller;

import com.baomidou.mybatisplus.core.metadata.IPage;
import com.starry.admin.common.oss.service.IOssFileService;
import com.starry.admin.modules.shop.module.entity.PlayShopCarouselInfoEntity;
import com.starry.admin.modules.shop.module.vo.PlayShopCarouselInfoAddVo;
import com.starry.admin.modules.shop.module.vo.PlayShopCarouselInfoQueryVo;
import com.starry.admin.modules.shop.module.vo.PlayShopCarouselInfoUpdateStateVo;
import com.starry.admin.modules.shop.service.IPlayShopCarouselInfoService;
import com.starry.admin.utils.SecurityUtils;
import com.starry.common.annotation.Log;
import com.starry.common.enums.BusinessType;
import com.starry.common.result.R;
import com.starry.common.utils.ConvertUtil;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiParam;
import io.swagger.annotations.ApiResponse;
import io.swagger.annotations.ApiResponses;
import java.io.IOException;
import javax.annotation.Resource;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;

/**
 * 店铺首页轮播Controller
 *
 * @author admin
 * @since 2024-06-05
 */
@Api(tags = "店铺轮播图管理", description = "店铺首页轮播图信息管理相关接口，包括查询、新增、修改和删除等操作")
@RestController
@RequestMapping("/shop/carousel")
public class PlayShopCarouselInfoController {
    @Resource
    private IPlayShopCarouselInfoService playCarouselInfoService;

    @Resource
    private IOssFileService ossFileService;

    /**
     * 查询店铺首页轮播列表
     */
    @ApiOperation(value = "分页查询轮播图列表", notes = "分页查询店铺首页轮播图信息列表")
    @ApiResponses({
            @ApiResponse(code = 200, message = "操作成功", response = PlayShopCarouselInfoEntity.class, responseContainer = "Page")})
    // @PreAuthorize("@customSs.hasPermission('shop:carousel:list')")
    @PostMapping("/listByPage")
    public R listByPage(
            @ApiParam(value = "查询条件", required = true) @Validated @RequestBody PlayShopCarouselInfoQueryVo vo) {
        IPage<PlayShopCarouselInfoEntity> list = playCarouselInfoService.selectByPage(vo);
        return R.ok(list);
    }

    @ApiOperation(value = "上传轮播图文件", notes = "上传店铺首页轮播图文件")
    @ApiResponses({@ApiResponse(code = 200, message = "操作成功，返回文件地址"),
            @ApiResponse(code = 500, message = "上传失败，包含详细错误信息")})
    // @PreAuthorize("@customSs.hasPermission('shop:carousel:add')")
    @PostMapping("/uploadFile")
    public R uploadFile(@ApiParam(value = "轮播图文件", required = true) @RequestParam("file") MultipartFile file)
            throws IOException {
        String fileAddress = ossFileService.upload(file.getInputStream(), SecurityUtils.getTenantId(),
                file.getOriginalFilename());
        return R.ok(fileAddress);
    }

    /**
     * 新增页轮播列表
     */
    @ApiOperation(value = "新增轮播图", notes = "新增店铺首页轮播图信息")
    @ApiResponses({@ApiResponse(code = 200, message = "操作成功"), @ApiResponse(code = 500, message = "添加失败，包含详细错误信息")})
    // @PreAuthorize("@customSs.hasPermission('shop:carousel:add')")
    @PostMapping("/create")
    public R create(@ApiParam(value = "轮播图信息", required = true) @Validated @RequestBody PlayShopCarouselInfoAddVo vo) {
        PlayShopCarouselInfoEntity entity = ConvertUtil.entityToVo(vo, PlayShopCarouselInfoEntity.class);
        playCarouselInfoService.create(entity);
        return R.ok();
    }

    /**
     * 修改页轮播状态
     */
    @ApiOperation(value = "更新轮播图状态", notes = "修改店铺首页轮播图状态")
    @ApiResponses({@ApiResponse(code = 200, message = "操作成功"), @ApiResponse(code = 500, message = "修改失败，包含详细错误信息")})
    // @PreAuthorize("@customSs.hasPermission('shop:carousel:update')")
    @PostMapping("/handleUpdateState")
    public R listByPage(
            @ApiParam(value = "更新信息", required = true) @Validated @RequestBody PlayShopCarouselInfoUpdateStateVo vo) {
        PlayShopCarouselInfoEntity entity = ConvertUtil.entityToVo(vo, PlayShopCarouselInfoEntity.class);
        playCarouselInfoService.update(entity);
        return R.ok();
    }

    /**
     * 删除店铺首页轮播
     */
    @ApiOperation(value = "删除轮播图", notes = "根据ID批量删除店铺首页轮播图信息")
    @ApiImplicitParam(name = "ids", value = "轮播图ID数组", required = true, paramType = "path", dataType = "String[]", example = "1,2,3")
    @ApiResponses({@ApiResponse(code = 200, message = "操作成功")})
    // @PreAuthorize("@customSs.hasPermission('shop:carousel:delete')")
    @Log(title = "店铺首页轮播", businessType = BusinessType.DELETE)
    @DeleteMapping("/{ids}")
    public R remove(@PathVariable String[] ids) {
        return R.ok(playCarouselInfoService.deletePlayCarouselInfoByIds(ids));
    }

}


--- File: play-admin/src/main/java/com/starry/admin/modules/shop/controller/ShopUiSettingController.java ---
package com.starry.admin.modules.shop.controller;

import com.starry.admin.modules.shop.module.entity.ShopUiSettingEntity;
import com.starry.admin.modules.shop.service.IShopUiSettingService;
import com.starry.common.result.R;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiParam;
import io.swagger.annotations.ApiResponse;
import io.swagger.annotations.ApiResponses;
import javax.annotation.Resource;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 * 店铺设置Controller
 *
 * @author admin
 * @since 2025-06-11
 */
@Api(tags = "店铺UI设置", description = "店铺UI设置相关接口，包括查询和修改操作")
@RestController
@RequestMapping("/play/setting")
public class ShopUiSettingController {
    @Resource
    private IShopUiSettingService shopUiSettingService;

    /**
     * 获取店铺设置详细信息
     */
    @ApiOperation(value = "获取店铺设置信息", notes = "获取店铺UI设置详细信息")
    @ApiResponses({@ApiResponse(code = 200, message = "操作成功", response = ShopUiSettingEntity.class)})
    @GetMapping(value = "/info")
    public R getInfo() {
        return R.ok(shopUiSettingService.selectShopUiSettingInfo());
    }

    /**
     * 修改店铺设置
     */
    @ApiOperation(value = "修改店铺设置", notes = "修改店铺UI设置信息")
    @ApiResponses({@ApiResponse(code = 200, message = "操作成功"), @ApiResponse(code = 500, message = "修改失败，包含详细错误信息")})
    @PostMapping(value = "/update")
    public R update(@ApiParam(value = "店铺UI设置信息", required = true) @RequestBody ShopUiSettingEntity shopUiSetting) {
        boolean success = shopUiSettingService.update(shopUiSetting);
        if (success) {
            return R.ok();
        }
        return R.error("修改失败");
    }

}


--- File: play-admin/src/main/java/com/starry/admin/modules/shop/mapper/PlayClerkGiftInfoMapper.java ---
package com.starry.admin.modules.shop.mapper;

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.starry.admin.modules.shop.module.entity.PlayClerkGiftInfoEntity;

/**
 * 店员和礼物关系Mapper接口
 *
 * @author admin
 * @since 2024-05-25
 */
public interface PlayClerkGiftInfoMapper extends BaseMapper<PlayClerkGiftInfoEntity> {

}


--- File: play-admin/src/main/java/com/starry/admin/modules/shop/mapper/PlayCommodityAndLevelInfoMapper.java ---
package com.starry.admin.modules.shop.mapper;

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.starry.admin.modules.shop.module.entity.PlayCommodityAndLevelInfoEntity;

/**
 * 服务项目和店员等级数据Mapper接口
 *
 * @author admin
 * @since 2024-08-16
 */
public interface PlayCommodityAndLevelInfoMapper extends BaseMapper<PlayCommodityAndLevelInfoEntity> {

}


--- File: play-admin/src/main/java/com/starry/admin/modules/shop/mapper/PlayCommodityInfoMapper.java ---
package com.starry.admin.modules.shop.mapper;

import com.github.yulichang.base.MPJBaseMapper;
import com.starry.admin.modules.shop.module.entity.PlayCommodityInfoEntity;
import com.starry.admin.modules.shop.module.vo.PlayCommodityInfoVo;
import org.apache.ibatis.annotations.Select;

/**
 * 服务项目Mapper接口
 *
 * @author admin
 * @since 2024-03-31
 */
public interface PlayCommodityInfoMapper extends MPJBaseMapper<PlayCommodityInfoEntity> {

    // /**
    // * 查询服务项目信息
    // *
    // * @param id UUID
    // * @return 项目信息
    // */
    // @Select("select t.id as commodityId,t.price as commodityPrice,t.item_name as
    // serviceDuration,t1.item_name as commodityName from play_commodity_info t left
    // join play_commodity_info t1 on t.p_id = t1.id where t.id = #{id} limit 1")
    // PlayCommodityInfoVo queryCommodityInfo(String id);

    /**
     * 查询服务项目信息
     *
     * @param id
     *            UUID
     * @return 项目信息
     */
    @Select("select t.id as commodityId,t3.price as commodityPrice,t.item_name as serviceDuration,t1.item_name as commodityName from play_commodity_info t left join play_commodity_info t1 on t.p_id = t1.id left join play_commodity_and_level_info t3 ON t3.commodity_id = t.id where t3.price is not null and t.id  = #{id} and t3.level_id = #{levelId} limit 1")
    PlayCommodityInfoVo queryCommodityInfo(String id, String levelId);

}


--- File: play-admin/src/main/java/com/starry/admin/modules/shop/mapper/PlayCouponDetailsMapper.java ---
package com.starry.admin.modules.shop.mapper;

import com.github.yulichang.base.MPJBaseMapper;
import com.starry.admin.modules.shop.module.entity.PlayCouponDetailsEntity;

/**
 * 优惠券详情Mapper接口
 *
 * @author admin
 * @since 2024-07-04
 */
public interface PlayCouponDetailsMapper extends MPJBaseMapper<PlayCouponDetailsEntity> {

}


--- File: play-admin/src/main/java/com/starry/admin/modules/shop/mapper/PlayCouponInfoMapper.java ---
package com.starry.admin.modules.shop.mapper;

import com.github.yulichang.base.MPJBaseMapper;
import com.starry.admin.modules.shop.module.entity.PlayCouponInfoEntity;

/**
 * 优惠券信息Mapper接口
 *
 * @author admin
 * @since 2024-07-04
 */
public interface PlayCouponInfoMapper extends MPJBaseMapper<PlayCouponInfoEntity> {

}


--- File: play-admin/src/main/java/com/starry/admin/modules/shop/mapper/PlayGiftInfoMapper.java ---
package com.starry.admin.modules.shop.mapper;

import com.github.yulichang.base.MPJBaseMapper;
import com.starry.admin.modules.shop.module.entity.PlayGiftInfoEntity;

/**
 * 礼物Mapper接口
 *
 * @author admin
 * @since 2024-04-25
 */
public interface PlayGiftInfoMapper extends MPJBaseMapper<PlayGiftInfoEntity> {

}


--- File: play-admin/src/main/java/com/starry/admin/modules/shop/mapper/PlayShopArticleInfoMapper.java ---
package com.starry.admin.modules.shop.mapper;

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.starry.admin.modules.shop.module.entity.PlayShopArticleInfoEntity;

/**
 * 店铺文章信息Mapper接口
 *
 * @author admin
 * @since 2024-06-05
 */
public interface PlayShopArticleInfoMapper extends BaseMapper<PlayShopArticleInfoEntity> {

}


--- File: play-admin/src/main/java/com/starry/admin/modules/shop/mapper/PlayShopCarouselInfoMapper.java ---
package com.starry.admin.modules.shop.mapper;

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.starry.admin.modules.shop.module.entity.PlayShopCarouselInfoEntity;

/**
 * 店铺首页轮播Mapper接口
 *
 * @author admin
 * @since 2024-06-05
 */
public interface PlayShopCarouselInfoMapper extends BaseMapper<PlayShopCarouselInfoEntity> {

}


--- File: play-admin/src/main/java/com/starry/admin/modules/shop/mapper/ShopUiSettingMapper.java ---
package com.starry.admin.modules.shop.mapper;

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.starry.admin.modules.shop.module.entity.ShopUiSettingEntity;

/**
 * 店铺设置Mapper接口
 *
 * @author admin
 * @since 2025-06-11
 */
public interface ShopUiSettingMapper extends BaseMapper<ShopUiSettingEntity> {

}


--- File: play-admin/src/main/java/com/starry/admin/modules/shop/module/constant/GiftConstant.java ---
package com.starry.admin.modules.shop.module.constant;

/**
 * 礼物常量
 *
 * @author admin
 * @since 2024/5/8 9:50
 **/
public class GiftConstant {

    /**
     * 礼物单词最大赠送数量
     *
     * @since 2024/5/8 9:51
     **/
    public final static int MAXIMUM_SINGLE_GIFT_QUANTITY = 1000;

    /**
     * 礼物单词最大赠送数量
     *
     * @since 2024/5/8 9:51
     **/
    public final static int MINIMUM_SINGLE_GIFT_QUANTITY = 1;
}


--- File: play-admin/src/main/java/com/starry/admin/modules/shop/module/entity/PlayClerkGiftInfoEntity.java ---
package com.starry.admin.modules.shop.module.entity;

import com.baomidou.mybatisplus.annotation.TableName;
import com.starry.common.domain.BaseEntity;
import lombok.Data;
import lombok.EqualsAndHashCode;

/**
 * 店员和礼物关系对象 play_clerk_gift_info
 *
 * @author admin
 * @since 2024-05-25
 */
@Data
@EqualsAndHashCode(callSuper = false)
@TableName("play_clerk_gift_info")
public class PlayClerkGiftInfoEntity extends BaseEntity<PlayClerkGiftInfoEntity> {

    /**
     * UUID
     */
    private String id;

    /**
     * 租户ID
     */
    private String tenantId;

    /**
     * 店员ID
     */
    private String clerkId;

    /**
     * 礼物ID
     */
    private String giffId;

    /**
     * 礼物数量
     */
    private Long giffNumber;

}


--- File: play-admin/src/main/java/com/starry/admin/modules/shop/module/entity/PlayCommodityAndLevelInfoEntity.java ---
package com.starry.admin.modules.shop.module.entity;

import com.baomidou.mybatisplus.annotation.TableName;
import com.starry.common.domain.BaseEntity;
import java.math.BigDecimal;
import lombok.Data;
import lombok.EqualsAndHashCode;

/**
 * 服务项目和店员等级数据对象 play_commodity_and_level_info
 *
 * @author admin
 * @since 2024-08-16
 */
@Data
@EqualsAndHashCode(callSuper = false)
@TableName("play_commodity_and_level_info")
public class PlayCommodityAndLevelInfoEntity extends BaseEntity<PlayCommodityAndLevelInfoEntity> {

    /**
     * UUID
     */
    private String id;

    /**
     * 租户ID
     */
    private String tenantId;

    /**
     * 商品ID
     */
    private String commodityId;

    /**
     * 店员等级ID
     */
    private String levelId;

    /**
     * 服务单价
     */
    private BigDecimal price;

    /**
     * 排序
     */
    private Long sort;

}


--- File: play-admin/src/main/java/com/starry/admin/modules/shop/module/entity/PlayCommodityInfoEntity.java ---
package com.starry.admin.modules.shop.module.entity;

import com.baomidou.mybatisplus.annotation.TableName;
import com.starry.common.domain.BaseEntity;
import lombok.Data;
import lombok.EqualsAndHashCode;

/**
 * 服务项目对象 play_commodity_info
 *
 * @author admin
 * @since 2024-03-31
 */
@Data
@EqualsAndHashCode(callSuper = false)
@TableName("play_commodity_info")
public class PlayCommodityInfoEntity extends BaseEntity<PlayCommodityInfoEntity> {

    /**
     * UUID
     */
    private String id;

    /**
     * Pid
     **/
    private String pId;

    /**
     * 租户ID
     */
    private String tenantId;

    /**
     * 项目类型
     */
    private String itemType;

    /**
     * 项目名称
     */
    private String itemName;

    /**
     * 服务时长（文字描述信息,不参与订单计算）
     */
    private String serviceDuration;

    /**
     * 启用状态(0:停用,1:启用)
     */
    private String enableStace;

    /**
     * 排序
     **/
    private Integer sort;
}


--- File: play-admin/src/main/java/com/starry/admin/modules/shop/module/entity/PlayCouponDetailsEntity.java ---
package com.starry.admin.modules.shop.module.entity;

import com.baomidou.mybatisplus.annotation.TableName;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.starry.common.domain.BaseEntity;
import java.time.LocalDateTime;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.springframework.format.annotation.DateTimeFormat;

/**
 * 优惠券详情对象 play_coupon_details
 *
 * @author admin
 * @since 2024-07-04
 */
@Data
@EqualsAndHashCode(callSuper = false)
@TableName("play_coupon_details")
public class PlayCouponDetailsEntity extends BaseEntity<PlayCouponDetailsEntity> {

    /**
     * UUID
     */
    private String id;

    /**
     * 租户ID
     */
    private String tenantId;

    /**
     * 顾客ID
     */
    private String customId;

    /**
     * 顾客昵称
     */
    private String customNickname;

    /**
     * 顾客等级ID
     */
    private String customLevelId;

    /**
     * 优惠券ID
     */
    private String couponId;

    /**
     * 获得渠道（1:自主领取;2:后台发放;3:退款返还;4:分享领取;5:抽奖获得）
     */
    private String obtainingChannels;

    /**
     * 优惠券使用状态(1:未使用;2:已使用;3:已回收)
     */
    private String useState;

    /**
     *
     * /** 获得时间
     */
    @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
    private LocalDateTime obtainingTime;

    /**
     * 使用时间
     */
    @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
    private LocalDateTime useTime;

}


--- File: play-admin/src/main/java/com/starry/admin/modules/shop/module/entity/PlayCouponInfoEntity.java ---
package com.starry.admin.modules.shop.module.entity;

import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableName;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.starry.admin.common.conf.StringTypeHandler;
import com.starry.common.domain.BaseEntity;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.List;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.springframework.format.annotation.DateTimeFormat;

/**
 * 优惠券信息对象 play_coupon_info
 *
 * @author admin
 * @since 2024-07-04
 */
@Data
@EqualsAndHashCode(callSuper = false)
@TableName(value = "play_coupon_info", autoResultMap = true)
public class PlayCouponInfoEntity extends BaseEntity<PlayCouponInfoEntity> {

    /**
     * UUID
     */
    private String id;

    /**
     * 优惠券名称
     **/
    private String couponName = "测试券";
    /**
     * 租户ID
     */
    private String tenantId;

    /**
     * 有效期类型（0:永久有效;1:固定时间内有效;2:领取后几天内有效）
     */
    private String validityPeriodType;

    /**
     * 生效时间
     */
    @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
    private LocalDateTime productiveTime;

    /**
     * 到期时间
     */
    @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
    private LocalDateTime expirationTime;

    /**
     * 生效时长,validityPeriodType=2时有效
     **/
    private Integer effectiveDay;

    /**
     * 使用优惠券最低消费金额
     **/
    private BigDecimal useMinAmount;

    /**
     * 优惠类型（0:满减;1:折扣）
     */
    private String discountType;

    /**
     * 优惠内容，文字展示
     */
    private String discountContent;

    /**
     * 折扣值
     */
    private BigDecimal discountAmount;

    /**
     * 优惠金额是否由店铺承担（0:不是;1:是）
     */
    private String attributionDiscounts;

    /**
     * 下单类型（0：指定单，1：随机单。2：打赏单）
     */
    @TableField(typeHandler = StringTypeHandler.class)
    private List<String> placeType;

    /**
     * 店员范围（0:所有店员）
     */
    private String clerkType;

    /**
     * 优惠券总数
     */
    private Integer couponQuantity;

    /**
     * 已发放数量
     */
    private Integer issuedQuantity;

    /**
     * 剩余数量
     */
    private Integer remainingQuantity;

    /**
     * 店员获得最大数量
     */
    private Integer clerkObtainedMaxQuantity;

    /**
     * 领取条件类型（0:所有人可领取,1:指定条件领取）
     */
    private String claimConditionType;

    /**
     * 顾客等级选择状态(0:未选择,1:选择)
     */
    private String customLevelCheckType;

    /**
     * 店员等级
     */
    @TableField(typeHandler = StringTypeHandler.class)
    private List<String> customLevel;

    /**
     * 用户关注公众号选择状态(0:未选择,1:选择)
     */
    private String customFollowStatusCheckType;

    /**
     * 顾客关注公众号状态(0:未关注,1:已关注)
     */
    private String customFollowStatus;

    /**
     * 顾客性别选择状态(0:未选择,1:选择)
     */
    private String customSexCheckType;
    /**
     * 顾客性别（0:未知;1:男,2:女）
     */
    @TableField(typeHandler = StringTypeHandler.class)
    private List<String> customSex;

    /**
     * 仅限新用户领取（0:不是;1:是）(没有下过单的用户，下单后再退单也不算)
     */
    private String newUser;

    /**
     * 优惠券上架状态（0;下架;1:上架）
     */
    private String couponOnLineState;

}


--- File: play-admin/src/main/java/com/starry/admin/modules/shop/module/entity/PlayGiftInfoEntity.java ---
package com.starry.admin.modules.shop.module.entity;

import com.baomidou.mybatisplus.annotation.TableName;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.starry.common.domain.BaseEntity;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.springframework.format.annotation.DateTimeFormat;

/**
 * 礼物对象 play_gift_info
 *
 * @author admin
 * @since 2024-04-25
 */
@Data
@EqualsAndHashCode(callSuper = false)
@TableName("play_gift_info")
public class PlayGiftInfoEntity extends BaseEntity<PlayGiftInfoEntity> {

    /**
     * UUID
     */
    private String id;

    /**
     * 租户ID
     */
    private String tenantId;

    /**
     * 是否是历史礼物(0:不是,1:是)
     */
    private String history;

    /**
     * 礼物名称
     */
    private String name;

    /**
     * 礼物类型（0：盲盒,1:普通礼物）
     */
    private String type;

    /**
     * 礼物图片地址
     */
    private String url;

    /**
     * 单价
     */
    private BigDecimal price;

    /**
     * 价格单位
     */
    private String unit;

    /**
     * 状态（0:正常,1:下架）
     */
    private String state;

    /**
     * 上架时间
     */
    @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
    private LocalDateTime listingTime;

    /**
     * 下架时间
     */
    @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
    private LocalDateTime delintingTime;

    /**
     * 备注
     */
    private String remark;

}


--- File: play-admin/src/main/java/com/starry/admin/modules/shop/module/entity/PlayShopArticleInfoEntity.java ---
package com.starry.admin.modules.shop.module.entity;

import com.baomidou.mybatisplus.annotation.TableName;
import com.starry.common.domain.BaseEntity;
import lombok.Data;
import lombok.EqualsAndHashCode;

/**
 * 店铺文章信息
 *
 * @author admin
 * @since 2024/7/18 11:00
 **/
@Data
@EqualsAndHashCode(callSuper = false)
@TableName(value = "play_shop_article_info", autoResultMap = true)
public class PlayShopArticleInfoEntity extends BaseEntity<PlayShopArticleInfoEntity> {
    /**
     * UUID
     */
    private String id;

    /**
     * 租户ID
     */
    private String tenantId;

    /**
     * 文章类型
     *
     **/
    private String articleType;

    /**
     * 文章标题
     *
     **/
    private String articleTitle;

    /**
     * 文章内容
     *
     **/
    private String articleContent;

    /**
     * 浏览量
     *
     **/
    private Integer visitsNumber;

    /**
     * 排序
     *
     **/
    private Integer sort;
}


--- File: play-admin/src/main/java/com/starry/admin/modules/shop/module/entity/PlayShopCarouselInfoEntity.java ---
package com.starry.admin.modules.shop.module.entity;

import com.baomidou.mybatisplus.annotation.TableName;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.starry.common.domain.BaseEntity;
import java.time.LocalDateTime;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.springframework.format.annotation.DateTimeFormat;

/**
 * 店铺首页轮播对象 play_carousel_info
 *
 * @author admin
 * @since 2024-06-05
 */
@Data
@EqualsAndHashCode(callSuper = false)
@TableName("play_shop_carousel_info")
public class PlayShopCarouselInfoEntity extends BaseEntity<PlayShopCarouselInfoEntity> {

    /**
     * UUID
     */
    private String id;

    /**
     * 租户ID
     */
    private String tenantId;

    /**
     * 轮播图位置(0:首页)
     */
    private String carouselIndex;

    /**
     * 启用状态（0:未启用,1:已启用）
     */
    private String enableState;

    /**
     * 图片URL
     */
    private String carouselUrl;

    /**
     * 导航类型(0:无)
     */
    private String navigationType;

    /**
     * 排序
     */
    private Integer sort;

    /**
     * 创建时间
     */
    @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
    private LocalDateTime addTime;

}


--- File: play-admin/src/main/java/com/starry/admin/modules/shop/module/entity/ShopUiSettingEntity.java ---
package com.starry.admin.modules.shop.module.entity;

import com.baomidou.mybatisplus.annotation.TableName;
import com.starry.common.domain.BaseEntity;
import java.util.Date;
import lombok.Data;
import lombok.EqualsAndHashCode;

/**
 * 店铺设置对象 shop_ui_setting
 *
 * @author admin
 * @since 2025-06-11
 */
@Data
@EqualsAndHashCode(callSuper = false)
@TableName("shop_ui_setting")
public class ShopUiSettingEntity extends BaseEntity<ShopUiSettingEntity> {

    /**
     * UUID
     */
    private String id;

    /**
     * 租户ID
     */
    private String tenantId;

    /**
     * 创建人的id
     */
    private String createdBy;

    /**
     * 创建时间
     */
    private Date createdTime;

    /**
     * 修改人的id
     */
    private String updatedBy;

    /**
     * 修改时间
     */
    private Date updatedTime;

    /**
     * 数据版本
     */
    private Long version;

    /**
     * 配置内容
     */
    private String settingConfig;

}


--- File: play-admin/src/main/java/com/starry/admin/modules/shop/module/vo/PlayCommodityInfoAddVo.java ---
package com.starry.admin.modules.shop.module.vo;

import javax.validation.constraints.NotNull;
import lombok.Data;
import lombok.EqualsAndHashCode;

/**
 * 服务项目对象 play_commodity_info
 *
 * @author admin
 * @since 2024-03-31
 */
@Data
@EqualsAndHashCode(callSuper = false)
public class PlayCommodityInfoAddVo {

    /**
     * 项目类型
     */
    @NotNull(message = "项目类型不能为空")
    private String itemType;

    /**
     * 项目名称
     */
    @NotNull(message = "项目名称不能为空")
    private String itemName;

    /**
     * 服务时长（文字描述信息,不参与订单计算）
     */
    @NotNull(message = "服务时长不能为空")
    private String serviceDuration;

    /**
     * 服务单价
     */
    @NotNull(message = "单价不能为空")
    private String price;

}


--- File: play-admin/src/main/java/com/starry/admin/modules/shop/module/vo/PlayCommodityInfoReturnVo.java ---
package com.starry.admin.modules.shop.module.vo;

import lombok.Data;

/**
 * @author admin
 * @since 2024/8/18 下午7:59
 **/
@Data
public class PlayCommodityInfoReturnVo {

    /**
     * UUID
     */
    private String id;

    /**
     * Pid
     **/
    private String pId;

    /**
     * 租户ID
     */
    private String tenantId;

    /**
     * 项目类型
     */
    private String itemType;

    /**
     * 项目名称
     */
    private String itemName;

    /**
     * 服务时长（文字描述信息,不参与订单计算）
     */
    private String serviceDuration;

    /**
     * 启用状态(0:停用,1:启用)
     */
    private String enableStace;

    /**
     * 排序
     **/
    private Integer sort;

}


--- File: play-admin/src/main/java/com/starry/admin/modules/shop/module/vo/PlayCommodityInfoUpdateVo.java ---
package com.starry.admin.modules.shop.module.vo;

import javax.validation.constraints.NotNull;
import lombok.Data;
import lombok.EqualsAndHashCode;

/**
 * 服务项目对象 play_commodity_info
 *
 * @author admin
 * @since 2024-03-31
 */
@Data
@EqualsAndHashCode(callSuper = false)
public class PlayCommodityInfoUpdateVo {

    @NotNull(message = "ID不能为空")
    private String id;

    /**
     * 服务时长（文字描述信息,不参与订单计算）
     */
    @NotNull(message = "服务时长不能为空")
    private String serviceDuration;

    /**
     * 服务单价
     */
    @NotNull(message = "单价不能为空")
    private String price;

}


--- File: play-admin/src/main/java/com/starry/admin/modules/shop/module/vo/PlayCommodityInfoVo.java ---
package com.starry.admin.modules.shop.module.vo;

import java.math.BigDecimal;
import lombok.Data;

/**
 * 商品信息查询返回对象
 *
 * @author admin
 * @since 2024/6/2 上午8:52
 **/
@Data
public class PlayCommodityInfoVo {

    /**
     * UUID
     */
    private String commodityId;

    /**
     * 单价
     */
    private BigDecimal commodityPrice;

    /**
     * 名称
     */
    private String commodityName;

    /**
     * 服务时长
     */
    private String serviceDuration;
}


--- File: play-admin/src/main/java/com/starry/admin/modules/shop/module/vo/PlayCouponDetailsQueryVo.java ---
package com.starry.admin.modules.shop.module.vo;

import com.starry.common.domain.BasePageEntity;
import java.util.List;
import lombok.Data;
import lombok.EqualsAndHashCode;

/**
 * 优惠券详情查询接口
 *
 * @author admin
 * @since 2024/7/4 16:53
 **/
@EqualsAndHashCode(callSuper = true)
@Data
public class PlayCouponDetailsQueryVo extends BasePageEntity {

    /**
     * 优惠券ID
     **/
    private String couponId;

    /**
     * 用户ID
     **/
    private String customId;

    /**
     * 获得渠道（1:自主领取;2:后台发放;3:退款返还;4:分享领取;5:抽奖获得）
     */
    private String obtainingChannels;

    /**
     * 使用状态（1:未使用;2:已使用;3:已回收）
     */
    private String useState;

    /**
     * 使用时间
     */
    private List<String> useTime;

    /**
     * 获得时间
     */
    private List<String> obtainingTime;

    /**
     * 用户等级
     */
    private String customLevelId;

}


--- File: play-admin/src/main/java/com/starry/admin/modules/shop/module/vo/PlayCouponDetailsReturnVo.java ---
package com.starry.admin.modules.shop.module.vo;

import com.baomidou.mybatisplus.annotation.TableField;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.starry.admin.common.conf.StringTypeHandler;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.List;
import lombok.Data;
import org.springframework.format.annotation.DateTimeFormat;

/**
 * 优惠券详细信息返回列表
 *
 * @author admin
 * @since 2024/7/8 下午11:29
 **/
@Data
public class PlayCouponDetailsReturnVo {

    /**
     * UUID
     */
    private String id;

    /**
     * 顾客ID
     */
    private String customId;

    /**
     * 顾客昵称
     */
    private String customNickname;

    /**
     * 顾客等级ID
     */
    private String customLevelId;

    /**
     * 优惠券ID
     */
    private String couponId;

    /**
     * 获得渠道（1:自主领取;2:后台发放;3:退款返还;4:分享领取;5:抽奖获得）
     */
    private String obtainingChannels;

    /**
     * 优惠券使用状态(1:未使用;2:已使用;3:已回收)
     */
    private String useState;

    /**
     * 生效时间
     */
    @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
    private LocalDateTime productiveTime;

    /**
     * 到期时间
     */
    @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
    private LocalDateTime expirationTime;

    /**
     * 获得时间
     */
    @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
    private LocalDateTime obtainingTime;

    /**
     * 使用时间
     */
    @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
    private LocalDateTime useTime;

    /**
     * 优惠券名称
     **/
    private String couponName;

    /**
     * 有效期类型（0:永久有效;1:固定时间内有效;2:领取后几天内有效）
     */
    private String validityPeriodType;

    /**
     * 生效时长,validityPeriodType=2时有效
     **/
    private Integer effectiveDay;

    /**
     * 使用优惠券最低消费金额
     **/
    private BigDecimal useMinAmount;

    /**
     * 优惠类型（0:满减;1:折扣）
     */
    private String discountType;

    /**
     * 优惠内容，文字展示
     */
    private String discountContent;

    /**
     * 折扣值
     */
    private BigDecimal discountAmount;

    /**
     * 优惠金额是否由店铺承担（0:不是;1:是）
     */
    private String attributionDiscounts;

    /**
     * 下单类型（0：指定单，1：随机单。2：打赏单）
     */
    @TableField(typeHandler = StringTypeHandler.class)
    private List<String> placeType;

    /**
     * 店员范围（0:所有店员）
     */
    private String clerkType;

    /**
     * 优惠券总数
     */
    private Integer couponQuantity;

    /**
     * 已发放数量
     */
    private Integer issuedQuantity;

    /**
     * 剩余数量
     */
    private Integer remainingQuantity;

    /**
     * 店员获得最大数量
     */
    private Integer clerkObtainedMaxQuantity;

    /**
     * 领取条件类型（0:所有人可领取,1:指定条件领取）
     */
    private String claimConditionType;

    /**
     * 顾客等级选择状态(0:未选择,1:选择)
     */
    private String customLevelCheckType;

    /**
     * 店员等级
     */
    @TableField(typeHandler = StringTypeHandler.class)
    private List<String> customLevel;

    /**
     * 用户关注公众号选择状态(0:未选择,1:选择)
     */
    private String customFollowStatusCheckType;

    /**
     * 顾客关注公众号状态(0:未关注,1:已关注)
     */
    private String customFollowStatus;

    /**
     * 顾客性别选择状态(0:未选择,1:选择)
     */
    private String customSexCheckType;
    /**
     * 顾客性别（0:未知;1:男,2:女）
     */
    @TableField(typeHandler = StringTypeHandler.class)
    private List<String> customSex;

    /**
     * 仅限新用户领取（0:不是;1:是）(没有下过单的用户，下单后再退单也不算)
     */
    private String newUser;

    /**
     * 优惠券上架状态（0;下架;1:上架）
     */
    private String couponOnLineState;

}


--- File: play-admin/src/main/java/com/starry/admin/modules/shop/module/vo/PlayCouponDetailsUpdateUseStateVo.java ---
package com.starry.admin.modules.shop.module.vo;

import java.util.List;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Pattern;
import javax.validation.constraints.Size;
import lombok.Data;

/**
 * 更新优惠券使用状态
 *
 * @author admin
 * @since 2024/7/4 16:53
 **/
@Data
public class PlayCouponDetailsUpdateUseStateVo {

    /**
     * 优惠券ID
     **/
    @NotNull(message = "优惠券ID不能为空")
    @Size(min = 1, message = "优惠券ID不能为空")
    private List<String> couponIds;

    /**
     * 优惠券使用状态(1:未使用;2:已使用;3:已回收)
     */
    @NotNull(message = "优惠券使用状态不能为空")
    @Pattern(regexp = "[0|1]", message = "店员范围只能为0或者1")
    private String useState;

}


--- File: play-admin/src/main/java/com/starry/admin/modules/shop/module/vo/PlayCouponInfoAddVo.java ---
package com.starry.admin.modules.shop.module.vo;

import java.math.BigDecimal;
import java.util.List;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Pattern;
import javax.validation.constraints.Size;
import lombok.Data;

/**
 * 优惠券信息新增实体
 *
 * @author admin
 * @since 2024/7/4 15:26
 **/
@Data
public class PlayCouponInfoAddVo {

    /**
     * 优惠券名称
     */
    @NotNull(message = "优惠券名称不能为空")
    private String couponName;

    /**
     * 有效期类型（0:永久有效;1:固定时间内有效;2:领取后几天内有效）
     */
    @NotNull(message = "有效期类型不能为空")
    @Pattern(regexp = "[012]", message = "有效期类型只能为0或1或2")
    private String validityPeriodType;

    /**
     * 生效时间
     */
    private List<String> productiveTime;

    /**
     * 生效时长,validityPeriodType=2时有效
     **/
    private Integer effectiveDay;

    /**
     * 使用优惠券最近消费金额
     **/
    @NotNull(message = "使用优惠券最近消费金额不能为空")
    private BigDecimal useMinAmount = BigDecimal.ZERO;

    /**
     * 优惠类型（0:满减;1:折扣）
     */
    @NotNull(message = "优惠类型不能为空")
    @Pattern(regexp = "[01]", message = "优惠类型只能为0或1")
    private String discountType;

    /**
     * 优惠内容，文字展示
     */
    private String discountContent;

    /**
     * 折扣值
     */
    @NotNull(message = "优惠金额不能为空")
    private BigDecimal discountAmount;

    /**
     * 优惠金额是否由店铺承担（0:不是;1:是）
     */
    @NotNull(message = "优惠金额是否由店铺承担不能为空")
    @Pattern(regexp = "[01]", message = "优惠金额是否由店铺承担只能为0或1")
    private String attributionDiscounts;

    /**
     * 下单类型（0：指定单，1：随机单。2：打赏单）
     */
    @NotNull(message = "订单类型不能为空")
    @Size(min = 1, message = "下单类型不能为空")
    private List<String> placeType;

    /**
     * 店员范围（0:所有店员）
     */
    @NotNull(message = "店员范围不能为空")
    @Pattern(regexp = "[0]", message = "店员范围只能为0")
    private String clerkType;

    /**
     * 优惠券数量限制(0:不限制,1:限制)
     */
    @NotNull(message = "优惠券总数不能为空")
    @Pattern(regexp = "[01]", message = "优惠券数量限制只能为0或1")
    private String couponQuantityType;
    /**
     * 优惠券总数（-1表示不限制数量）
     */
    @NotNull(message = "优惠券总数不能为空")
    private Integer couponQuantity;

    /**
     * 已发放数量
     */
    private Integer issuedQuantity;

    /**
     * 剩余数量
     */
    private Integer remainingQuantity;

    /**
     * 店员获得最大数量是否限制
     */
    @NotNull(message = "店员获得最大数量是否限制不能为空")
    @Pattern(regexp = "[01]", message = "店员获得最大数量是否限制只能为0或1")
    private String clerkObtainedMaxQuantityType;

    /**
     * 店员获得最大数量
     */
    @NotNull(message = " 店员获得最大数量不能为空")
    private Integer clerkObtainedMaxQuantity;

    /**
     * 领取条件类型（0:所有人可领取,1:指定条件领取）
     */
    @NotNull(message = "领取条件类型不能为空")
    @Pattern(regexp = "[0|1]", message = "店员范围只能为0或者1")
    private String claimConditionType;

    /**
     * 顾客等级选择状态(0:未选择,1:选择)
     */
    private String customLevelCheckType;

    /**
     * 店员等级
     */
    private List<String> customLevel;

    /**
     * 用户关注公众号选择状态(0:未选择,1:选择)
     */
    private String customFollowStatusCheckType;

    /**
     * 顾客关注公众号状态(0:未关注,1:已关注)
     */
    private String customFollowStatus;

    /**
     * 顾客性别关注状态(0:未选择,1:选择)
     */
    private String customSexCheckType;
    /**
     * 顾客性别（0:未知;1:男,2:女）
     */
    private List<String> customSex;

    /**
     * 仅限新用户领取选择状态(0:未选择,1:已选择)
     */
    private String isNewUserCheckType;

    /**
     * 仅限新用户领取（0:不是;1:是）(没有下过单的用户，下单后再退单也不算)
     */
    private String newUser;
}


--- File: play-admin/src/main/java/com/starry/admin/modules/shop/module/vo/PlayCouponInfoOnLineStateUpdateVo.java ---
package com.starry.admin.modules.shop.module.vo;

import javax.validation.constraints.NotNull;
import javax.validation.constraints.Pattern;
import lombok.Data;

/**
 * 优惠券信息上架信息查询
 *
 * @author admin
 * @since 2024/7/4 15:26
 **/
@Data
public class PlayCouponInfoOnLineStateUpdateVo {

    /**
     * 优惠券ID
     */
    @NotNull(message = "优惠券ID不能为空")
    private String id;

    /**
     * 上下架状态不能为空
     */
    @NotNull(message = "上下架状态不能为空不能为空")
    @Pattern(regexp = "[0|1]", message = "上下架状态不能必须为0或1")
    private String couponOnLineState;

}


--- File: play-admin/src/main/java/com/starry/admin/modules/shop/module/vo/PlayCouponInfoQueryVo.java ---
package com.starry.admin.modules.shop.module.vo;

import com.starry.common.domain.BasePageEntity;
import lombok.Data;
import lombok.EqualsAndHashCode;

/**
 * 优惠券信息查询实体
 *
 * @author admin
 * @since 2024/7/4 15:26
 **/
@EqualsAndHashCode(callSuper = true)
@Data
public class PlayCouponInfoQueryVo extends BasePageEntity {

}


--- File: play-admin/src/main/java/com/starry/admin/modules/shop/module/vo/PlayCouponInfoReturnVo.java ---
package com.starry.admin.modules.shop.module.vo;

import com.baomidou.mybatisplus.annotation.TableField;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.starry.admin.common.conf.StringTypeHandler;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.List;
import lombok.Data;
import org.springframework.format.annotation.DateTimeFormat;

/**
 * 优惠券信息查询返回
 *
 * @author admin
 * @since 2024/7/4 15:26
 **/
@Data
public class PlayCouponInfoReturnVo {

    /**
     * UUID
     */
    private String id;

    /**
     * 优惠券名称
     **/
    private String couponName;

    /**
     * 有效期类型（0:永久有效;1:固定时间内有效;2:领取后几天内有效）
     */
    private String validityPeriodType;

    /**
     * 生效时间
     */
    @DateTimeFormat(pattern = "yyyy-MM-dd")
    @JsonFormat(pattern = "yyyy-MM-dd", timezone = "GMT+8")
    private LocalDateTime productiveTime;

    /**
     * 到期时间
     */
    @DateTimeFormat(pattern = "yyyy-MM-dd")
    @JsonFormat(pattern = "yyyy-MM-dd", timezone = "GMT+8")
    private LocalDateTime expirationTime;

    /**
     * 生效时长,validityPeriodType=2时有效
     **/
    private Integer effectiveDay;

    /**
     * 使用优惠券最近消费金额
     **/
    private BigDecimal useMinAmount;

    /**
     * 优惠类型（0:满减;1:折扣）
     */
    private String discountType;

    /**
     * 优惠内容，文字展示
     */
    private String discountContent;

    /**
     * 折扣值
     */
    private BigDecimal discountAmount;

    /**
     * 优惠金额是否由店铺承担（0:不是;1:是）
     */
    private String attributionDiscounts;

    /**
     * 下单类型（0：指定单，1：随机单。2：打赏单）
     */
    @TableField(typeHandler = StringTypeHandler.class)
    private List<String> placeType;

    /**
     * 店员范围（0:所有店员）
     */
    private String clerkType;

    /**
     * 优惠券总数
     */
    private Integer couponQuantity;

    /**
     * 已发放数量
     */
    private Integer issuedQuantity;

    /**
     * 剩余数量
     */
    private Integer remainingQuantity;

    /**
     * 店员获得最大数量
     */
    private Integer clerkObtainedMaxQuantity;

    /**
     * 领取条件类型（0:所有人可领取,1:指定条件领取）
     */
    private String claimConditionType;

    /**
     * 顾客等级选择状态(0:未选择,1:选择)
     */
    private String customLevelCheckType;

    /**
     * 店员等级
     */
    @TableField(typeHandler = StringTypeHandler.class)
    private List<String> customLevel;

    /**
     * 用户关注公众号选择状态(0:未选择,1:选择)
     */
    private String customFollowStatusCheckType;

    /**
     * 顾客关注公众号状态(0:未关注,1:已关注)
     */
    private String customFollowStatus;

    /**
     * 顾客性别选择状态(0:未选择,1:选择)
     */
    private String customSexCheckType;
    /**
     * 顾客性别（0:未知;1:男,2:女）
     */
    @TableField(typeHandler = StringTypeHandler.class)
    private List<String> customSex;

    /**
     * 仅限新用户领取（0:不是;1:是）(没有下过单的用户，下单后再退单也不算)
     */
    private String newUser;

    /**
     * 优惠券上架状态（0;下架;1:上架）
     */
    private String couponOnLineState;

}


--- File: play-admin/src/main/java/com/starry/admin/modules/shop/module/vo/PlayCouponInfoSendVo.java ---
package com.starry.admin.modules.shop.module.vo;

import javax.validation.constraints.Max;
import javax.validation.constraints.Min;
import javax.validation.constraints.NotNull;
import lombok.Data;

/**
 * 优惠券信息上架信息查询
 *
 * @author admin
 * @since 2024/7/4 15:26
 **/
@Data
public class PlayCouponInfoSendVo {

    /**
     * 优惠券ID
     */
    @NotNull(message = "优惠券ID不能为空")
    private String id;

    /**
     * 用户ID
     */
    @NotNull(message = "用户ID不能为空")
    private String customId;

    /**
     * 优惠券数量不能为空
     */
    @Max(value = 1, message = "优惠券数量必须小于1")
    @Min(value = 1, message = "优惠券数量必须大于1")
    private Integer sendNumber;

}


--- File: play-admin/src/main/java/com/starry/admin/modules/shop/module/vo/PlayShopArticleInfoAddVo.java ---
package com.starry.admin.modules.shop.module.vo;

import javax.validation.constraints.NotNull;
import lombok.Data;

/**
 * 店铺文章信息新增
 *
 * @author admin
 * @since 2024/6/5 上午10:36
 **/
@Data
public class PlayShopArticleInfoAddVo {

    private String id;
    /**
     * 文章类型
     **/
    @NotNull(message = "文章类型不能为空")
    private String articleType;

    /**
     * 文章标题
     **/
    @NotNull(message = "文章标题不能为空")
    private String articleTitle;

    /**
     * 文章内容
     **/
    @NotNull(message = "文章内容不能为空")
    private String articleContent;

    /**
     * 浏览量
     **/
    private Integer visitsNumber = 0;

    /**
     * 排序
     */
    private Integer sort = 0;
}


--- File: play-admin/src/main/java/com/starry/admin/modules/shop/module/vo/PlayShopArticleInfoQueryVo.java ---
package com.starry.admin.modules.shop.module.vo;

import com.starry.common.domain.BasePageEntity;
import lombok.Data;
import lombok.EqualsAndHashCode;

/**
 * 店铺文章查询对象
 *
 * @author admin
 * @since 2024/6/5 上午10:36
 **/
@EqualsAndHashCode(callSuper = true)
@Data
public class PlayShopArticleInfoQueryVo extends BasePageEntity {
    /**
     * 文章ID
     **/
    private String id;

    /**
     * 文章类型
     */
    private String articleType;

    /**
     * 文章标题
     */
    private String articleTitle;
}


--- File: play-admin/src/main/java/com/starry/admin/modules/shop/module/vo/PlayShopArticleInfoUpdateVo.java ---
package com.starry.admin.modules.shop.module.vo;

import javax.validation.constraints.NotNull;
import lombok.Data;

/**
 * 店铺文章修改实体
 *
 * @author admin
 * @since 2024/6/5 上午10:36
 **/
@Data
public class PlayShopArticleInfoUpdateVo {

    @NotNull(message = "ID不能为空")
    private String id;

    /**
     * 文章类型
     *
     **/
    private String articleType;

    /**
     * 文章标题
     *
     **/
    private String articleTitle;

    /**
     * 文章内容
     *
     **/
    private String articleContent;

    /**
     * 浏览量
     *
     **/
    private Integer visitsNumber;

    /**
     * 排序
     *
     **/
    private Integer sort;
}


--- File: play-admin/src/main/java/com/starry/admin/modules/shop/module/vo/PlayShopCarouselInfoAddVo.java ---
package com.starry.admin.modules.shop.module.vo;

import com.fasterxml.jackson.annotation.JsonFormat;
import java.time.LocalDateTime;
import javax.validation.constraints.NotNull;
import lombok.Data;
import org.springframework.format.annotation.DateTimeFormat;

/**
 * @author admin
 * @since 2024/6/5 上午10:36
 **/
@Data
public class PlayShopCarouselInfoAddVo {

    /**
     * 轮播图位置(0:首页)
     */
    @NotNull(message = "轮播图位置不能为空")
    private String carouselIndex;

    /**
     * 启用状态（0:未启用,1:已启用）
     */
    @NotNull(message = "启用状态不能为空")
    private String enableState;

    /**
     * 图片URL
     */
    @NotNull(message = "图片URL不能为空")
    private String carouselUrl;

    /**
     * 排序
     */
    private Integer sort;

    /**
     * 创建时间
     */
    @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
    private LocalDateTime addTime = LocalDateTime.now();
}


--- File: play-admin/src/main/java/com/starry/admin/modules/shop/module/vo/PlayShopCarouselInfoQueryVo.java ---
package com.starry.admin.modules.shop.module.vo;

import com.starry.common.domain.BasePageEntity;
import java.util.List;
import lombok.Data;
import lombok.EqualsAndHashCode;

/**
 * @author admin
 * @since 2024/6/5 上午10:36
 **/
@EqualsAndHashCode(callSuper = true)
@Data
public class PlayShopCarouselInfoQueryVo extends BasePageEntity {

    /**
     * 轮播图位置(0:首页)
     */
    private String carouselIndex;

    /**
     * 启用状态（0:未启用,1:已启用）
     */
    private String enableState;

    /**
     * 创建时间
     */
    private List<String> addTime;
}


--- File: play-admin/src/main/java/com/starry/admin/modules/shop/module/vo/PlayShopCarouselInfoReturnVo.java ---
package com.starry.admin.modules.shop.module.vo;

import com.starry.common.domain.BasePageEntity;
import lombok.Data;
import lombok.EqualsAndHashCode;

/**
 * @author admin
 * @since 2024/6/5 上午10:36
 **/
@EqualsAndHashCode(callSuper = true)
@Data
public class PlayShopCarouselInfoReturnVo extends BasePageEntity {
}


--- File: play-admin/src/main/java/com/starry/admin/modules/shop/module/vo/PlayShopCarouselInfoUpdateStateVo.java ---
package com.starry.admin.modules.shop.module.vo;

import javax.validation.constraints.NotNull;
import lombok.Data;

/**
 * @author admin
 * @since 2024/6/5 上午10:36
 **/
@Data
public class PlayShopCarouselInfoUpdateStateVo {

    @NotNull(message = "ID不能为空")
    private String id;

    /**
     * 启用状态（0:未启用,1:已启用）
     */
    @NotNull(message = "enableState不能为空")
    private String enableState;
}


--- File: play-admin/src/main/java/com/starry/admin/modules/shop/service/IPlayClerkGiftInfoService.java ---
package com.starry.admin.modules.shop.service;

import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.service.IService;
import com.starry.admin.modules.shop.module.entity.PlayClerkGiftInfoEntity;
import java.util.List;

/**
 * 店员和礼物关系Service接口
 *
 * @author admin
 * @since 2024-05-25
 */
public interface IPlayClerkGiftInfoService extends IService<PlayClerkGiftInfoEntity> {

    /**
     * 根据顾客ID和礼物ID，查询当前礼物活点亮情况
     *
     * @param giftId
     *            礼物ID
     * @param clerkId
     *            店员ID
     * @return 物活点亮情况
     */
    PlayClerkGiftInfoEntity selectByGiftIdAndClerkId(String giftId, String clerkId);

    /**
     * 根据店员ID查询店员活动礼物列表
     *
     * @param clerkId
     *            店员ID
     * @return 店员活动礼物列表
     */

    List<PlayClerkGiftInfoEntity> selectBtyClerkId(String clerkId);

    /**
     * 查询店员和礼物关系
     *
     * @param id
     *            店员和礼物关系主键
     * @return 店员和礼物关系
     */
    PlayClerkGiftInfoEntity selectPlayClerkGiftInfoById(String id);

    /**
     * 查询店员和礼物关系列表
     *
     * @param playClerkGiftInfo
     *            店员和礼物关系
     * @return 店员和礼物关系集合
     */
    IPage<PlayClerkGiftInfoEntity> selectPlayClerkGiftInfoByPage(PlayClerkGiftInfoEntity playClerkGiftInfo);

    /**
     * 新增店员和礼物关系
     *
     * @param playClerkGiftInfo
     *            店员和礼物关系
     * @return 结果
     */
    boolean create(PlayClerkGiftInfoEntity playClerkGiftInfo);

    /**
     * 修改店员和礼物关系
     *
     * @param playClerkGiftInfo
     *            店员和礼物关系
     * @return 结果
     */
    boolean update(PlayClerkGiftInfoEntity playClerkGiftInfo);

    /**
     * 批量删除店员和礼物关系
     *
     * @param ids
     *            需要删除的店员和礼物关系主键集合
     * @return 结果
     */
    int deletePlayClerkGiftInfoByIds(String[] ids);

    /**
     * 删除店员和礼物关系信息
     *
     * @param id
     *            店员和礼物关系主键
     * @return 结果
     */
    int deletePlayClerkGiftInfoById(String id);
}


--- File: play-admin/src/main/java/com/starry/admin/modules/shop/service/IPlayCommodityAndLevelInfoService.java ---
package com.starry.admin.modules.shop.service;

import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.service.IService;
import com.starry.admin.modules.shop.module.entity.PlayCommodityAndLevelInfoEntity;
import java.util.List;

/**
 * 服务项目和店员等级数据Service接口
 *
 * @author admin
 * @since 2024-08-16
 */
public interface IPlayCommodityAndLevelInfoService extends IService<PlayCommodityAndLevelInfoEntity> {

    PlayCommodityAndLevelInfoEntity queryById(String commodityId, String levelId);
    /**
     * 查询服务项目和店员等级数据
     *
     * @return 服务项目和店员等级数据
     */
    List<PlayCommodityAndLevelInfoEntity> selectAll();

    /**
     * 查询服务项目和店员等级数据
     *
     * @param id
     *            服务项目和店员等级数据主键
     * @return 服务项目和店员等级数据
     */
    PlayCommodityAndLevelInfoEntity selectPlayCommodityAndLevelInfoById(String id);

    /**
     * 查询服务项目和店员等级数据列表
     *
     * @param playCommodityAndLevelInfo
     *            服务项目和店员等级数据
     * @return 服务项目和店员等级数据集合
     */
    IPage<PlayCommodityAndLevelInfoEntity> selectPlayCommodityAndLevelInfoByPage(
            PlayCommodityAndLevelInfoEntity playCommodityAndLevelInfo);

    /**
     * 新增服务项目和店员等级数据
     *
     * @param playCommodityAndLevelInfo
     *            服务项目和店员等级数据
     * @return 结果
     */
    boolean create(PlayCommodityAndLevelInfoEntity playCommodityAndLevelInfo);

    /**
     * 修改服务项目和店员等级数据
     *
     * @param playCommodityAndLevelInfo
     *            服务项目和店员等级数据
     * @return 结果
     */
    boolean update(PlayCommodityAndLevelInfoEntity playCommodityAndLevelInfo);

    /**
     * 批量删除服务项目和店员等级数据
     *
     * @param ids
     *            需要删除的服务项目和店员等级数据主键集合
     * @return 结果
     */
    int deletePlayCommodityAndLevelInfoByIds(String[] ids);

    /**
     * 删除服务项目和店员等级数据信息
     *
     * @param id
     *            服务项目和店员等级数据主键
     * @return 结果
     */
    int deletePlayCommodityAndLevelInfoById(String id);
}


--- File: play-admin/src/main/java/com/starry/admin/modules/shop/service/IPlayCommodityInfoService.java ---
package com.starry.admin.modules.shop.service;

import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.service.IService;
import com.starry.admin.modules.shop.module.entity.PlayCommodityInfoEntity;
import com.starry.admin.modules.shop.module.vo.PlayCommodityInfoVo;
import com.starry.admin.modules.weichat.entity.PlayCommodityReturnVo;
import java.util.List;

/**
 * 服务项目Service接口
 *
 * @author admin
 * @since 2024-03-31
 */
public interface IPlayCommodityInfoService extends IService<PlayCommodityInfoEntity> {

    /**
     * * 新增租户时 * 初始化租户服务项目
     *
     * @param tenantId
     *            租户ID
     */
    void initPlayCommodityInfo(String tenantId);

    /**
     * 查询服务项目
     *
     * @param id
     *            服务项目ID
     * @param levelId
     *            店员等级ID
     * @return PlayCommodityInfoVo
     * @author admin
     **/
    PlayCommodityInfoVo queryCommodityInfo(String id, String levelId);

    // /**
    // * 查询服务项目
    // *
    // * @param id UUID
    // * @return 服务项目
    // */
    // PlayCommodityInfoVo queryCommodityInfo(String id);

    /**
     * 查询服务项目
     *
     * @param id
     *            服务项目主键
     * @return 服务项目
     */
    PlayCommodityInfoEntity selectPlayCommodityInfoById(String id);

    /**
     * 获取店铺服务项目
     *
     * @return List<PlayCommodityReturnVo>
     **/
    List<PlayCommodityReturnVo> selectTree();

    /**
     * 查询当前租户所有服务项目
     *
     * @return 当前租户所有服务项目列表
     */
    List<PlayCommodityInfoEntity> selectByType();

    /**
     * 查询当前租户所有服务项目列表
     *
     * @return 当前租户所有服务项目列表
     */
    List<PlayCommodityInfoEntity> selectAll();

    /**
     * 查询当前租户所有服务项目列表
     *
     * @return 当前租户所有服务项目列表
     */
    List<PlayCommodityInfoEntity> selectByPId(String pId);

    /**
     * 查询服务项目列表
     *
     * @param playCommodityInfo
     *            服务项目
     * @return 服务项目集合
     */
    IPage<PlayCommodityInfoEntity> selectPlayCommodityInfoByPage(PlayCommodityInfoEntity playCommodityInfo);

    /**
     * 新增服务项目
     *
     * @param playCommodityInfo
     *            服务项目
     * @return 结果
     */
    boolean create(PlayCommodityInfoEntity playCommodityInfo);

    /**
     * 修改服务项目
     *
     * @param playCommodityInfo
     *            服务项目
     * @return 结果
     */
    boolean update(PlayCommodityInfoEntity playCommodityInfo);

    /**
     * 批量删除服务项目
     *
     * @param ids
     *            需要删除的服务项目主键集合
     * @return 结果
     */
    int deletePlayCommodityInfoByIds(String[] ids);

    /**
     * 删除服务项目信息
     *
     * @param id
     *            服务项目主键
     * @return 结果
     */
    int deletePlayCommodityInfoById(String id);
}


--- File: play-admin/src/main/java/com/starry/admin/modules/shop/service/IPlayCouponDetailsService.java ---
package com.starry.admin.modules.shop.service;

import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.service.IService;
import com.starry.admin.modules.shop.module.entity.PlayCouponDetailsEntity;
import com.starry.admin.modules.shop.module.vo.PlayCouponDetailsQueryVo;
import com.starry.admin.modules.shop.module.vo.PlayCouponDetailsReturnVo;
import java.util.List;

/**
 * 优惠券详情Service接口
 *
 * @author admin
 * @since 2024-07-04
 */
public interface IPlayCouponDetailsService extends IService<PlayCouponDetailsEntity> {
    /**
     * 查询优惠券详情
     *
     * @param id
     *            优惠券详情主键
     * @return 优惠券详情
     */
    PlayCouponDetailsReturnVo selectPlayCouponDetailsById(String id);

    /**
     * 查询优惠券详情列表
     *
     * @param vo
     *            优惠券详情
     * @return 优惠券详情集合
     */
    IPage<PlayCouponDetailsReturnVo> selectByPage(PlayCouponDetailsQueryVo vo);

    /**
     * 查询优惠券详情列表
     *
     * @param playCouponDetails
     *            优惠券详情
     * @return 优惠券详情集合
     */
    IPage<PlayCouponDetailsEntity> selectPlayCouponDetailsByPage(PlayCouponDetailsEntity playCouponDetails);

    /**
     * 根据顾客ID，查询当前顾客优惠券列表
     *
     * @param customId
     *            顾客ID
     * @return List<com.starry.admin.modules.shop.module.entity.PlayCouponDetailsEntity>
     * @author admin
     * @since 2024/7/5 11:37
     **/
    List<PlayCouponDetailsReturnVo> selectByCustomId(String customId);

    /**
     * 新建优惠券使用详情
     *
     * @param customId
     *            用户ID
     * @param customNickName
     *            用户昵称
     * @param customLevel
     *            用户等级
     * @param couponId
     *            优惠券ID
     * @param obtainingChannels
     *            获得渠道（1:自主领取;2:后台发放;3:退款返还;4:分享领取;5:抽奖获得）
     * @param useState
     *            优惠券使用状态(1:未使用;2:已使用;3:已回收)
     * @author admin
     * @since 2024/7/8 16:21
     **/
    void create(String customId, String customNickName, String customLevel, String couponId, String obtainingChannels,
            String useState);

    /**
     * 新增优惠券详情
     *
     * @param playCouponDetails
     *            优惠券详情
     * @return 结果
     */
    boolean create(PlayCouponDetailsEntity playCouponDetails);

    /**
     * 修改优惠券详情
     *
     * @param playCouponDetails
     *            优惠券详情
     * @return 结果
     */
    boolean update(PlayCouponDetailsEntity playCouponDetails);

    /**
     * 批量修改优惠券使用状态 优惠券使用状态(1:未使用;2:已使用;3:已回收)
     *
     * @param ids
     *            需要删除的优惠券详情主键集合
     * @param useState
     *            优惠券使用状态(1 : 未使用 ; 2 : 已使用 ; 3 : 已回收)
     */
    void updateCouponUseStateByIds(List<String> ids, String useState);

    /**
     * 批量删除优惠券详情
     *
     * @param ids
     *            需要删除的优惠券详情主键集合
     * @return 结果
     */
    int deletePlayCouponDetailsByIds(String[] ids);

    /**
     * 删除优惠券详情信息
     *
     * @param id
     *            优惠券详情主键
     * @return 结果
     */
    int deletePlayCouponDetailsById(String id);
}


--- File: play-admin/src/main/java/com/starry/admin/modules/shop/service/IPlayCouponInfoService.java ---
package com.starry.admin.modules.shop.service;

import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.service.IService;
import com.starry.admin.modules.custom.module.entity.PlayCustomUserInfoEntity;
import com.starry.admin.modules.shop.module.entity.PlayCouponInfoEntity;
import com.starry.admin.modules.shop.module.vo.PlayCouponInfoQueryVo;
import com.starry.admin.modules.shop.module.vo.PlayCouponInfoReturnVo;
import java.math.BigDecimal;
import java.util.List;

/**
 * 优惠券信息Service接口
 *
 * @author admin
 * @since 2024-07-04
 */
public interface IPlayCouponInfoService extends IService<PlayCouponInfoEntity> {

    /**
     * 获取优惠券不可用的原因
     *
     * @param couponInfo
     *            优惠券信息
     * @param placeType
     *            下单类型
     * @param commodityId
     *            商品ID
     * @param commodityQuantity
     *            商品数量
     * @param price
     *            商品单价
     * @return 不可用原因
     */
    String getCouponReasonForUnavailableUse(PlayCouponInfoEntity couponInfo, String placeType, String commodityId,
            Integer commodityQuantity, BigDecimal price);

    /**
     * 获取优惠券不可领取的原因
     *
     * @param entity
     *            优惠券实体
     * @param customUserInfo
     *            顾客实体
     * @return String
     * @author admin
     * @since 2024/7/8 14:44
     **/
    String getReasonForNotObtainingCoupons(PlayCouponInfoEntity entity, PlayCustomUserInfoEntity customUserInfo);

    /**
     * 查询优惠券信息
     *
     * @param id
     *            优惠券信息主键
     * @return 优惠券信息
     */
    PlayCouponInfoEntity selectPlayCouponInfoById(String id);

    /**
     * 查询优惠券信息列表
     *
     * @param vo
     *            优惠券分页查询实体
     * @return 优惠券信息集合
     */
    IPage<PlayCouponInfoReturnVo> selectByPage(PlayCouponInfoQueryVo vo);

    /**
     * 查询优惠券信息列表
     *
     * @return 优惠券信息集合
     */
    List<PlayCouponInfoEntity> queryAll();

    /**
     * 查询优惠券信息列表
     *
     * @param playCouponInfo
     *            优惠券信息
     * @return 优惠券信息集合
     */
    IPage<PlayCouponInfoEntity> selectPlayCouponInfoByPage(PlayCouponInfoEntity playCouponInfo);

    /**
     * 新增优惠券信息
     *
     * @param playCouponInfo
     *            优惠券信息
     * @return 结果
     */
    boolean create(PlayCouponInfoEntity playCouponInfo);

    /**
     * 修改优惠券信息
     *
     * @param playCouponInfo
     *            优惠券信息
     * @return 结果
     */
    boolean update(PlayCouponInfoEntity playCouponInfo);

    /**
     * 批量删除优惠券信息
     *
     * @param ids
     *            需要删除的优惠券信息主键集合
     * @return 结果
     */
    int deletePlayCouponInfoByIds(String[] ids);

    /**
     * 删除优惠券信息信息
     *
     * @param id
     *            优惠券信息主键
     * @return 结果
     */
    int deletePlayCouponInfoById(String id);
}


--- File: play-admin/src/main/java/com/starry/admin/modules/shop/service/IPlayGiftInfoService.java ---
package com.starry.admin.modules.shop.service;

import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.service.IService;
import com.starry.admin.modules.shop.module.entity.PlayGiftInfoEntity;
import com.starry.admin.modules.weichat.entity.gift.PlayClerkGiftReturnVo;
import java.util.List;

/**
 * 礼物Service接口
 *
 * @author admin
 * @since 2024-04-25
 */
public interface IPlayGiftInfoService extends IService<PlayGiftInfoEntity> {
    /**
     * 查询礼物
     *
     * @param id
     *            礼物主键
     * @return 礼物
     */
    PlayGiftInfoEntity selectPlayGiftInfoById(String id);

    /**
     * 查询所有礼物
     *
     * @return List<com.starry.admin.modules.gift.module.entity.PlayGiftInfoEntity>
     * @author admin
     * @since 2024/4/25 15:56
     **/
    List<PlayGiftInfoEntity> listByAll();

    /**
     * 店员查询所有礼物
     *
     * @param clerkId
     *            店员ID
     * @param obtained
     *            店员获得礼物状态,[0:未获得,1:已获得]
     * @return List<com.starry.admin.modules.gift.module.entity.PlayGiftInfoEntity>
     * @author admin
     * @since 2024/4/25 15:56
     **/
    List<PlayClerkGiftReturnVo> clerkListByAll(String clerkId, String obtained);

    /**
     * 店员查询所有礼物
     *
     * @param customId
     *            顾客ID
     * @param obtained
     *            礼物状态,[0:未获得,1:已获得]
     * @param history
     *            是否是历史礼物(0:不是,1:是)
     * @return List<com.starry.admin.modules.gift.module.entity.PlayGiftInfoEntity>
     * @author admin
     * @since 2024/4/25 15:56
     **/
    List<PlayClerkGiftReturnVo> customListByAll(String customId, String obtained);

    /**
     * 查询礼物列表
     *
     * @param playGiftInfo
     *            礼物
     * @return 礼物集合
     */
    IPage<PlayGiftInfoEntity> selectPlayGiftInfoByPage(PlayGiftInfoEntity playGiftInfo);

    /**
     * 新增礼物
     *
     * @param playGiftInfo
     *            礼物
     * @return 结果
     */
    boolean create(PlayGiftInfoEntity playGiftInfo);

    /**
     * 修改礼物
     *
     * @param playGiftInfo
     *            礼物
     * @return 结果
     */
    boolean update(PlayGiftInfoEntity playGiftInfo);

    /**
     * 批量删除礼物
     *
     * @param ids
     *            需要删除的礼物主键集合
     * @return 结果
     */
    int deletePlayGiftInfoByIds(String[] ids);

    /**
     * 删除礼物信息
     *
     * @param id礼物主键
     * @return 结果
     */
    int deletePlayGiftInfoById(String id);
}


--- File: play-admin/src/main/java/com/starry/admin/modules/shop/service/IPlayShopArticleInfoService.java ---
package com.starry.admin.modules.shop.service;

import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.service.IService;
import com.starry.admin.modules.shop.module.entity.PlayShopArticleInfoEntity;
import com.starry.admin.modules.shop.module.vo.PlayShopArticleInfoQueryVo;
import java.util.List;

/**
 * 店铺文章信息Service接口
 *
 * @author admin
 * @since 2024-06-05
 */
public interface IPlayShopArticleInfoService extends IService<PlayShopArticleInfoEntity> {
    /**
     * 查询店铺文章信息
     *
     * @param id
     *            店铺文章信息主键
     * @return 店铺文章信息
     */
    PlayShopArticleInfoEntity selectById(String id);

    /**
     * 根据文章类型查询店铺文章
     *
     * @param type
     *            店铺文章类型
     * @return 店铺文章信息
     */
    PlayShopArticleInfoEntity selectByType(String type);
    /**
     * 查询店铺文章信息列表
     *
     * @param vo
     *            店铺文章信息查询对象
     * @return 店铺文章信息集合
     */
    IPage<PlayShopArticleInfoEntity> selectByPage(PlayShopArticleInfoQueryVo vo);

    /**
     * 查询店铺文章信息列表
     *
     * @return 店铺文章信息集合
     */
    List<PlayShopArticleInfoEntity> selectByList();

    /**
     * 新增店铺文章信息
     *
     * @param articleInfoEntity
     *            店铺文章信息
     * @return 结果
     */
    boolean create(PlayShopArticleInfoEntity articleInfoEntity);

    /**
     * 修改店铺文章信息
     *
     * @param articleInfoEntity
     *            店铺文章信息
     * @return 结果
     */
    boolean update(PlayShopArticleInfoEntity articleInfoEntity);

    /**
     * 批量删除店铺文章信息
     *
     * @param ids
     *            需要删除的店铺文章信息主键集合
     * @return 结果
     */
    int deleteByIds(String[] ids);

    /**
     * 删除店铺文章信息信息
     *
     * @param id
     *            店铺文章信息主键
     * @return 结果
     */
    int deleteById(String id);
}


--- File: play-admin/src/main/java/com/starry/admin/modules/shop/service/IPlayShopCarouselInfoService.java ---
package com.starry.admin.modules.shop.service;

import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.service.IService;
import com.starry.admin.modules.shop.module.entity.PlayShopCarouselInfoEntity;
import com.starry.admin.modules.shop.module.vo.PlayShopCarouselInfoQueryVo;
import java.util.List;

/**
 * 店铺首页轮播Service接口
 *
 * @author admin
 * @since 2024-06-05
 */
public interface IPlayShopCarouselInfoService extends IService<PlayShopCarouselInfoEntity> {
    /**
     * 查询店铺首页轮播
     *
     * @param id
     *            店铺首页轮播主键
     * @return 店铺首页轮播
     */
    PlayShopCarouselInfoEntity selectPlayCarouselInfoById(String id);

    /**
     * 查询店铺首页轮播列表
     *
     * @param vo
     *            店铺首页轮播查询对象
     * @return 店铺首页轮播集合
     */
    IPage<PlayShopCarouselInfoEntity> selectByPage(PlayShopCarouselInfoQueryVo vo);

    /**
     * 查询店铺首页轮播列表
     *
     * @return 店铺首页轮播集合
     */
    List<PlayShopCarouselInfoEntity> selectHomeCarouselInfo();

    /**
     * 新增店铺首页轮播
     *
     * @param playCarouselInfo
     *            店铺首页轮播
     * @return 结果
     */
    boolean create(PlayShopCarouselInfoEntity playCarouselInfo);

    /**
     * 修改店铺首页轮播
     *
     * @param playCarouselInfo
     *            店铺首页轮播
     * @return 结果
     */
    boolean update(PlayShopCarouselInfoEntity playCarouselInfo);

    /**
     * 批量删除店铺首页轮播
     *
     * @param ids
     *            需要删除的店铺首页轮播主键集合
     * @return 结果
     */
    int deletePlayCarouselInfoByIds(String[] ids);

    /**
     * 删除店铺首页轮播信息
     *
     * @param id
     *            店铺首页轮播主键
     * @return 结果
     */
    int deletePlayCarouselInfoById(String id);
}


--- File: play-admin/src/main/java/com/starry/admin/modules/shop/service/IShopUiSettingService.java ---
package com.starry.admin.modules.shop.service;

import com.baomidou.mybatisplus.extension.service.IService;
import com.starry.admin.modules.shop.module.entity.ShopUiSettingEntity;

/**
 * 店铺设置Service接口
 *
 * @author admin
 * @since 2025-06-11
 */
public interface IShopUiSettingService extends IService<ShopUiSettingEntity> {
    /**
     * 查询店铺设置
     *
     * @return 店铺设置
     */
    ShopUiSettingEntity selectShopUiSettingInfo();

    /**
     * 修改店铺设置
     *
     * @param shopUiSetting
     *            店铺设置
     * @return 结果
     */
    boolean update(ShopUiSettingEntity shopUiSetting);

}


--- File: play-admin/src/main/java/com/starry/admin/modules/shop/service/impl/PlayClerkGiftInfoServiceImpl.java ---
package com.starry.admin.modules.shop.service.impl;

import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.starry.admin.modules.shop.mapper.PlayClerkGiftInfoMapper;
import com.starry.admin.modules.shop.module.entity.PlayClerkGiftInfoEntity;
import com.starry.admin.modules.shop.service.IPlayClerkGiftInfoService;
import com.starry.common.utils.IdUtils;
import java.util.Arrays;
import java.util.List;
import javax.annotation.Resource;
import org.springframework.stereotype.Service;

/**
 * 店员和礼物关系Service业务层处理
 *
 * @author admin
 * @since 2024-05-25
 */
@Service
public class PlayClerkGiftInfoServiceImpl extends ServiceImpl<PlayClerkGiftInfoMapper, PlayClerkGiftInfoEntity>
        implements
            IPlayClerkGiftInfoService {
    @Resource
    private PlayClerkGiftInfoMapper playClerkGiftInfoMapper;

    @Override
    public PlayClerkGiftInfoEntity selectByGiftIdAndClerkId(String giftId, String clerkId) {
        LambdaQueryWrapper<PlayClerkGiftInfoEntity> lambdaQueryWrapper = new LambdaQueryWrapper<>();
        lambdaQueryWrapper.eq(PlayClerkGiftInfoEntity::getClerkId, clerkId);
        lambdaQueryWrapper.eq(PlayClerkGiftInfoEntity::getGiffId, giftId);
        return this.baseMapper.selectOne(lambdaQueryWrapper);
    }

    /**
     * 根据店员ID查询店员活动礼物列表
     *
     * @param clerkId
     *            店员ID
     * @return 店员活动礼物列表
     */
    @Override
    public List<PlayClerkGiftInfoEntity> selectBtyClerkId(String clerkId) {
        LambdaQueryWrapper<PlayClerkGiftInfoEntity> lambdaQueryWrapper = new LambdaQueryWrapper<>();
        lambdaQueryWrapper.eq(PlayClerkGiftInfoEntity::getClerkId, clerkId);
        return this.baseMapper.selectList(lambdaQueryWrapper);
    }

    /**
     * 查询店员和礼物关系
     *
     * @param id
     *            店员和礼物关系主键
     * @return 店员和礼物关系
     */
    @Override
    public PlayClerkGiftInfoEntity selectPlayClerkGiftInfoById(String id) {
        return this.baseMapper.selectById(id);
    }

    /**
     * 查询店员和礼物关系列表
     *
     * @param playClerkGiftInfo
     *            店员和礼物关系
     * @return 店员和礼物关系
     */
    @Override
    public IPage<PlayClerkGiftInfoEntity> selectPlayClerkGiftInfoByPage(PlayClerkGiftInfoEntity playClerkGiftInfo) {
        Page<PlayClerkGiftInfoEntity> page = new Page<>(1, 10);
        return this.baseMapper.selectPage(page, new LambdaQueryWrapper<>());
    }

    /**
     * 新增店员和礼物关系
     *
     * @param playClerkGiftInfo
     *            店员和礼物关系
     * @return 结果
     */
    @Override
    public boolean create(PlayClerkGiftInfoEntity playClerkGiftInfo) {
        if (StrUtil.isBlankIfStr(playClerkGiftInfo.getId())) {
            playClerkGiftInfo.setId(IdUtils.getUuid());
        }
        return save(playClerkGiftInfo);
    }

    /**
     * 修改店员和礼物关系
     *
     * @param playClerkGiftInfo
     *            店员和礼物关系
     * @return 结果
     */
    @Override
    public boolean update(PlayClerkGiftInfoEntity playClerkGiftInfo) {
        return updateById(playClerkGiftInfo);
    }

    /**
     * 批量删除店员和礼物关系
     *
     * @param ids
     *            需要删除的店员和礼物关系主键
     * @return 结果
     */
    @Override
    public int deletePlayClerkGiftInfoByIds(String[] ids) {
        return playClerkGiftInfoMapper.deleteBatchIds(Arrays.asList(ids));
    }

    /**
     * 删除店员和礼物关系信息
     *
     * @param id
     *            店员和礼物关系主键
     * @return 结果
     */
    @Override
    public int deletePlayClerkGiftInfoById(String id) {
        return playClerkGiftInfoMapper.deleteById(id);
    }
}


--- File: play-admin/src/main/java/com/starry/admin/modules/shop/service/impl/PlayCommodityAndLevelInfoServiceImpl.java ---
package com.starry.admin.modules.shop.service.impl;

import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.starry.admin.modules.shop.mapper.PlayCommodityAndLevelInfoMapper;
import com.starry.admin.modules.shop.module.entity.PlayCommodityAndLevelInfoEntity;
import com.starry.admin.modules.shop.service.IPlayCommodityAndLevelInfoService;
import com.starry.common.utils.IdUtils;
import java.util.Arrays;
import java.util.List;
import javax.annotation.Resource;
import org.springframework.stereotype.Service;

/**
 * 服务项目和店员等级数据Service业务层处理
 *
 * @author admin
 * @since 2024-08-16
 */
@Service
public class PlayCommodityAndLevelInfoServiceImpl
        extends
            ServiceImpl<PlayCommodityAndLevelInfoMapper, PlayCommodityAndLevelInfoEntity>
        implements
            IPlayCommodityAndLevelInfoService {
    @Resource
    private PlayCommodityAndLevelInfoMapper playCommodityAndLevelInfoMapper;

    @Override
    public PlayCommodityAndLevelInfoEntity queryById(String commodityId, String levelId) {
        LambdaQueryWrapper<PlayCommodityAndLevelInfoEntity> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(PlayCommodityAndLevelInfoEntity::getCommodityId, commodityId);
        queryWrapper.eq(PlayCommodityAndLevelInfoEntity::getLevelId, levelId);
        return playCommodityAndLevelInfoMapper.selectOne(queryWrapper);
    }

    @Override
    public List<PlayCommodityAndLevelInfoEntity> selectAll() {
        return this.baseMapper.selectList(new LambdaQueryWrapper<>());
    }

    /**
     * 查询服务项目和店员等级数据
     *
     * @param id
     *            服务项目和店员等级数据主键
     * @return 服务项目和店员等级数据
     */
    @Override
    public PlayCommodityAndLevelInfoEntity selectPlayCommodityAndLevelInfoById(String id) {
        return this.baseMapper.selectById(id);
    }

    /**
     * 查询服务项目和店员等级数据列表
     *
     * @param playCommodityAndLevelInfo
     *            服务项目和店员等级数据
     * @return 服务项目和店员等级数据
     */
    @Override
    public IPage<PlayCommodityAndLevelInfoEntity> selectPlayCommodityAndLevelInfoByPage(
            PlayCommodityAndLevelInfoEntity playCommodityAndLevelInfo) {
        Page<PlayCommodityAndLevelInfoEntity> page = new Page<>(1, 10);
        return this.baseMapper.selectPage(page, new LambdaQueryWrapper<PlayCommodityAndLevelInfoEntity>());
    }

    /**
     * 新增服务项目和店员等级数据
     *
     * @param playCommodityAndLevelInfo
     *            服务项目和店员等级数据
     * @return 结果
     */
    @Override
    public boolean create(PlayCommodityAndLevelInfoEntity playCommodityAndLevelInfo) {
        if (StrUtil.isBlankIfStr(playCommodityAndLevelInfo.getId())) {
            playCommodityAndLevelInfo.setId(IdUtils.getUuid());
        }
        return save(playCommodityAndLevelInfo);
    }

    /**
     * 修改服务项目和店员等级数据
     *
     * @param playCommodityAndLevelInfo
     *            服务项目和店员等级数据
     * @return 结果
     */
    @Override
    public boolean update(PlayCommodityAndLevelInfoEntity playCommodityAndLevelInfo) {
        return updateById(playCommodityAndLevelInfo);
    }

    /**
     * 批量删除服务项目和店员等级数据
     *
     * @param ids
     *            需要删除的服务项目和店员等级数据主键
     * @return 结果
     */
    @Override
    public int deletePlayCommodityAndLevelInfoByIds(String[] ids) {
        return playCommodityAndLevelInfoMapper.deleteBatchIds(Arrays.asList(ids));
    }

    /**
     * 删除服务项目和店员等级数据信息
     *
     * @param id
     *            服务项目和店员等级数据主键
     * @return 结果
     */
    @Override
    public int deletePlayCommodityAndLevelInfoById(String id) {
        return playCommodityAndLevelInfoMapper.deleteById(id);
    }
}


--- File: play-admin/src/main/java/com/starry/admin/modules/shop/service/impl/PlayCommodityInfoServiceImpl.java ---
package com.starry.admin.modules.shop.service.impl;

import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.github.yulichang.wrapper.MPJLambdaWrapper;
import com.starry.admin.common.exception.CustomException;
import com.starry.admin.modules.shop.mapper.PlayCommodityInfoMapper;
import com.starry.admin.modules.shop.module.entity.PlayCommodityInfoEntity;
import com.starry.admin.modules.shop.module.vo.PlayCommodityInfoVo;
import com.starry.admin.modules.shop.service.IPlayCommodityInfoService;
import com.starry.admin.modules.weichat.entity.PlayCommodityReturnVo;
import com.starry.admin.utils.SecurityUtils;
import com.starry.common.utils.ConvertUtil;
import com.starry.common.utils.IdUtils;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import javax.annotation.Resource;
import org.springframework.stereotype.Service;

/**
 * 服务项目Service业务层处理
 *
 * @author admin
 * @since 2024-03-31
 */
@Service
public class PlayCommodityInfoServiceImpl extends ServiceImpl<PlayCommodityInfoMapper, PlayCommodityInfoEntity>
        implements
            IPlayCommodityInfoService {
    @Resource
    private PlayCommodityInfoMapper playCommodityInfoMapper;

    @Override
    public void initPlayCommodityInfo(String tenantId) {
        LambdaQueryWrapper<PlayCommodityInfoEntity> lambdaQueryWrapper = new LambdaQueryWrapper<>();
        lambdaQueryWrapper.eq(PlayCommodityInfoEntity::getTenantId, tenantId);
        List<PlayCommodityInfoEntity> list = playCommodityInfoMapper.selectList(lambdaQueryWrapper);
        if (list == null || list.isEmpty()) {
            // 查询默认租户的服务信息，然后复制其属性
            SecurityUtils.setTenantId("default");
            LambdaQueryWrapper<PlayCommodityInfoEntity> lambdaQueryWrapper1 = new LambdaQueryWrapper<>();
            lambdaQueryWrapper1.eq(PlayCommodityInfoEntity::getPId, "00");
            for (PlayCommodityInfoEntity entity : playCommodityInfoMapper.selectList(lambdaQueryWrapper1)) {
                initPlayCommodityInfo(entity, tenantId);
            }
        }
    }

    public void initPlayCommodityInfo(PlayCommodityInfoEntity entity, String tenantId) {
        LambdaQueryWrapper<PlayCommodityInfoEntity> lambdaQueryWrapper = new LambdaQueryWrapper<>();
        lambdaQueryWrapper.eq(PlayCommodityInfoEntity::getPId, entity.getId());
        entity.setId(IdUtils.getUuid());
        entity.setTenantId(tenantId);
        this.baseMapper.insert(entity);
        List<PlayCommodityInfoEntity> list = playCommodityInfoMapper.selectList(lambdaQueryWrapper);
        for (PlayCommodityInfoEntity commodityInfo : list) {
            commodityInfo.setPId(entity.getId());
            initPlayCommodityInfo(commodityInfo, tenantId);
        }
    }

    @Override
    public List<PlayCommodityReturnVo> selectTree() {
        MPJLambdaWrapper<PlayCommodityInfoEntity> lambdaQueryWrapper = new MPJLambdaWrapper<>();
        lambdaQueryWrapper.selectAll(PlayCommodityInfoEntity.class);
        lambdaQueryWrapper.orderByDesc(PlayCommodityInfoEntity::getSort);
        List<PlayCommodityInfoEntity> list = this.baseMapper.selectJoinList(PlayCommodityInfoEntity.class,
                lambdaQueryWrapper);
        Map<String, List<PlayCommodityInfoEntity>> collect = list.stream().filter(a -> a != null && a.getId() != null)
                .collect(Collectors.groupingBy(PlayCommodityInfoEntity::getPId));
        return this.assembleTree(collect, collect.get("00"));
    }

    /**
     * 组装数据
     *
     * @param data
     *            数据，key=区域编码,value=区域列表
     * @param list
     *            区域略表
     * @return List<com.starry.admin.modules.system.entity.SysAdministrativeAreaDictInfoEntity>
     * @author admin
     * @since 2024/4/10 15:14
     **/
    public List<PlayCommodityReturnVo> assembleTree(Map<String, List<PlayCommodityInfoEntity>> data,
            List<PlayCommodityInfoEntity> list) {
        if (list == null) {
            return new ArrayList<>();
        }
        List<PlayCommodityReturnVo> result = ConvertUtil.entityToVoList(list, PlayCommodityReturnVo.class);
        for (PlayCommodityReturnVo entity : result) {
            entity.setChild(assembleTree(data, data.get(entity.getId())));
        }
        return result;
    }

    @Override
    public List<PlayCommodityInfoEntity> selectByType() {
        LambdaQueryWrapper<PlayCommodityInfoEntity> lambdaQueryWrapper = new LambdaQueryWrapper<>();
        lambdaQueryWrapper.eq(PlayCommodityInfoEntity::getItemType, "服务类型");
        lambdaQueryWrapper.orderByDesc(PlayCommodityInfoEntity::getSort);
        return this.baseMapper.selectList(lambdaQueryWrapper);
    }

    @Override
    public List<PlayCommodityInfoEntity> selectAll() {
        return this.baseMapper.selectList(new LambdaQueryWrapper<>());
    }

    @Override
    public PlayCommodityInfoVo queryCommodityInfo(String id, String levelId) {
        PlayCommodityInfoVo vo = this.baseMapper.queryCommodityInfo(id, levelId);
        if (vo == null) {
            throw new CustomException("服务项目不存在");
        }
        return vo;
    }

    /**
     * 查询服务项目
     *
     * @param id
     *            服务项目主键
     * @return 服务项目
     */
    @Override
    public PlayCommodityInfoEntity selectPlayCommodityInfoById(String id) {
        PlayCommodityInfoEntity entity = this.baseMapper.selectById(id);
        if (entity == null) {
            throw new CustomException("服务项目不存在");
        }
        return entity;
    }

    @Override
    public List<PlayCommodityInfoEntity> selectByPId(String pId) {
        LambdaQueryWrapper<PlayCommodityInfoEntity> lambdaQueryWrapper = new LambdaQueryWrapper<>();
        lambdaQueryWrapper.eq(PlayCommodityInfoEntity::getPId, pId);
        lambdaQueryWrapper.orderByDesc(PlayCommodityInfoEntity::getSort);
        return this.baseMapper.selectList(lambdaQueryWrapper);
    }

    /**
     * 查询服务项目列表
     *
     * @param playCommodityInfo
     *            服务项目
     * @return 服务项目
     */
    @Override
    public IPage<PlayCommodityInfoEntity> selectPlayCommodityInfoByPage(PlayCommodityInfoEntity playCommodityInfo) {
        Page<PlayCommodityInfoEntity> page = new Page<>(1, 10);
        return this.baseMapper.selectPage(page, new LambdaQueryWrapper<>());
    }

    /**
     * 新增服务项目
     *
     * @param playCommodityInfo
     *            服务项目
     * @return 结果
     */
    @Override
    public boolean create(PlayCommodityInfoEntity playCommodityInfo) {
        if (StrUtil.isBlankIfStr(playCommodityInfo.getId())) {
            playCommodityInfo.setId(IdUtils.getUuid());
        }
        return save(playCommodityInfo);
    }

    /**
     * 修改服务项目
     *
     * @param playCommodityInfo
     *            服务项目
     * @return 结果
     */
    @Override
    public boolean update(PlayCommodityInfoEntity playCommodityInfo) {
        return updateById(playCommodityInfo);
    }

    /**
     * 批量删除服务项目
     *
     * @param ids
     *            需要删除的服务项目主键
     * @return 结果
     */
    @Override
    public int deletePlayCommodityInfoByIds(String[] ids) {
        return playCommodityInfoMapper.deleteBatchIds(Arrays.asList(ids));
    }

    /**
     * 删除服务项目信息
     *
     * @param id
     *            服务项目主键
     * @return 结果
     */
    @Override
    public int deletePlayCommodityInfoById(String id) {
        return playCommodityInfoMapper.deleteById(id);
    }
}


--- File: play-admin/src/main/java/com/starry/admin/modules/shop/service/impl/PlayCouponDetailsServiceImpl.java ---
package com.starry.admin.modules.shop.service.impl;

import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.github.yulichang.wrapper.MPJLambdaWrapper;
import com.starry.admin.modules.shop.mapper.PlayCouponDetailsMapper;
import com.starry.admin.modules.shop.module.entity.PlayCouponDetailsEntity;
import com.starry.admin.modules.shop.module.entity.PlayCouponInfoEntity;
import com.starry.admin.modules.shop.module.vo.PlayCouponDetailsQueryVo;
import com.starry.admin.modules.shop.module.vo.PlayCouponDetailsReturnVo;
import com.starry.admin.modules.shop.service.IPlayCouponDetailsService;
import com.starry.common.utils.IdUtils;
import java.time.LocalDateTime;
import java.util.Arrays;
import java.util.List;
import javax.annotation.Resource;
import org.springframework.stereotype.Service;

/**
 * 优惠券详情Service业务层处理
 *
 * @author admin
 * @since 2024-07-04
 */
@Service
public class PlayCouponDetailsServiceImpl extends ServiceImpl<PlayCouponDetailsMapper, PlayCouponDetailsEntity>
        implements
            IPlayCouponDetailsService {
    @Resource
    private PlayCouponDetailsMapper playCouponDetailsMapper;

    /**
     * 查询优惠券详情
     *
     * @param id
     *            优惠券详情主键
     * @return 优惠券详情
     */
    @Override
    public PlayCouponDetailsReturnVo selectPlayCouponDetailsById(String id) {
        MPJLambdaWrapper<PlayCouponDetailsEntity> lambdaWrapper = new MPJLambdaWrapper<>();
        lambdaWrapper.selectAll(PlayCouponDetailsEntity.class);
        lambdaWrapper.leftJoin(PlayCouponInfoEntity.class, PlayCouponInfoEntity::getId,
                PlayCouponDetailsEntity::getCouponId);
        lambdaWrapper.eq(PlayCouponDetailsEntity::getId, id);
        return this.baseMapper.selectJoinOne(PlayCouponDetailsReturnVo.class, lambdaWrapper);
    }

    @Override
    public IPage<PlayCouponDetailsReturnVo> selectByPage(PlayCouponDetailsQueryVo vo) {
        MPJLambdaWrapper<PlayCouponDetailsEntity> lambdaWrapper = new MPJLambdaWrapper<>();
        lambdaWrapper.selectAll(PlayCouponDetailsEntity.class);
        lambdaWrapper.selectAll(PlayCouponInfoEntity.class);
        lambdaWrapper.leftJoin(PlayCouponInfoEntity.class, PlayCouponInfoEntity::getId,
                PlayCouponDetailsEntity::getCouponId);
        if (StrUtil.isNotBlank(vo.getCouponId())) {
            lambdaWrapper.eq(PlayCouponDetailsEntity::getCouponId, vo.getCouponId());
        }
        if (StrUtil.isNotBlank(vo.getCustomId())) {
            lambdaWrapper.eq(PlayCouponDetailsEntity::getCustomId, vo.getCustomId());
        }
        if (StrUtil.isNotBlank(vo.getUseState())) {
            lambdaWrapper.eq(PlayCouponDetailsEntity::getUseState, vo.getUseState());
        }
        if (StrUtil.isNotBlank(vo.getObtainingChannels())) {
            lambdaWrapper.eq(PlayCouponDetailsEntity::getObtainingChannels, vo.getObtainingChannels());
        }
        if (vo.getObtainingTime() != null && vo.getObtainingTime().size() == 2) {
            lambdaWrapper.between(PlayCouponDetailsEntity::getObtainingTime, vo.getObtainingTime().get(0),
                    vo.getObtainingTime().get(1));
        }
        if (vo.getUseTime() != null && vo.getUseTime().size() == 2) {
            lambdaWrapper.between(PlayCouponDetailsEntity::getUseTime, vo.getUseTime().get(0), vo.getUseTime().get(1));
        }

        return this.baseMapper.selectJoinPage(new Page<>(vo.getPageNum(), vo.getPageSize()),
                PlayCouponDetailsReturnVo.class, lambdaWrapper);
    }

    /**
     * 查询优惠券详情列表
     *
     * @param playCouponDetails
     *            优惠券详情
     * @return 优惠券详情
     */
    @Override
    public IPage<PlayCouponDetailsEntity> selectPlayCouponDetailsByPage(PlayCouponDetailsEntity playCouponDetails) {
        Page<PlayCouponDetailsEntity> page = new Page<>(1, 10);
        return this.baseMapper.selectPage(page, new LambdaQueryWrapper<PlayCouponDetailsEntity>());
    }

    @Override
    public void create(String customId, String customNickName, String customLevel, String couponId,
            String obtainingChannels, String useState) {
        PlayCouponDetailsEntity couponDetails = new PlayCouponDetailsEntity();
        couponDetails.setId(IdUtils.getUuid());
        couponDetails.setCustomId(customId);
        couponDetails.setCustomNickname(customNickName);
        couponDetails.setCustomLevelId(customLevel);
        couponDetails.setCouponId(couponId);
        couponDetails.setObtainingChannels(obtainingChannels);
        couponDetails.setObtainingTime(LocalDateTime.now());
        couponDetails.setUseState(useState);
        this.create(couponDetails);
    }

    /**
     * 新增优惠券详情
     *
     * @param playCouponDetails
     *            优惠券详情
     * @return 结果
     */
    @Override
    public boolean create(PlayCouponDetailsEntity playCouponDetails) {
        if (StrUtil.isBlankIfStr(playCouponDetails.getId())) {
            playCouponDetails.setId(IdUtils.getUuid());
        }
        return save(playCouponDetails);
    }

    @Override
    public List<PlayCouponDetailsReturnVo> selectByCustomId(String customId) {
        MPJLambdaWrapper<PlayCouponDetailsEntity> lambdaWrapper = new MPJLambdaWrapper<>();
        lambdaWrapper.selectAll(PlayCouponDetailsEntity.class);
        lambdaWrapper.selectAll(PlayCouponInfoEntity.class);
        lambdaWrapper.eq(PlayCouponDetailsEntity::getCustomId, customId);
        lambdaWrapper.leftJoin(PlayCouponInfoEntity.class, PlayCouponInfoEntity::getId,
                PlayCouponDetailsEntity::getCouponId);
        return this.baseMapper.selectJoinList(PlayCouponDetailsReturnVo.class, lambdaWrapper);
    }

    /**
     * 修改优惠券详情
     *
     * @param playCouponDetails
     *            优惠券详情
     * @return 结果
     */
    @Override
    public boolean update(PlayCouponDetailsEntity playCouponDetails) {
        return updateById(playCouponDetails);
    }

    @Override
    public void updateCouponUseStateByIds(List<String> ids, String useState) {
        for (String id : ids) {
            PlayCouponDetailsEntity entity = new PlayCouponDetailsEntity();
            entity.setId(id);
            entity.setUseState(useState);
            entity.setUseTime(LocalDateTime.now());
            baseMapper.updateById(entity);
        }
    }

    /**
     * 批量删除优惠券详情
     *
     * @param ids
     *            需要删除的优惠券详情主键
     * @return 结果
     */
    @Override
    public int deletePlayCouponDetailsByIds(String[] ids) {
        return playCouponDetailsMapper.deleteBatchIds(Arrays.asList(ids));
    }

    /**
     * 删除优惠券详情信息
     *
     * @param id
     *            优惠券详情主键
     * @return 结果
     */
    @Override
    public int deletePlayCouponDetailsById(String id) {
        return playCouponDetailsMapper.deleteById(id);
    }
}


--- File: play-admin/src/main/java/com/starry/admin/modules/shop/service/impl/PlayCouponInfoServiceImpl.java ---
package com.starry.admin.modules.shop.service.impl;

import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.github.yulichang.wrapper.MPJLambdaWrapper;
import com.starry.admin.common.exception.CustomException;
import com.starry.admin.modules.custom.module.entity.PlayCustomUserInfoEntity;
import com.starry.admin.modules.shop.mapper.PlayCouponInfoMapper;
import com.starry.admin.modules.shop.module.entity.PlayCouponInfoEntity;
import com.starry.admin.modules.shop.module.vo.PlayCouponInfoQueryVo;
import com.starry.admin.modules.shop.module.vo.PlayCouponInfoReturnVo;
import com.starry.admin.modules.shop.service.IPlayCouponInfoService;
import com.starry.common.utils.IdUtils;
import java.math.BigDecimal;
import java.util.Arrays;
import java.util.List;
import javax.annotation.Resource;
import org.springframework.stereotype.Service;

/**
 * 优惠券信息Service业务层处理
 *
 * @author admin
 * @since 2024-07-04
 */
@Service
public class PlayCouponInfoServiceImpl extends ServiceImpl<PlayCouponInfoMapper, PlayCouponInfoEntity>
        implements
            IPlayCouponInfoService {
    @Resource
    private PlayCouponInfoMapper playCouponInfoMapper;

    /**
     * 获取优惠券不可用的原因
     *
     * @param couponInfo
     *            优惠券信息
     * @param placeType
     *            下单类型
     * @param commodityId
     *            商品ID
     * @param commodityQuantity
     *            商品数量
     * @return 不可用原因
     */
    @Override
    public String getCouponReasonForUnavailableUse(PlayCouponInfoEntity couponInfo, String placeType,
            String commodityId, Integer commodityQuantity, BigDecimal price) {
        if (StrUtil.isEmpty(commodityId) || commodityQuantity <= 0) {
            return "订单满1船票余额时可用";
        }
        boolean placeTypeSuccess = false;
        for (String string : couponInfo.getPlaceType()) {
            if (string.equals(placeType)) {
                placeTypeSuccess = true;
                break;
            }
        }
        if (!placeTypeSuccess) {
            return "订单类型不符合";
        }
        // 订单门槛不等0，并且订单金额小于订单门槛
        if (BigDecimal.ZERO.compareTo(couponInfo.getUseMinAmount()) != 0
                && price.multiply(new BigDecimal(commodityQuantity)).compareTo(couponInfo.getUseMinAmount()) <= -1) {
            return "订单门槛不符合";
        }
        return "";
    }

    /**
     * 获取优惠券不可领取的原因
     *
     * @param entity
     *            优惠券实体
     * @param customUserInfo
     *            顾客实体
     * @return String
     * @author admin
     * @since 2024/7/8 14:44
     **/
    @Override
    public String getReasonForNotObtainingCoupons(PlayCouponInfoEntity entity,
            PlayCustomUserInfoEntity customUserInfo) {
        // 优惠券是否设置指定条件领取
        if ("0".equals(entity.getClaimConditionType())) {
            return "";
        }
        // 顾客等级判断
        String msg = reasonForNotObtainingCoupons(entity.getCustomLevelCheckType(), entity.getCustomLevel(),
                customUserInfo.getLevelId(), "0");
        if (StrUtil.isNotEmpty(msg)) {
            return msg;
        }
        // 顾客性别判断
        msg = reasonForNotObtainingCoupons(entity.getCustomSexCheckType(), entity.getCustomSex(),
                customUserInfo.getSex().toString(), "`1");
        if (StrUtil.isNotEmpty(msg)) {
            return msg;
        }
        // 顾客关注公众号状态判断

        // 顾客是否是新用户判断
        return "";
    }

    /**
     * 获取优惠券不可领取的原因
     *
     * @param checkStatus
     *            是否配置了该项指标（0:未配置,1:已配置）
     * @param attributes
     *            配置的属性列表
     * @param attribute
     *            用户属性值
     * @param type
     *            条件类型(0:用户等级,1:用户性别)
     * @return String
     * @author admin
     * @since 2024/7/8 15:22
     **/
    public String reasonForNotObtainingCoupons(String checkStatus, List<String> attributes, String attribute,
            String type) {
        if ("0".equals(checkStatus)) {
            return "";
        }
        for (String s : attributes) {
            if (s.equals(attribute)) {
                return "";
            }
        }
        if ("0".equals(type)) {
            return "用户等级不匹配";
        }
        if ("1".equals(type)) {
            return "用户性别不匹配";
        }
        return "";
    }

    /**
     * 查询优惠券信息
     *
     * @param id
     *            优惠券信息主键
     * @return 优惠券信息
     */
    @Override
    public PlayCouponInfoEntity selectPlayCouponInfoById(String id) {
        PlayCouponInfoEntity entity = this.baseMapper.selectById(id);
        if (entity == null) {
            throw new CustomException("优惠券不存在");
        }
        return entity;
    }

    @Override
    public IPage<PlayCouponInfoReturnVo> selectByPage(PlayCouponInfoQueryVo vo) {
        MPJLambdaWrapper<PlayCouponInfoEntity> lambdaQueryWrapper = new MPJLambdaWrapper<>();
        lambdaQueryWrapper.selectAll(PlayCouponInfoEntity.class);
        lambdaQueryWrapper.orderByDesc(PlayCouponInfoEntity::getCreatedTime);
        return this.baseMapper.selectJoinPage(new Page<>(vo.getPageNum(), vo.getPageSize()),
                PlayCouponInfoReturnVo.class, lambdaQueryWrapper);
    }

    @Override
    public List<PlayCouponInfoEntity> queryAll() {
        MPJLambdaWrapper<PlayCouponInfoEntity> lambdaQueryWrapper = new MPJLambdaWrapper<>();
        lambdaQueryWrapper.selectAll(PlayCouponInfoEntity.class);
        lambdaQueryWrapper.orderByDesc(PlayCouponInfoEntity::getCreatedTime);
        return this.baseMapper.selectJoinList(PlayCouponInfoEntity.class, lambdaQueryWrapper);
    }

    /**
     * 查询优惠券信息列表
     *
     * @param playCouponInfo
     *            优惠券信息
     * @return 优惠券信息
     */
    @Override
    public IPage<PlayCouponInfoEntity> selectPlayCouponInfoByPage(PlayCouponInfoEntity playCouponInfo) {
        Page<PlayCouponInfoEntity> page = new Page<>(1, 10);
        return this.baseMapper.selectPage(page, new LambdaQueryWrapper<PlayCouponInfoEntity>());
    }

    /**
     * 新增优惠券信息
     *
     * @param playCouponInfo
     *            优惠券信息
     * @return 结果
     */
    @Override
    public boolean create(PlayCouponInfoEntity playCouponInfo) {
        if (StrUtil.isBlankIfStr(playCouponInfo.getId())) {
            playCouponInfo.setId(IdUtils.getUuid());
        }
        return save(playCouponInfo);
    }

    /**
     * 修改优惠券信息
     *
     * @param playCouponInfo
     *            优惠券信息
     * @return 结果
     */
    @Override
    public boolean update(PlayCouponInfoEntity playCouponInfo) {
        return updateById(playCouponInfo);
    }

    /**
     * 批量删除优惠券信息
     *
     * @param ids
     *            需要删除的优惠券信息主键
     * @return 结果
     */
    @Override
    public int deletePlayCouponInfoByIds(String[] ids) {
        return playCouponInfoMapper.deleteBatchIds(Arrays.asList(ids));
    }

    /**
     * 删除优惠券信息信息
     *
     * @param id
     *            优惠券信息主键
     * @return 结果
     */
    @Override
    public int deletePlayCouponInfoById(String id) {
        return playCouponInfoMapper.deleteById(id);
    }
}


--- File: play-admin/src/main/java/com/starry/admin/modules/shop/service/impl/PlayGiftInfoServiceImpl.java ---
package com.starry.admin.modules.shop.service.impl;

import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.github.yulichang.wrapper.MPJLambdaWrapper;
import com.starry.admin.common.exception.CustomException;
import com.starry.admin.modules.custom.module.entity.PlayCustomGiftInfoEntity;
import com.starry.admin.modules.custom.service.impl.PlayCustomGiftInfoServiceImpl;
import com.starry.admin.modules.shop.mapper.PlayGiftInfoMapper;
import com.starry.admin.modules.shop.module.entity.PlayClerkGiftInfoEntity;
import com.starry.admin.modules.shop.module.entity.PlayGiftInfoEntity;
import com.starry.admin.modules.shop.service.IPlayGiftInfoService;
import com.starry.admin.modules.weichat.entity.gift.PlayClerkGiftReturnVo;
import com.starry.common.utils.IdUtils;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
import javax.annotation.Resource;
import org.springframework.stereotype.Service;

/**
 * 礼物Service业务层处理
 *
 * @author admin
 * @since 2024-04-25
 */
@Service
public class PlayGiftInfoServiceImpl extends ServiceImpl<PlayGiftInfoMapper, PlayGiftInfoEntity>
        implements
            IPlayGiftInfoService {
    @Resource
    private PlayGiftInfoMapper playGiftInfoMapper;

    @Resource
    private PlayClerkGiftInfoServiceImpl clerkGiftInfoService;

    @Resource
    private PlayCustomGiftInfoServiceImpl customGiftInfoService;

    /**
     * 查询礼物
     *
     * @param id
     *            礼物主键
     * @return 礼物
     */
    @Override
    public PlayGiftInfoEntity selectPlayGiftInfoById(String id) {
        PlayGiftInfoEntity entity = this.baseMapper.selectById(id);
        if (entity == null) {
            throw new CustomException("礼物不存在");
        }
        return entity;
    }

    @Override
    public List<PlayClerkGiftReturnVo> customListByAll(String customId, String obtained) {
        if ("0".equals(obtained)) {
            // 查询所有礼物，然后减去已获得礼物
            MPJLambdaWrapper<PlayGiftInfoEntity> lambdaWrapper = new MPJLambdaWrapper<>();
            lambdaWrapper.selectAll(PlayGiftInfoEntity.class);
            List<PlayClerkGiftReturnVo> list = this.baseMapper.selectJoinList(PlayClerkGiftReturnVo.class,
                    lambdaWrapper);
            List<PlayCustomGiftInfoEntity> giftInfoEntities = customGiftInfoService.selectBtyCustomId(customId);
            // 使用迭代器安全地移除元素
            Iterator<PlayClerkGiftReturnVo> iterator = list.iterator();
            while (iterator.hasNext()) {
                PlayClerkGiftReturnVo item = iterator.next();
                for (PlayCustomGiftInfoEntity giftInfoEntity : giftInfoEntities) {
                    if (giftInfoEntity.getGiffId().equals(item.getId())) {
                        iterator.remove();
                        break;
                    }
                }

            }
            return list;
        }
        if ("1".equals(obtained)) {
            MPJLambdaWrapper<PlayGiftInfoEntity> lambdaWrapper = new MPJLambdaWrapper<>();
            lambdaWrapper.selectAll(PlayGiftInfoEntity.class);
            // 礼物活动表
            lambdaWrapper.selectAs(PlayCustomGiftInfoEntity::getGiffNumber, "giffNumber");
            lambdaWrapper.innerJoin(PlayCustomGiftInfoEntity.class, PlayCustomGiftInfoEntity::getGiffId,
                    PlayGiftInfoEntity::getId);
            lambdaWrapper.eq(PlayCustomGiftInfoEntity::getCustomId, customId);
            return this.baseMapper.selectJoinList(PlayClerkGiftReturnVo.class, lambdaWrapper);
        }
        return new ArrayList<>();

    }

    @Override
    public List<PlayClerkGiftReturnVo> clerkListByAll(String clerkId, String obtained) {
        if ("0".equals(obtained)) {
            // 查询所有礼物，然后减去已获得礼物
            MPJLambdaWrapper<PlayGiftInfoEntity> lambdaWrapper = new MPJLambdaWrapper<>();
            lambdaWrapper.selectAll(PlayGiftInfoEntity.class);
            List<PlayClerkGiftReturnVo> list = this.baseMapper.selectJoinList(PlayClerkGiftReturnVo.class,
                    lambdaWrapper);
            List<PlayClerkGiftInfoEntity> clerkGiftInfoEntities = clerkGiftInfoService.selectBtyClerkId(clerkId);
            // 使用迭代器安全地移除元素
            Iterator<PlayClerkGiftReturnVo> iterator = list.iterator();
            while (iterator.hasNext()) {
                PlayClerkGiftReturnVo item = iterator.next();
                for (PlayClerkGiftInfoEntity clerkGiftInfoEntity : clerkGiftInfoEntities) {
                    if (clerkGiftInfoEntity.getGiffId().equals(item.getId())) {
                        iterator.remove();
                        break;
                    }
                }

            }
            return list;
        }
        if ("1".equals(obtained)) {
            MPJLambdaWrapper<PlayGiftInfoEntity> lambdaWrapper = new MPJLambdaWrapper<>();
            lambdaWrapper.selectAll(PlayGiftInfoEntity.class);
            // 礼物活动表
            lambdaWrapper.selectAs(PlayClerkGiftInfoEntity::getGiffNumber, "giffNumber");
            lambdaWrapper.innerJoin(PlayClerkGiftInfoEntity.class, PlayClerkGiftInfoEntity::getGiffId,
                    PlayGiftInfoEntity::getId);
            lambdaWrapper.eq(PlayClerkGiftInfoEntity::getClerkId, clerkId);
            return this.baseMapper.selectJoinList(PlayClerkGiftReturnVo.class, lambdaWrapper);
        }
        return new ArrayList<>();
    }

    @Override
    public List<PlayGiftInfoEntity> listByAll() {
        return this.baseMapper.selectList(new LambdaQueryWrapper<>());
    }

    /**
     * 查询礼物列表
     *
     * @param playGiftInfo
     *            礼物
     * @return 礼物
     */
    @Override
    public IPage<PlayGiftInfoEntity> selectPlayGiftInfoByPage(PlayGiftInfoEntity playGiftInfo) {
        Page<PlayGiftInfoEntity> page = new Page<>(1, 10);
        return this.baseMapper.selectPage(page, new LambdaQueryWrapper<>());
    }

    /**
     * 新增礼物
     *
     * @param playGiftInfo
     *            礼物
     * @return 结果
     */
    @Override
    public boolean create(PlayGiftInfoEntity playGiftInfo) {
        if (StrUtil.isBlankIfStr(playGiftInfo.getId())) {
            playGiftInfo.setId(IdUtils.getUuid());
        }
        return save(playGiftInfo);
    }

    /**
     * 修改礼物
     *
     * @param playGiftInfo
     *            礼物
     * @return 结果
     */
    @Override
    public boolean update(PlayGiftInfoEntity playGiftInfo) {
        return updateById(playGiftInfo);
    }

    /**
     * 批量删除礼物
     *
     * @param ids
     *            需要删除的礼物主键
     * @return 结果
     */
    @Override
    public int deletePlayGiftInfoByIds(String[] ids) {
        return playGiftInfoMapper.deleteBatchIds(Arrays.asList(ids));
    }

    /**
     * 删除礼物信息
     *
     * @param id
     *            礼物主键
     * @return 结果
     */
    @Override
    public int deletePlayGiftInfoById(String id) {
        return playGiftInfoMapper.deleteById(id);
    }
}


--- File: play-admin/src/main/java/com/starry/admin/modules/shop/service/impl/PlayShopArticleInfoServiceImpl.java ---
package com.starry.admin.modules.shop.service.impl;

import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.starry.admin.common.exception.CustomException;
import com.starry.admin.modules.shop.mapper.PlayShopArticleInfoMapper;
import com.starry.admin.modules.shop.module.entity.PlayShopArticleInfoEntity;
import com.starry.admin.modules.shop.module.vo.PlayShopArticleInfoQueryVo;
import com.starry.admin.modules.shop.service.IPlayShopArticleInfoService;
import com.starry.common.utils.IdUtils;
import java.util.Arrays;
import java.util.List;
import org.springframework.stereotype.Service;

/**
 * 店铺首页轮播Service业务层处理
 *
 * @author admin
 * @since 2024-06-05
 */
@Service
public class PlayShopArticleInfoServiceImpl extends ServiceImpl<PlayShopArticleInfoMapper, PlayShopArticleInfoEntity>
        implements
            IPlayShopArticleInfoService {

    @Override
    public PlayShopArticleInfoEntity selectByType(String type) {
        LambdaQueryWrapper<PlayShopArticleInfoEntity> lambdaWrapper = new LambdaQueryWrapper<>();
        lambdaWrapper.eq(PlayShopArticleInfoEntity::getArticleType, type);
        return this.baseMapper.selectOne(lambdaWrapper);
    }

    /**
     * 查询店铺首页轮播
     *
     * @param id
     *            店铺首页轮播主键
     * @return 店铺首页轮播
     */
    @Override
    public PlayShopArticleInfoEntity selectById(String id) {
        PlayShopArticleInfoEntity entity = this.baseMapper.selectById(id);
        if (entity == null) {
            throw new CustomException("对象不存在");
        }
        return entity;
    }

    @Override
    public List<PlayShopArticleInfoEntity> selectByList() {
        return this.baseMapper.selectList(new LambdaQueryWrapper<>());

    }

    @Override
    public IPage<PlayShopArticleInfoEntity> selectByPage(PlayShopArticleInfoQueryVo vo) {
        LambdaQueryWrapper<PlayShopArticleInfoEntity> lambdaWrapper = new LambdaQueryWrapper<>();
        if (StrUtil.isNotBlank(vo.getId())) {
            lambdaWrapper.eq(PlayShopArticleInfoEntity::getId, vo.getId());
        }
        if (StrUtil.isNotBlank(vo.getArticleType())) {
            lambdaWrapper.eq(PlayShopArticleInfoEntity::getArticleType, vo.getArticleType());
        }
        if (StrUtil.isNotBlank(vo.getArticleTitle())) {
            lambdaWrapper.like(PlayShopArticleInfoEntity::getArticleTitle, vo.getArticleTitle());
        }
        return this.baseMapper.selectPage(new Page<>(vo.getPageNum(), vo.getPageSize()), lambdaWrapper);
    }

    /**
     * 新增店铺首页轮播
     *
     * @param articleInfoEntity
     *            店铺首页轮播
     * @return 结果
     */
    @Override
    public boolean create(PlayShopArticleInfoEntity articleInfoEntity) {
        if (StrUtil.isBlankIfStr(articleInfoEntity.getId())) {
            articleInfoEntity.setId(IdUtils.getUuid());
        }
        return save(articleInfoEntity);
    }

    /**
     * 修改店铺首页轮播
     *
     * @param articleInfoEntity
     *            店铺首页轮播
     * @return 结果
     */
    @Override
    public boolean update(PlayShopArticleInfoEntity articleInfoEntity) {
        return updateById(articleInfoEntity);
    }

    /**
     * 批量删除店铺首页轮播
     *
     * @param ids
     *            需要删除的店铺首页轮播主键
     * @return 结果
     */
    @Override
    public int deleteByIds(String[] ids) {
        return this.baseMapper.deleteBatchIds(Arrays.asList(ids));
    }

    /**
     * 删除店铺首页轮播信息
     *
     * @param id
     *            店铺首页轮播主键
     * @return 结果
     */
    @Override
    public int deleteById(String id) {
        return this.baseMapper.deleteById(id);
    }
}


--- File: play-admin/src/main/java/com/starry/admin/modules/shop/service/impl/PlayShopCarouselInfoServiceImpl.java ---
package com.starry.admin.modules.shop.service.impl;

import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.starry.admin.modules.shop.mapper.PlayShopCarouselInfoMapper;
import com.starry.admin.modules.shop.module.entity.PlayShopCarouselInfoEntity;
import com.starry.admin.modules.shop.module.vo.PlayShopCarouselInfoQueryVo;
import com.starry.admin.modules.shop.service.IPlayShopCarouselInfoService;
import com.starry.common.utils.IdUtils;
import java.util.Arrays;
import java.util.List;
import javax.annotation.Resource;
import org.springframework.stereotype.Service;

/**
 * 店铺首页轮播Service业务层处理
 *
 * @author admin
 * @since 2024-06-05
 */
@Service
public class PlayShopCarouselInfoServiceImpl extends ServiceImpl<PlayShopCarouselInfoMapper, PlayShopCarouselInfoEntity>
        implements
            IPlayShopCarouselInfoService {
    @Resource
    private PlayShopCarouselInfoMapper playCarouselInfoMapper;

    /**
     * 查询店铺首页轮播
     *
     * @param id
     *            店铺首页轮播主键
     * @return 店铺首页轮播
     */
    @Override
    public PlayShopCarouselInfoEntity selectPlayCarouselInfoById(String id) {
        return this.baseMapper.selectById(id);
    }

    @Override
    public List<PlayShopCarouselInfoEntity> selectHomeCarouselInfo() {
        LambdaQueryWrapper<PlayShopCarouselInfoEntity> lambdaWrapper = new LambdaQueryWrapper<>();
        lambdaWrapper.eq(PlayShopCarouselInfoEntity::getCarouselIndex, "0");
        lambdaWrapper.eq(PlayShopCarouselInfoEntity::getEnableState, "1");
        return this.baseMapper.selectList(lambdaWrapper);
    }

    @Override
    public IPage<PlayShopCarouselInfoEntity> selectByPage(PlayShopCarouselInfoQueryVo vo) {
        LambdaQueryWrapper<PlayShopCarouselInfoEntity> lambdaWrapper = new LambdaQueryWrapper<>();
        if (StrUtil.isNotBlank(vo.getCarouselIndex())) {
            lambdaWrapper.eq(PlayShopCarouselInfoEntity::getCarouselIndex, vo.getCarouselIndex());
        }
        if (StrUtil.isNotBlank(vo.getEnableState())) {
            lambdaWrapper.eq(PlayShopCarouselInfoEntity::getEnableState, vo.getEnableState());
        }
        if (vo.getAddTime() != null && vo.getAddTime().size() == 2) {
            lambdaWrapper.between(PlayShopCarouselInfoEntity::getAddTime, vo.getAddTime().get(0),
                    vo.getAddTime().get(1));
        }
        return this.baseMapper.selectPage(new Page<>(vo.getPageNum(), vo.getPageSize()), lambdaWrapper);

    }

    /**
     * 新增店铺首页轮播
     *
     * @param playCarouselInfo
     *            店铺首页轮播
     * @return 结果
     */
    @Override
    public boolean create(PlayShopCarouselInfoEntity playCarouselInfo) {
        if (StrUtil.isBlankIfStr(playCarouselInfo.getId())) {
            playCarouselInfo.setId(IdUtils.getUuid());
        }
        return save(playCarouselInfo);
    }

    /**
     * 修改店铺首页轮播
     *
     * @param playCarouselInfo
     *            店铺首页轮播
     * @return 结果
     */
    @Override
    public boolean update(PlayShopCarouselInfoEntity playCarouselInfo) {
        return updateById(playCarouselInfo);
    }

    /**
     * 批量删除店铺首页轮播
     *
     * @param ids
     *            需要删除的店铺首页轮播主键
     * @return 结果
     */
    @Override
    public int deletePlayCarouselInfoByIds(String[] ids) {
        return playCarouselInfoMapper.deleteBatchIds(Arrays.asList(ids));
    }

    /**
     * 删除店铺首页轮播信息
     *
     * @param id
     *            店铺首页轮播主键
     * @return 结果
     */
    @Override
    public int deletePlayCarouselInfoById(String id) {
        return playCarouselInfoMapper.deleteById(id);
    }
}


--- File: play-admin/src/main/java/com/starry/admin/modules/shop/service/impl/ShopUiSettingServiceImpl.java ---
package com.starry.admin.modules.shop.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.starry.admin.modules.shop.mapper.ShopUiSettingMapper;
import com.starry.admin.modules.shop.module.entity.ShopUiSettingEntity;
import com.starry.admin.modules.shop.service.IShopUiSettingService;
import java.util.Objects;
import javax.annotation.Resource;
import org.springframework.stereotype.Service;

/**
 * 店铺设置Service业务层处理
 *
 * @author admin
 * @since 2025-06-11
 */
@Service
public class ShopUiSettingServiceImpl extends ServiceImpl<ShopUiSettingMapper, ShopUiSettingEntity>
        implements
            IShopUiSettingService {
    @Resource
    private ShopUiSettingMapper shopUiSettingMapper;

    /**
     * 查询店铺设置
     *
     * @return 店铺设置
     */
    @Override
    public ShopUiSettingEntity selectShopUiSettingInfo() {
        LambdaQueryWrapper<ShopUiSettingEntity> wrapper = Wrappers.lambdaQuery(ShopUiSettingEntity.class);
        ShopUiSettingEntity setting = this.baseMapper.selectOne(wrapper, false);
        if (Objects.nonNull(setting)) {
            return setting;
        } else {
            setting = new ShopUiSettingEntity();
            setting.setSettingConfig(null);
            this.baseMapper.insert(setting);
        }
        return setting;
    }

    /**
     * 修改店铺设置
     *
     * @param shopUiSetting
     *            店铺设置
     * @return 结果
     */
    @Override
    public boolean update(ShopUiSettingEntity shopUiSetting) {
        return updateById(shopUiSetting);
    }
}


--- File: play-admin/src/main/java/com/starry/admin/modules/statistics/controller/PlayClerkPerformanceController.java ---
package com.starry.admin.modules.statistics.controller;

import com.baomidou.mybatisplus.core.metadata.IPage;
import com.starry.admin.modules.clerk.module.entity.PlayClerkLevelInfoEntity;
import com.starry.admin.modules.clerk.module.entity.PlayClerkUserInfoEntity;
import com.starry.admin.modules.clerk.service.IPlayClerkLevelInfoService;
import com.starry.admin.modules.clerk.service.IPlayClerkUserInfoService;
import com.starry.admin.modules.order.module.entity.PlayOrderInfoEntity;
import com.starry.admin.modules.order.service.IPlayOrderInfoService;
import com.starry.admin.modules.personnel.module.entity.PlayPersonnelGroupInfoEntity;
import com.starry.admin.modules.personnel.service.IPlayPersonnelGroupInfoService;
import com.starry.admin.modules.statistics.module.vo.PlayClerkPerformanceInfoQueryVo;
import com.starry.admin.modules.statistics.module.vo.PlayClerkPerformanceInfoReturnVo;
import com.starry.admin.modules.statistics.service.IPlayClerkPerformanceService;
import com.starry.common.result.R;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiParam;
import io.swagger.annotations.ApiResponse;
import io.swagger.annotations.ApiResponses;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.time.temporal.TemporalAdjusters;
import java.util.*;
import java.util.stream.Collectors;
import javax.annotation.Resource;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 * 店员业绩查询对
 *
 * @author admin
 * @since 2024/6/15 下午3:15
 **/
@Api(tags = "店员业绩统计", description = "店员业绩统计相关接口，包括按日期、月份查询等操作")
@RestController
@RequestMapping("/statistics/performance")
public class PlayClerkPerformanceController {

    @Resource
    private IPlayClerkUserInfoService clerkUserInfoService;

    @Resource
    private IPlayOrderInfoService playOrderInfoService;

    @Resource
    private IPlayClerkLevelInfoService playClerkLevelInfoService;

    @Resource
    private IPlayPersonnelGroupInfoService playPersonnelGroupInfoService;

    @Resource
    private IPlayClerkPerformanceService playClerkPerformanceService;

    @ApiOperation(value = "分页查询店员业绩", notes = "分页查询店员业绩统计信息")
    @ApiResponses({
            @ApiResponse(code = 200, message = "操作成功", response = PlayClerkPerformanceInfoReturnVo.class, responseContainer = "Page")})
    @PostMapping("/listByPage")
    public R listByPage(
            @ApiParam(value = "查询条件", required = true) @Validated @RequestBody PlayClerkPerformanceInfoQueryVo vo) {
        IPage<PlayClerkUserInfoEntity> page = clerkUserInfoService.selectByPage(vo);
        IPage<PlayClerkPerformanceInfoReturnVo> voPage = page.convert(u -> {
            List<PlayClerkLevelInfoEntity> clerkLevelInfoEntity = playClerkLevelInfoService.selectAll();
            String startTime = vo.getEndOrderTime() != null ? vo.getEndOrderTime().get(0) : "";
            String endTime = vo.getEndOrderTime() != null ? vo.getEndOrderTime().get(1) : "";
            List<PlayOrderInfoEntity> orderInfoEntities = playOrderInfoService.clerkSelectOrderInfoList(u.getId(),
                    startTime, endTime);
            List<PlayPersonnelGroupInfoEntity> groupInfoEntities = playPersonnelGroupInfoService.selectAll();
            return playClerkPerformanceService.getClerkPerformanceInfo(u, orderInfoEntities, clerkLevelInfoEntity,
                    groupInfoEntities);
        });
        voPage.setRecords(voPage.getRecords().stream()
                .sorted(Comparator.comparing(PlayClerkPerformanceInfoReturnVo::getOrderNumber).reversed())
                .collect(Collectors.toList()));
        return R.ok(voPage);
    }

    @ApiOperation(value = "按日查询业绩", notes = "按日期查询店员业绩统计信息")
    @ApiResponses({@ApiResponse(code = 200, message = "操作成功")})
    @PostMapping("/listByTime")
    public R listByTime(
            @ApiParam(value = "查询条件", required = true) @Validated @RequestBody PlayClerkPerformanceInfoQueryVo vo) {
        // 时间范围
        List<String> dates = getDateRangeByDay(vo.getEndOrderTime().get(0), vo.getEndOrderTime().get(1));
        // 指定时间内所有订单
        // List<PlayOrderInfoEntity> orderInfoEntities =
        // playOrderInfoService.listByTime("", vo.getEndOrderTime().get(0),
        // vo.getEndOrderTime().get(1), Stream.of("2",
        // "3").collect(Collectors.toList()));
        List<PlayOrderInfoEntity> orderInfoEntities = playOrderInfoService.listByTime("", vo.getEndOrderTime().get(0),
                vo.getEndOrderTime().get(1), new ArrayList<>());
        List<PlayClerkPerformanceInfoReturnVo> orders = new ArrayList<>(dates.size());
        for (String date : dates) {
            orders.add(getPerformanceInfo(date, orderInfoEntities));
        }
        Map<String, Object> resultMap = new HashMap<>(orders.size());
        resultMap.put("dates", dates);
        resultMap.put("orderToTal", getTotalInfo(orderInfoEntities));
        resultMap.put("list", orders);
        return R.ok(resultMap);
    }

    @ApiOperation(value = "按月查询业绩", notes = "按月份查询店员业绩统计信息")
    @ApiResponses({@ApiResponse(code = 200, message = "操作成功")})
    @PostMapping("/listByMonth")
    public R listByMonth(
            @ApiParam(value = "查询条件", required = true) @Validated @RequestBody PlayClerkPerformanceInfoQueryVo vo) {
        String startTime = LocalDate.of(Integer.parseInt(vo.getEndOrderTime().get(0).split("-")[0]),
                Integer.parseInt(vo.getEndOrderTime().get(0).split("-")[1]), 1).toString() + " 00:00:00";
        LocalDate endDate = LocalDate.of(Integer.parseInt(vo.getEndOrderTime().get(1).split("-")[0]),
                Integer.parseInt(vo.getEndOrderTime().get(1).split("-")[1]), 1);
        String endTime = endDate.with(TemporalAdjusters.lastDayOfMonth()) + " 23:59:59";

        List<String> dates = getDateRangeByMonth(startTime, endTime);
        // 指定时间内所有订单
        // List<PlayOrderInfoEntity> orderInfoEntities =
        // playOrderInfoService.listByTime("", startTime, endTime,
        // Stream.of("4").collect(Collectors.toList()));
        List<PlayOrderInfoEntity> orderInfoEntities = playOrderInfoService.listByTime("", startTime, endTime,
                new ArrayList<>());
        List<PlayClerkPerformanceInfoReturnVo> orders = new ArrayList<>(dates.size());
        for (String date : dates) {
            orders.add(getPerformanceInfo(date, orderInfoEntities));
        }
        Map<String, Object> resultMap = new HashMap<>(orders.size());
        resultMap.put("dates", dates);
        resultMap.put("orderToTal", getTotalInfo(orderInfoEntities));
        resultMap.put("list", orders);
        return R.ok(resultMap);
    }

    /**
     * 根据订单开始时间和结束时间，组装订单统计信息的查询时间范围
     *
     * @param startTime
     *            开始时间
     * @param endTime
     *            结束时间
     * @return 时间范围
     */
    public List<String> getDateRangeByDay(String startTime, String endTime) {
        List<String> dates = new ArrayList<>();
        LocalDateTime date1 = LocalDateTime.parse(startTime, DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
        LocalDateTime date2 = LocalDateTime.parse(endTime, DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
        while (!date1.isAfter(date2)) {
            dates.add(date1.toLocalDate().toString());
            date1 = date1.plusDays(1);

        }
        return dates;
    }

    /**
     * 根据订单开始时间和结束时间，组装订单统计信息的查询时间范围
     *
     * @param startTime
     *            开始时间
     * @param endTime
     *            结束时间
     * @return 时间范围
     */
    public List<String> getDateRangeByMonth(String startTime, String endTime) {
        List<String> dates = new ArrayList<>();
        LocalDateTime date1 = LocalDateTime.parse(startTime, DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
        LocalDateTime date2 = LocalDateTime.parse(endTime, DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
        while (!date1.isAfter(date2)) {
            dates.add(date1.getYear() + "-" + date1.getMonthValue());
            date1 = date1.plusMonths(1);

        }
        return dates;
    }

    public PlayClerkPerformanceInfoReturnVo getTotalInfo(List<PlayOrderInfoEntity> orderInfoEntities) {
        Set<String> customIds = new HashSet<>();
        int orderContinueNumber = 0;
        int orderRefundNumber = 0;
        int ordersExpiredNumber = 0;
        BigDecimal orderMoney = BigDecimal.ZERO;
        BigDecimal finalAmount = BigDecimal.ZERO;
        BigDecimal orderFirstAmount = BigDecimal.ZERO;
        BigDecimal orderTotalAmount = BigDecimal.ZERO;
        BigDecimal orderRewardAmount = BigDecimal.ZERO;
        BigDecimal orderRefundAmount = BigDecimal.ZERO;
        BigDecimal estimatedRevenue = BigDecimal.ZERO;
        for (PlayOrderInfoEntity orderInfoEntity : orderInfoEntities) {
            customIds.add(orderInfoEntity.getPurchaserBy());
            finalAmount = finalAmount.add(orderInfoEntity.getFinalAmount());
            orderMoney = orderMoney.add(orderInfoEntity.getOrderMoney());
            if ("1".equals(orderInfoEntity.getFirstOrder())) {
                orderFirstAmount = orderFirstAmount.add(orderInfoEntity.getFinalAmount());
            } else {
                orderContinueNumber++;
                orderTotalAmount = orderTotalAmount.add(orderInfoEntity.getFinalAmount());
            }
            if ("2".equals(orderInfoEntity.getPlaceType())) {
                orderRewardAmount = orderRewardAmount.add(orderInfoEntity.getFinalAmount());
            }
            if ("1".equals(orderInfoEntity.getRefundType())) {
                orderRefundNumber++;
                orderRefundAmount = orderRefundAmount.add(orderInfoEntity.getRefundAmount());
            }
            if ("1".equals(orderInfoEntity.getOrdersExpiredState())) {
                ordersExpiredNumber++;
            }
        }
        PlayClerkPerformanceInfoReturnVo returnVo = new PlayClerkPerformanceInfoReturnVo();
        returnVo.setOrderNumber(orderInfoEntities.size());
        returnVo.setOrderContinueNumber(orderContinueNumber);
        returnVo.setOrderRefundNumber(orderRefundNumber);
        returnVo.setOrdersExpiredNumber(ordersExpiredNumber);
        returnVo.setFinalAmount(finalAmount);
        returnVo.setOrderFirstAmount(orderFirstAmount);
        returnVo.setOrderTotalAmount(orderTotalAmount);
        returnVo.setOrderRewardAmount(orderRewardAmount);
        returnVo.setOrderRefundAmount(orderRefundAmount);
        returnVo.setCustomNumber(customIds.size());
        returnVo.setEstimatedRevenue(estimatedRevenue);
        returnVo.setOrderMoney(estimatedRevenue);
        return returnVo;
    }
    /**
     * 组装统计数据
     *
     * @param performanceDate
     *            统计时间
     * @param orderInfoEntities
     *            订单列表
     * @return 统计数据
     */

    public PlayClerkPerformanceInfoReturnVo getPerformanceInfo(String performanceDate,
            List<PlayOrderInfoEntity> orderInfoEntities) {

        List<PlayOrderInfoEntity> itemOrderInfo = new ArrayList<>();
        for (PlayOrderInfoEntity orderInfoEntity : orderInfoEntities) {
            // 接单时间，在指定日期的 00:00:00点到23:59：59之间
            if (orderInfoEntity.getPurchaserTime()
                    .isAfter(LocalDateTime.parse(performanceDate + " 00:00:00",
                            DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")))
                    && orderInfoEntity.getPurchaserTime().isBefore(LocalDateTime.parse(performanceDate + " 23:59:59",
                            DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")))) {
                itemOrderInfo.add(orderInfoEntity);
            }
        }

        Set<String> customIds = new HashSet<>();
        int orderContinueNumber = 0;
        int orderRefundNumber = 0;
        int ordersExpiredNumber = 0;
        BigDecimal orderMoney = BigDecimal.ZERO;
        BigDecimal finalAmount = BigDecimal.ZERO;
        BigDecimal orderFirstAmount = BigDecimal.ZERO;
        BigDecimal orderTotalAmount = BigDecimal.ZERO;
        BigDecimal orderRewardAmount = BigDecimal.ZERO;
        BigDecimal orderRefundAmount = BigDecimal.ZERO;
        BigDecimal estimatedRevenue = BigDecimal.ZERO;
        for (PlayOrderInfoEntity orderInfoEntity : itemOrderInfo) {
            customIds.add(orderInfoEntity.getPurchaserBy());
            finalAmount = finalAmount.add(orderInfoEntity.getFinalAmount());
            orderMoney = orderMoney.add(orderInfoEntity.getOrderMoney());
            if ("1".equals(orderInfoEntity.getFirstOrder())) {
                orderFirstAmount = orderFirstAmount.add(orderInfoEntity.getFinalAmount());
            } else {
                orderContinueNumber++;
                orderTotalAmount = orderTotalAmount.add(orderInfoEntity.getFinalAmount());
            }
            if ("2".equals(orderInfoEntity.getPlaceType())) {
                orderRewardAmount = orderRewardAmount.add(orderInfoEntity.getFinalAmount());
            }
            if ("1".equals(orderInfoEntity.getRefundType())) {
                orderRefundNumber++;
                orderRefundAmount = orderRefundAmount.add(orderInfoEntity.getRefundAmount());
            }
            if ("1".equals(orderInfoEntity.getOrdersExpiredState())) {
                ordersExpiredNumber++;
            }
        }
        PlayClerkPerformanceInfoReturnVo returnVo = new PlayClerkPerformanceInfoReturnVo();
        returnVo.setPerformanceDate(performanceDate);
        returnVo.setOrderNumber(itemOrderInfo.size());
        returnVo.setOrderContinueNumber(orderContinueNumber);
        returnVo.setOrderRefundNumber(orderRefundNumber);
        returnVo.setOrdersExpiredNumber(ordersExpiredNumber);
        returnVo.setFinalAmount(finalAmount);
        returnVo.setOrderFirstAmount(orderFirstAmount);
        returnVo.setOrderTotalAmount(orderTotalAmount);
        returnVo.setOrderRewardAmount(orderRewardAmount);
        returnVo.setOrderRefundAmount(orderRefundAmount);
        returnVo.setCustomNumber(customIds.size());
        returnVo.setEstimatedRevenue(estimatedRevenue);
        returnVo.setOrderMoney(estimatedRevenue);
        return returnVo;
    }

}


--- File: play-admin/src/main/java/com/starry/admin/modules/statistics/module/vo/PlayClerkPerformanceInfoQueryVo.java ---
package com.starry.admin.modules.statistics.module.vo;

import com.starry.common.domain.BasePageEntity;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import java.util.List;
import lombok.Data;
import lombok.EqualsAndHashCode;

/**
 * @author admin
 * @since 2024/6/10 下午8:58
 **/
@EqualsAndHashCode(callSuper = true)
@Data
@ApiModel(value = "店员业绩查询参数", description = "查询店员业绩统计信息的条件参数")
public class PlayClerkPerformanceInfoQueryVo extends BasePageEntity {

    /**
     * 分组ID
     */
    @ApiModelProperty(value = "分组ID", example = "1", notes = "店员所属分组ID")
    private String groupId;

    /**
     * 店员ID
     */
    @ApiModelProperty(value = "店员ID", example = "1", notes = "店员ID")
    private String clerkId;

    /**
     * 店员性别
     */
    @ApiModelProperty(value = "店员性别", example = "1", notes = "店员性别，1男2女")
    private String sex;

    /**
     * 店员上架状态
     */
    @ApiModelProperty(value = "上架状态", example = "1", notes = "店员上架状态，1上架0下架")
    private String listingState;

    /**
     * 订单状态ID
     */
    @ApiModelProperty(value = "订单状态", example = "[\"1\",\"2\"]", notes = "订单状态ID列表")
    private List<String> orderStatus;
    /**
     * 完成订单时间
     */
    @ApiModelProperty(value = "订单时间范围", example = "[\"2024-01-01 00:00:00\",\"2024-12-31 23:59:59\"]", notes = "订单开始和结束时间")
    private List<String> endOrderTime;
}


--- File: play-admin/src/main/java/com/starry/admin/modules/statistics/module/vo/PlayClerkPerformanceInfoReturnVo.java ---
package com.starry.admin.modules.statistics.module.vo;

import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import java.math.BigDecimal;
import lombok.Data;

/**
 * @author admin
 * @since 2024/6/10 下午8:58
 **/
@Data
@ApiModel(value = "店员业绩返回数据", description = "店员业绩统计信息的返回数据")
public class PlayClerkPerformanceInfoReturnVo {

    /**
     * 店员ID
     */
    @ApiModelProperty(value = "店员ID", example = "1")
    private String clerkId;

    /**
     * 店员昵称
     */
    @ApiModelProperty(value = "店员昵称", example = "张三")
    private String clerkNickname;

    /**
     * 店员昵称
     */
    @ApiModelProperty(value = "店员性别", example = "1", notes = "1男2女")
    private String clerkSex;
    /**
     * 分组名称
     */
    @ApiModelProperty(value = "分组名称", example = "销售组")
    private String groupName = "";
    /**
     * 分组名称
     */
    @ApiModelProperty(value = "等级名称", example = "初级")
    private String levelName = "";
    /**
     * 在线时长
     */
    @ApiModelProperty(value = "在线时长", example = "120", notes = "单位：分钟")
    private Integer onlineDuration = 0;
    /**
     * 订单总数
     */
    @ApiModelProperty(value = "订单总数", example = "10")
    private Integer orderNumber = 0;
    /**
     * 首单数
     */
    @ApiModelProperty(value = "首单数", example = "5")
    private Integer orderFirstNumber = 0;
    /**
     * 续单数
     */
    @ApiModelProperty(value = "续单数", example = "5")
    private Integer orderContinueNumber = 0;
    /**
     * 退款单数
     */
    @ApiModelProperty(value = "退款单数", example = "1")
    private Integer orderRefundNumber = 0;
    /**
     * 超时未接单数
     */
    @ApiModelProperty(value = "超时未接单数", example = "1")
    private Integer ordersExpiredNumber = 0;

    /**
     * 订单金额
     */
    @ApiModelProperty(value = "订单金额", example = "1000.00")
    private BigDecimal orderMoney;

    /**
     * 订单最终金额（支付金额）
     */
    @ApiModelProperty(value = "支付金额", example = "900.00", notes = "订单最终金额（支付金额）")
    private BigDecimal finalAmount = BigDecimal.ZERO;

    /**
     * 充值金额
     */
    @ApiModelProperty(value = "充值金额", example = "500.00")
    private BigDecimal rechargeAmount = BigDecimal.ZERO;
    /**
     * 首单金额
     */
    @ApiModelProperty(value = "首单金额", example = "300.00")
    private BigDecimal orderFirstAmount = BigDecimal.ZERO;
    /**
     * 续单金额
     */
    @ApiModelProperty(value = "续单金额", example = "600.00")
    private BigDecimal orderTotalAmount = BigDecimal.ZERO;
    /**
     * 打赏金额
     */
    @ApiModelProperty(value = "打赏金额", example = "100.00")
    private BigDecimal orderRewardAmount = BigDecimal.ZERO;
    /**
     * 退款金额
     */
    @ApiModelProperty(value = "退款金额", example = "50.00")
    private BigDecimal orderRefundAmount = BigDecimal.ZERO;
    /**
     * 用户数
     */
    @ApiModelProperty(value = "用户数", example = "8")
    private Integer customNumber = 0;

    /**
     * 连续用户数
     */
    @ApiModelProperty(value = "连续用户数", example = "3")
    private final Integer continuousCustomNumber = 0;
    /**
     * 续单比例
     */
    @ApiModelProperty(value = "续单比例", example = "0.5", notes = "续单数/订单总数")
    private final BigDecimal orderContinueProportion = BigDecimal.ZERO;
    /**
     * 订单平均价格
     */
    @ApiModelProperty(value = "订单平均价格", example = "90.00")
    private final BigDecimal averageUnitPrice = BigDecimal.ZERO;

    /**
     * 续费率
     */
    @ApiModelProperty(value = "续费率", example = "0.6")
    private BigDecimal orderContinueRechargeProportion = BigDecimal.ZERO;
    /**
     * 退单率
     */
    @ApiModelProperty(value = "退单率", example = "0.1")
    private BigDecimal orderChargebackProportion = BigDecimal.ZERO;
    /**
     * 续客率
     */
    @ApiModelProperty(value = "续客率", example = "0.4")
    private BigDecimal continuousCustomProportion = BigDecimal.ZERO;
    /**
     * 店员预计收入
     */
    @ApiModelProperty(value = "预计收入", example = "450.00")
    private BigDecimal estimatedRevenue = BigDecimal.ZERO;

    /**
     * 统计时间
     */
    @ApiModelProperty(value = "统计时间", example = "2024-06-01")
    private String performanceDate;

}


--- File: play-admin/src/main/java/com/starry/admin/modules/statistics/service/IPlayClerkPerformanceService.java ---
package com.starry.admin.modules.statistics.service;

import com.starry.admin.modules.clerk.module.entity.PlayClerkLevelInfoEntity;
import com.starry.admin.modules.clerk.module.entity.PlayClerkUserInfoEntity;
import com.starry.admin.modules.order.module.entity.PlayOrderInfoEntity;
import com.starry.admin.modules.personnel.module.entity.PlayPersonnelGroupInfoEntity;
import com.starry.admin.modules.statistics.module.vo.PlayClerkPerformanceInfoReturnVo;
import java.util.List;

/**
 * @author admin
 * @since 2024/6/15 下午3:29
 **/
public interface IPlayClerkPerformanceService {

    /**
     * 根据店员订单ID，生成店员业绩信息
     *
     * @param userInfo
     *            店员信息
     * @param orderInfoEntities
     *            店员订单列表
     * @param clerkLevelInfoEntity
     *            店员等级信息
     * @param groupInfoEntities
     *            店员分组信息
     * @return 业绩信息
     */
    PlayClerkPerformanceInfoReturnVo getClerkPerformanceInfo(PlayClerkUserInfoEntity userInfo,
            List<PlayOrderInfoEntity> orderInfoEntities, List<PlayClerkLevelInfoEntity> clerkLevelInfoEntity,
            List<PlayPersonnelGroupInfoEntity> groupInfoEntities);
}


--- File: play-admin/src/main/java/com/starry/admin/modules/statistics/service/impl/PlayClerkPerformanceServiceImpl.java ---
package com.starry.admin.modules.statistics.service.impl;

import com.starry.admin.modules.clerk.module.entity.PlayClerkLevelInfoEntity;
import com.starry.admin.modules.clerk.module.entity.PlayClerkUserInfoEntity;
import com.starry.admin.modules.order.module.entity.PlayOrderInfoEntity;
import com.starry.admin.modules.personnel.module.entity.PlayPersonnelGroupInfoEntity;
import com.starry.admin.modules.statistics.module.vo.PlayClerkPerformanceInfoReturnVo;
import com.starry.admin.modules.statistics.service.IPlayClerkPerformanceService;
import java.math.BigDecimal;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import org.springframework.stereotype.Service;

/**
 * @author admin
 * @since 2024/6/15 下午3:29
 **/
@Service
public class PlayClerkPerformanceServiceImpl implements IPlayClerkPerformanceService {

    @Override
    public PlayClerkPerformanceInfoReturnVo getClerkPerformanceInfo(PlayClerkUserInfoEntity userInfo,
            List<PlayOrderInfoEntity> orderInfoEntities, List<PlayClerkLevelInfoEntity> clerkLevelInfoEntities,
            List<PlayPersonnelGroupInfoEntity> groupInfoEntities) {

        Set<String> customIds = new HashSet<>();
        int orderContinueNumber = 0;
        int orderRefundNumber = 0;
        int ordersExpiredNumber = 0;
        BigDecimal finalAmount = BigDecimal.ZERO;
        BigDecimal orderFirstAmount = BigDecimal.ZERO;
        BigDecimal orderTotalAmount = BigDecimal.ZERO;
        BigDecimal orderRewardAmount = BigDecimal.ZERO;
        BigDecimal orderRefundAmount = BigDecimal.ZERO;
        BigDecimal estimatedRevenue = BigDecimal.ZERO;
        for (PlayOrderInfoEntity orderInfoEntity : orderInfoEntities) {
            customIds.add(orderInfoEntity.getPurchaserBy());
            finalAmount = finalAmount.add(orderInfoEntity.getFinalAmount());
            if ("1".equals(orderInfoEntity.getFirstOrder())) {
                orderFirstAmount = orderFirstAmount.add(orderInfoEntity.getFinalAmount());
            } else {
                orderContinueNumber++;
                orderTotalAmount = orderTotalAmount.add(orderInfoEntity.getFinalAmount());
            }
            if ("2".equals(orderInfoEntity.getPlaceType())) {
                orderRewardAmount = orderRewardAmount.add(orderInfoEntity.getFinalAmount());
            }
            if ("1".equals(orderInfoEntity.getRefundType())) {
                orderRefundNumber++;
                orderRefundAmount = orderRefundAmount.add(orderInfoEntity.getRefundAmount());
            }
            if ("1".equals(orderInfoEntity.getOrdersExpiredState())) {
                ordersExpiredNumber++;
            }
        }
        PlayClerkPerformanceInfoReturnVo returnVo = new PlayClerkPerformanceInfoReturnVo();
        returnVo.setClerkId(userInfo.getId());
        returnVo.setClerkNickname(userInfo.getNickname());
        returnVo.setClerkSex(userInfo.getSex());
        for (PlayClerkLevelInfoEntity infoEntity : clerkLevelInfoEntities) {
            if (infoEntity.getId().equals(userInfo.getLevelId())) {
                returnVo.setLevelName(infoEntity.getName());
            }
        }
        for (PlayPersonnelGroupInfoEntity infoEntity : groupInfoEntities) {
            if (infoEntity.getId().equals(userInfo.getGroupId())) {
                returnVo.setGroupName(infoEntity.getGroupName());
            }
        }
        returnVo.setOrderNumber(orderInfoEntities.size());
        returnVo.setOrderContinueNumber(orderContinueNumber);
        returnVo.setOrderRefundNumber(orderRefundNumber);
        returnVo.setOrdersExpiredNumber(ordersExpiredNumber);
        returnVo.setFinalAmount(finalAmount);
        returnVo.setOrderFirstAmount(orderFirstAmount);
        returnVo.setOrderTotalAmount(orderTotalAmount);
        returnVo.setOrderRewardAmount(orderRewardAmount);
        returnVo.setOrderRefundAmount(orderRefundAmount);
        returnVo.setCustomNumber(customIds.size());
        returnVo.setEstimatedRevenue(estimatedRevenue);

        return returnVo;
    }
}


--- File: play-admin/src/main/java/com/starry/admin/modules/system/controller/AdministrativeAreaDictInfoController.java ---
package com.starry.admin.modules.system.controller;

import com.starry.admin.modules.system.service.ISysAdministrativeAreaDictInfoService;
import com.starry.common.result.R;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiResponse;
import io.swagger.annotations.ApiResponses;
import javax.annotation.Resource;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 * 行政区域字典信息Controller
 *
 * @author admin
 * @since 2024-04-03
 */
@Api(tags = "行政区域字典", description = "行政区域字典信息相关接口")
@RestController
@RequestMapping("/sys/area")
public class AdministrativeAreaDictInfoController {
    @Resource
    private ISysAdministrativeAreaDictInfoService playAdministrativeAreaDictInfoService;

    /**
     * 查询省/市行政区域代码
     */
    @ApiOperation(value = "获取行政区域树", notes = "查询省/市行政区域代码的树形结构")
    @ApiResponses({@ApiResponse(code = 200, message = "操作成功")})
    @PreAuthorize("@customSs.hasPermission('play:info:list')")
    @GetMapping("/tree")
    public R list() throws Exception {
        return R.ok(playAdministrativeAreaDictInfoService.selectTree("2"));
    }

}


--- File: play-admin/src/main/java/com/starry/admin/modules/system/controller/CacheController.java ---
package com.starry.admin.modules.system.controller;

import com.starry.admin.modules.system.module.entity.Cache;
import com.starry.common.constant.CacheConstants;
import com.starry.common.result.R;
import com.starry.common.utils.StringUtils;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiResponse;
import io.swagger.annotations.ApiResponses;
import java.util.*;
import javax.annotation.Resource;
import org.springframework.data.redis.connection.RedisServerCommands;
import org.springframework.data.redis.core.RedisCallback;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.*;

/**
 * @author huoqiang 缓存监控
 * @since 2022/10/17
 */
@Api(tags = "缓存监控", description = "系统缓存监控相关接口")
@RestController
@RequestMapping("/monitor/cache")
public class CacheController {

    private final static List<Cache> CACHES = new ArrayList<>();
    @Resource
    private RedisTemplate<String, String> redisTemplate;

    {
        CACHES.add(new Cache(CacheConstants.LOGIN_TOKEN_KEY, "用户信息"));
        CACHES.add(new Cache(CacheConstants.SYS_DICT_KEY, "数据字典"));
        CACHES.add(new Cache(CacheConstants.CAPTCHA_CODE_KEY, "验证码"));
    }

    @ApiOperation(value = "获取缓存信息", notes = "获取Redis服务器信息和命令统计")
    @ApiResponses({@ApiResponse(code = 200, message = "操作成功", response = Map.class)})
    @PreAuthorize("@customSs.hasPermission('monitor:cache:list')")
    @GetMapping()
    public R getInfo() throws Exception {
        Properties info = (Properties) redisTemplate.execute((RedisCallback<Object>) RedisServerCommands::info);
        Properties commandStats = (Properties) redisTemplate
                .execute((RedisCallback<Object>) connection -> connection.info("commandstats"));
        Object dbSize = redisTemplate.execute((RedisCallback<Object>) RedisServerCommands::dbSize);

        Map<String, Object> result = new HashMap<>(3);
        result.put("info", info);
        result.put("dbSize", dbSize);
        List<Map<String, String>> pieList = new ArrayList<>();
        commandStats.stringPropertyNames().forEach(key -> {
            Map<String, String> data = new HashMap<>(2);
            String property = commandStats.getProperty(key);
            data.put("name", StringUtils.removeStart(key, "cmdstat_"));
            data.put("value", StringUtils.substringBetween(property, "calls=", ",usec"));
            pieList.add(data);
        });
        result.put("commandStats", pieList);
        return R.ok(result);
    }

    @ApiOperation(value = "获取缓存名称", notes = "获取系统中所有缓存分类名称")
    @ApiResponses({@ApiResponse(code = 200, message = "操作成功", response = Cache.class, responseContainer = "List")})
    @PreAuthorize("@customSs.hasPermission('monitor:cache:list')")
    @GetMapping("/getNames")
    public R cache() {
        return R.ok(CACHES);
    }

    @ApiOperation(value = "获取缓存键名", notes = "根据缓存名称获取对应的所有缓存键")
    @ApiImplicitParam(name = "cacheName", value = "缓存名称", required = true, dataType = "String", paramType = "path")
    @ApiResponses({@ApiResponse(code = 200, message = "操作成功", response = String.class, responseContainer = "Set")})
    @PreAuthorize("@customSs.hasPermission('monitor:cache:list')")
    @GetMapping("/getKeys/{cacheName}")
    public R getCacheKeys(@PathVariable String cacheName) {
        Set<String> cacheKeys = redisTemplate.keys(cacheName + "*");
        return R.ok(cacheKeys);
    }

    @ApiOperation(value = "获取缓存值", notes = "根据缓存名称和键名获取缓存值")
    @ApiResponses({@ApiResponse(code = 200, message = "操作成功", response = Cache.class)})
    @PreAuthorize("@customSs.hasPermission('monitor:cache:list')")
    @GetMapping("/getValue/{cacheName}/{cacheKey}")
    public R getCacheValue(@PathVariable String cacheName, @PathVariable String cacheKey) {
        String cacheValue = redisTemplate.opsForValue().get(cacheKey);
        Cache cache = new Cache(cacheName, cacheKey, cacheValue);
        return R.ok(cache);
    }

    @ApiOperation(value = "清除缓存名称", notes = "清除指定缓存名称下的所有缓存")
    @ApiImplicitParam(name = "cacheName", value = "缓存名称", required = true, dataType = "String", paramType = "path")
    @ApiResponses({@ApiResponse(code = 200, message = "操作成功")})
    @PreAuthorize("@customSs.hasPermission('monitor:cache:list')")
    @DeleteMapping("/clearCacheName/{cacheName}")
    public R clearCacheName(@PathVariable String cacheName) {
        Collection<String> cacheKeys = redisTemplate.keys(cacheName + "*");
        redisTemplate.delete(cacheKeys);
        return R.ok();
    }

    @ApiOperation(value = "清除缓存键", notes = "清除指定的缓存键")
    @ApiImplicitParam(name = "cacheKey", value = "缓存键", required = true, dataType = "String", paramType = "path")
    @ApiResponses({@ApiResponse(code = 200, message = "操作成功")})
    @PreAuthorize("@customSs.hasPermission('monitor:cache:list')")
    @DeleteMapping("/clearCacheKey/{cacheKey}")
    public R clearCacheKey(@PathVariable String cacheKey) {
        redisTemplate.delete(cacheKey);
        return R.ok();
    }

    @ApiOperation(value = "清除所有缓存", notes = "清除系统中所有缓存数据")
    @ApiResponses({@ApiResponse(code = 200, message = "操作成功")})
    @PreAuthorize("@customSs.hasPermission('monitor:cache:list')")
    @DeleteMapping("/clearCacheAll")
    public R clearCacheAll() {
        Collection<String> cacheKeys = redisTemplate.keys("*");
        redisTemplate.delete(cacheKeys);
        return R.ok();
    }
}


--- File: play-admin/src/main/java/com/starry/admin/modules/system/controller/HealthController.java ---
package com.starry.admin.modules.system.controller;

import com.starry.common.result.R;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiResponse;
import io.swagger.annotations.ApiResponses;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.HashMap;
import java.util.Map;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 * Health Check Controller
 *
 * @author admin
 * @since 2025/08/30
 */
@Api(tags = "健康检查", description = "服务健康状态检查接口")
@RestController
@RequestMapping("/health")
public class HealthController {

    @ApiOperation(value = "健康检查", notes = "检查服务是否正常运行")
    @ApiResponses({@ApiResponse(code = 200, message = "服务正常运行")})
    @GetMapping
    public R health() {
        Map<String, Object> data = new HashMap<>();
        data.put("status", "UP");
        data.put("message", "Hello, I'm alive!");
        data.put("timestamp", LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
        data.put("service", "peipei-backend");
        return R.ok(data);
    }

    @ApiOperation(value = "简单检查", notes = "最简单的存活检查")
    @ApiResponses({@ApiResponse(code = 200, message = "服务正常")})
    @GetMapping("/ping")
    public R ping() {
        return R.ok("pong");
    }
}


--- File: play-admin/src/main/java/com/starry/admin/modules/system/controller/LoginController.java ---
package com.starry.admin.modules.system.controller;

import com.starry.admin.common.component.JwtToken;
import com.starry.admin.common.domain.LoginUser;
import com.starry.admin.modules.system.module.entity.SysMenuEntity;
import com.starry.admin.modules.system.module.entity.SysUserEntity;
import com.starry.admin.modules.system.service.LoginService;
import com.starry.admin.modules.system.service.SysMenuService;
import com.starry.admin.modules.system.vo.LoginVo;
import com.starry.admin.modules.system.vo.RouterVo;
import com.starry.admin.utils.SecurityUtils;
import com.starry.common.redis.CaptchaService;
import com.starry.common.result.R;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiParam;
import io.swagger.annotations.ApiResponse;
import io.swagger.annotations.ApiResponses;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.annotation.Resource;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;

/**
 * @author admin
 */
@Api(tags = "登录管理", description = "用户登录及认证相关接口")
@RestController
public class LoginController {

    @Value("${jwt.tokenHead}")
    private String tokenHead;
    @Value(value = "${xl.login.authCode.enable}")
    private boolean needAuthCode;
    @Resource
    private LoginService loginService;
    @Resource
    private SysMenuService menuService;
    @Resource
    private CaptchaService captchaService;
    @Resource
    private JwtToken jwtToken;

    @ApiOperation(value = "旧版登录", notes = "用户登录接口，返回Token")
    @ApiResponses({@ApiResponse(code = 200, message = "操作成功", response = Map.class),
            @ApiResponse(code = 500, message = "登录失败，用户名或密码错误")})
    @PostMapping(value = "old-login")
    public R login(@ApiParam(value = "登录信息", required = true) @RequestBody LoginVo loginVo) {
        // 只有开启了验证码功能才需要验证
        if (needAuthCode) {
            String msg = captchaService.checkImageCode(loginVo.getNonceStr(), loginVo.getValue());
            if (StringUtils.isNotBlank(msg)) {
                return R.error(msg);
            }
        }
        String token = loginService.login(loginVo.getUserName(), loginVo.getPassWord());
        if (StringUtils.isBlank(token)) {
            return R.error("用户名或密码错误");
        }
        Map<String, String> tokenMap = new HashMap<>();
        tokenMap.put("token", token);
        tokenMap.put("tokenHead", tokenHead);
        return R.ok(tokenMap);
    }

    @ApiOperation(value = "获取用户信息", notes = "登录后获取当前用户的详细信息")
    @ApiResponses({@ApiResponse(code = 200, message = "操作成功", response = Map.class),
            @ApiResponse(code = 401, message = "未授权")})
    @GetMapping("get-info")
    public R getInfo() {
        SysUserEntity user = SecurityUtils.getLoginUser().getUser();
        if (user == null) {
            return R.unauthorized();
        }
        // 权限集合
        Set<String> permissions = loginService.getMenuPermission(user);
        // 角色集合
        Set<String> roles = loginService.getRolePermission(user);
        Map<String, Object> data = new HashMap<>();
        data.put("userCode", user.getUserCode());
        data.put("name", user.getRealName());
        data.put("avatar", user.getAvatar());
        data.put("roles", roles);
        data.put("permissions", permissions);
        return R.ok(data);
    }

    @ApiOperation(value = "获取路由信息", notes = "登录后获取当前用户的菜单路由信息")
    @ApiResponses({@ApiResponse(code = 200, message = "操作成功", response = RouterVo.class, responseContainer = "List")})
    @GetMapping("get-routers")
    public R getRoutes() {
        List<SysMenuEntity> menus = menuService.selectMenuTreeByUserId(SecurityUtils.getUserId());
        List<RouterVo> routerVos = menuService.buildMenus(menus);
        return R.ok(routerVos);
    }

    @ApiOperation(value = "新版登录", notes = "新版用户登录接口，支持租户")
    @ApiResponses({@ApiResponse(code = 200, message = "操作成功", response = Map.class),
            @ApiResponse(code = 500, message = "登录失败，验证码错误或用户名密码错误")})
    @PostMapping(value = "login")
    public R newLogin(@ApiParam(value = "登录信息", required = true) @RequestBody LoginVo loginVo) {
        // 只有开启了验证码功能才需要验证
        if (needAuthCode) {
            String msg = captchaService.checkImageCode(loginVo.getNonceStr(), loginVo.getValue());
            if (StringUtils.isNotBlank(msg)) {
                return R.error(msg);
            }
        }
        // 用户登录
        LoginUser userInfo = loginService.newLogin(loginVo.getUserName(), loginVo.getPassWord(),
                loginVo.getTenantKey());
        Map<String, Object> tokenMap = jwtToken.createToken(userInfo);
        return R.ok(tokenMap);
    }

    // @ApiOperation(value = "租户登录")
    // @PostMapping(value = "tenant/login")
    // public R TenantLoginVo(@RequestBody TenantLoginVo loginVo) {
    // // 只有开启了验证码功能才需要验证
    // if (needAuthCode) {
    // String msg = captchaService.checkImageCode(loginVo.getNonceStr(),
    // loginVo.getValue());
    // if (StringUtils.isNotBlank(msg)) {
    // return R.error(msg);
    // }
    // }
    // // 用户登录
    // LoginUser userInfo = loginService.tenantLogin(loginVo.getTenantkey(),
    // loginVo.getUserName(), loginVo.getPassWord());
    // Map<String, Object> tokenMap = jwtToken.createToken(userInfo);
    // return R.ok(tokenMap);
    // }
}


--- File: play-admin/src/main/java/com/starry/admin/modules/system/controller/ServerController.java ---
package com.starry.admin.modules.system.controller;

import com.starry.admin.modules.system.module.entity.Server;
import com.starry.common.result.R;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiResponse;
import io.swagger.annotations.ApiResponses;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 * @author huoqiang 服务器监控
 * @since 2022/10/17
 */
@Api(tags = "服务器监控", description = "服务器状态监控相关接口")
@RestController
@RequestMapping("/monitor/server")
public class ServerController {

    @ApiOperation(value = "获取服务器信息", notes = "获取服务器运行状态、CPU、内存等信息")
    @ApiResponses({@ApiResponse(code = 200, message = "操作成功", response = Server.class)})
    @PreAuthorize("@customSs.hasPermission('monitor:server:list')")
    @GetMapping()
    public R getInfo() throws Exception {
        Server server = new Server();
        server.setServeInfo();
        return R.ok(server);
    }
}


--- File: play-admin/src/main/java/com/starry/admin/modules/system/controller/SysDeptController.java ---
package com.starry.admin.modules.system.controller;

import com.starry.admin.common.domain.TreeSelect;
import com.starry.admin.modules.system.module.entity.SysDeptEntity;
import com.starry.admin.modules.system.service.SysDeptService;
import com.starry.common.constant.UserConstants;
import com.starry.common.result.R;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiParam;
import io.swagger.annotations.ApiResponse;
import io.swagger.annotations.ApiResponses;
import java.util.Collections;
import java.util.List;
import javax.annotation.Resource;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.*;

/**
 * <p>
 * 部门表 前端控制器
 * </p>
 *
 * @author admin
 * @since 2022-07-12
 */
@Api(tags = "部门管理", description = "系统部门相关接口")
@RestController
@RequestMapping("/ucenter/dept")
public class SysDeptController {

    @Resource
    private SysDeptService deptService;

    @ApiOperation(value = "获取部门列表", notes = "根据条件查询部门列表")
    @ApiResponses({
            @ApiResponse(code = 200, message = "操作成功", response = SysDeptEntity.class, responseContainer = "List")})
    @PreAuthorize("@customSs.hasPermission('system:dept:list')")
    @GetMapping("/list")
    public R list(@ApiParam(value = "查询条件") SysDeptEntity dept) {
        List<SysDeptEntity> depts = deptService.selectDeptList(dept);
        return R.ok(depts);
    }

    @ApiOperation(value = "添加部门", notes = "创建新的部门")
    @ApiResponses({@ApiResponse(code = 200, message = "操作成功"), @ApiResponse(code = 500, message = "添加失败，部门名称已存在")})
    @PreAuthorize("@customSs.hasPermission('system:dept:create')")
    @PostMapping("/create")
    public R creat(@ApiParam(value = "部门信息", required = true) @RequestBody SysDeptEntity dept) {
        if (UserConstants.NOT_UNIQUE.equals(deptService.checkDeptNameUnique(dept))) {
            return R.error("新增部门'" + dept.getDeptName() + "'失败，部门名称已存在");
        }
        boolean success = deptService.create(dept);
        if (success) {
            return R.ok();
        }
        return R.error("添加失败");
    }

    @ApiOperation(value = "修改部门", notes = "根据ID修改部门信息")
    @ApiImplicitParam(name = "id", value = "部门ID", required = true, dataType = "Long", paramType = "path")
    @ApiResponses({@ApiResponse(code = 200, message = "操作成功"),
            @ApiResponse(code = 500, message = "修改失败，部门名称已存在或上级部门不能是自己")})
    @PreAuthorize("@customSs.hasPermission('system:dept:edit')")
    @PostMapping("/update/{id}")
    public R update(@PathVariable Long id, @ApiParam(value = "部门信息", required = true) @RequestBody SysDeptEntity dept) {
        dept.setDeptId(id);
        if (UserConstants.NOT_UNIQUE.equals(deptService.checkDeptNameUnique(dept))) {
            return R.error("修改部门'" + dept.getDeptName() + "'失败，部门名称已存在");
        } else if (dept.getParentId().equals(id)) {
            return R.error("修改部门'" + dept.getDeptName() + "'失败，上级部门不能是自己");
        }
        boolean success = deptService.update(dept);
        if (success) {
            return R.ok();
        }
        return R.error("修改失败");
    }

    @ApiOperation(value = "删除部门", notes = "根据ID删除部门")
    @ApiImplicitParam(name = "id", value = "部门ID", required = true, dataType = "Long", paramType = "path")
    @ApiResponses({@ApiResponse(code = 200, message = "操作成功"), @ApiResponse(code = 500, message = "删除失败，存在下级部门")})
    @PreAuthorize("@customSs.hasPermission('system:dept:delete')")
    @PostMapping(value = "/delete/{id}")
    public R delete(@PathVariable("id") Long deptId) {
        if (deptService.hasChildByDeptId(deptId)) {
            return R.error("存在下级部门,不允许删除");
        }
        boolean success = deptService.delete(Collections.singletonList(deptId));
        if (success) {
            return R.ok();
        }
        return R.error("删除失败");
    }

    @ApiOperation(value = "获取部门下拉树列表", notes = "获取部门的树形结构数据")
    @ApiResponses({@ApiResponse(code = 200, message = "操作成功", response = TreeSelect.class, responseContainer = "List")})
    @GetMapping("/treeselect")
    public R treeselect(@ApiParam(value = "查询条件") SysDeptEntity dept) {
        List<SysDeptEntity> list = deptService.selectDeptList(dept);
        List<TreeSelect> treeList = deptService.buildDeptTreeSelect(list);
        return R.ok(treeList);
    }

    @ApiOperation(value = "获取部门详情", notes = "根据部门ID获取详细信息")
    @ApiImplicitParam(name = "deptId", value = "部门ID", required = true, dataType = "Long", paramType = "path")
    @ApiResponses({@ApiResponse(code = 200, message = "操作成功", response = SysDeptEntity.class)})
    @GetMapping(value = "/{deptId}")
    public R getInfo(@PathVariable Long deptId) {
        return R.ok(deptService.getById(deptId));
    }

}


--- File: play-admin/src/main/java/com/starry/admin/modules/system/controller/SysDictController.java ---
package com.starry.admin.modules.system.controller;

import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.starry.admin.modules.system.module.entity.SysDictEntity;
import com.starry.admin.modules.system.service.ISysDictService;
import com.starry.admin.modules.system.vo.SysDictVo;
import com.starry.common.annotation.Log;
import com.starry.common.enums.BusinessType;
import com.starry.common.result.R;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiParam;
import io.swagger.annotations.ApiResponse;
import io.swagger.annotations.ApiResponses;
import java.util.List;
import javax.annotation.Resource;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.*;

/**
 * 字典表Controller
 *
 * @author admin
 */
@Api(tags = "字典类型管理", description = "系统字典类型相关接口")
@RestController
@RequestMapping("/system/dict")
public class SysDictController {
    @Resource
    private ISysDictService xlDictService;

    /**
     * 查询字典表列表
     */
    @ApiOperation(value = "查询字典类型列表", notes = "分页查询字典类型列表")
    @ApiResponses({
            @ApiResponse(code = 200, message = "操作成功", response = SysDictEntity.class, responseContainer = "Page")})
    @PreAuthorize("@customSs.hasPermission('system:dict:list')")
    @GetMapping("/list")
    public R list(@ApiParam(value = "查询条件") SysDictVo dictVo) {
        Page<SysDictEntity> list = xlDictService.selectXlDictList(dictVo);
        return R.ok(list);
    }

    /**
     * 获取字典表详细信息
     */
    @ApiOperation(value = "获取字典类型详情", notes = "根据ID获取字典类型详细信息")
    @ApiImplicitParam(name = "dictId", value = "字典ID", required = true, dataType = "Long", paramType = "path")
    @ApiResponses({@ApiResponse(code = 200, message = "操作成功", response = SysDictEntity.class)})
    @PreAuthorize("@customSs.hasPermission('system:dict:query')")
    @GetMapping(value = "/{dictId}")
    public R getInfo(@PathVariable("dictId") Long dictId) {
        return R.ok(xlDictService.selectXlDictByDictId(dictId));
    }

    /**
     * 新增字典表
     */
    @ApiOperation(value = "新增字典类型", notes = "创建新的字典类型")
    @ApiResponses({@ApiResponse(code = 200, message = "操作成功"), @ApiResponse(code = 500, message = "添加失败")})
    @PreAuthorize("@customSs.hasPermission('system:dict:create')")
    @Log(title = "字典表", businessType = BusinessType.INSERT)
    @PostMapping("/create")
    public R create(@ApiParam(value = "字典类型信息", required = true) @RequestBody SysDictEntity sysDictEntity) {
        boolean success = xlDictService.create(sysDictEntity);
        if (success) {
            return R.ok();
        }
        return R.error("添加失败");
    }

    /**
     * 修改字典表
     */
    @ApiOperation(value = "修改字典类型", notes = "根据ID修改字典类型")
    @ApiImplicitParam(name = "dictId", value = "字典ID", required = true, dataType = "Long", paramType = "path")
    @ApiResponses({@ApiResponse(code = 200, message = "操作成功"), @ApiResponse(code = 500, message = "修改失败")})
    @PreAuthorize("@customSs.hasPermission('system:dict:edit')")
    @Log(title = "字典表", businessType = BusinessType.UPDATE)
    @PostMapping(value = "/update/{dictId}")
    public R update(@PathVariable Long dictId,
            @ApiParam(value = "字典类型信息", required = true) @RequestBody SysDictEntity sysDictEntity) {
        sysDictEntity.setDictId(dictId);
        boolean success = xlDictService.update(sysDictEntity);
        if (success) {
            return R.ok();
        }
        return R.error("修改失败");
    }

    /**
     * 删除字典表
     */
    @ApiOperation(value = "删除字典类型", notes = "根据ID批量删除字典类型")
    @ApiImplicitParam(name = "dictIds", value = "字典ID数组", required = true, dataType = "Long[]", paramType = "path")
    @ApiResponses({@ApiResponse(code = 200, message = "操作成功", response = Boolean.class)})
    @PreAuthorize("@customSs.hasPermission('system:dict:remove')")
    @Log(title = "字典表", businessType = BusinessType.DELETE)
    @DeleteMapping("/{dictIds}")
    public R remove(@PathVariable Long[] dictIds) {
        return R.ok(xlDictService.deleteXlDictByDictIds(dictIds));
    }

    @ApiOperation(value = "获取字典选择列表", notes = "获取所有字典类型的下拉选择数据")
    @ApiResponses({
            @ApiResponse(code = 200, message = "操作成功", response = SysDictEntity.class, responseContainer = "List")})
    @GetMapping("/optionselect")
    public R optionselect() {
        List<SysDictEntity> list = xlDictService.selectDictTypeAll();
        return R.ok(list);
    }

    /**
     * 刷新字典缓存
     */
    @ApiOperation(value = "刷新字典缓存", notes = "清除并重新加载字典缓存")
    @ApiResponses({@ApiResponse(code = 200, message = "操作成功")})
    @PreAuthorize("@customSs.hasPermission('system:dict:remove')")
    @Log(title = "字典类型", businessType = BusinessType.CLEAN)
    @DeleteMapping("/refreshCache")
    public R refreshCache() {
        xlDictService.resetDictCache();
        return R.ok();
    }
}


--- File: play-admin/src/main/java/com/starry/admin/modules/system/controller/SysDictDataController.java ---
package com.starry.admin.modules.system.controller;

import cn.hutool.core.collection.CollectionUtil;
import com.starry.admin.modules.system.module.entity.SysDictDataEntity;
import com.starry.admin.modules.system.service.ISysDictDataService;
import com.starry.admin.modules.system.vo.SysDictDataVo;
import com.starry.common.annotation.Log;
import com.starry.common.enums.BusinessType;
import com.starry.common.result.R;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiParam;
import io.swagger.annotations.ApiResponse;
import io.swagger.annotations.ApiResponses;
import java.util.ArrayList;
import java.util.List;
import javax.annotation.Resource;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.*;

/**
 * 字典数据表Controller
 *
 * @author admin
 */
@Api(tags = "字典数据管理", description = "系统字典数据项相关接口")
@RestController
@RequestMapping("/system/dict/data")
public class SysDictDataController {
    @Resource
    private ISysDictDataService xlDictDataService;

    /**
     * 查询字典数据表列表
     */
    @ApiOperation(value = "查询字典数据列表", notes = "根据条件查询字典数据项列表")
    @ApiResponses({
            @ApiResponse(code = 200, message = "操作成功", response = SysDictDataEntity.class, responseContainer = "Page")})
    @PreAuthorize("@customSs.hasPermission('system:dict:list')")
    @GetMapping("/list")
    public R list(@ApiParam(value = "查询条件") SysDictDataVo sysDictDataEntity) {
        return R.ok(xlDictDataService.selectXlDictDataList(sysDictDataEntity));
    }

    /**
     * 获取字典数据表详细信息
     */
    @ApiOperation(value = "获取字典数据详情", notes = "根据ID获取字典数据项详细信息")
    @ApiImplicitParam(name = "dictDataId", value = "字典数据ID", required = true, dataType = "Long", paramType = "path")
    @ApiResponses({@ApiResponse(code = 200, message = "操作成功", response = SysDictDataEntity.class)})
    @PreAuthorize("@customSs.hasPermission('system:dict:query')")
    @GetMapping(value = "/{dictDataId}")
    public R getInfo(@PathVariable("dictDataId") Long dictDataId) {
        return R.ok(xlDictDataService.selectXlDictDataByDictDataId(dictDataId));
    }

    /**
     * 新增字典数据表
     */
    @ApiOperation(value = "新增字典数据", notes = "创建新的字典数据项")
    @ApiResponses({@ApiResponse(code = 200, message = "操作成功"), @ApiResponse(code = 500, message = "添加失败")})
    @PreAuthorize("@customSs.hasPermission('system:dict:create')")
    @Log(title = "字典数据表", businessType = BusinessType.INSERT)
    @PostMapping("/create")
    public R create(@ApiParam(value = "字典数据信息", required = true) @RequestBody SysDictDataEntity sysDictDataEntity) {
        boolean success = xlDictDataService.create(sysDictDataEntity);
        if (success) {
            return R.ok();
        }
        return R.error("添加失败");
    }

    /**
     * 修改字典数据表
     */
    @ApiOperation(value = "修改字典数据", notes = "根据ID修改字典数据项")
    @ApiImplicitParam(name = "dictDataId", value = "字典数据ID", required = true, dataType = "Long", paramType = "path")
    @ApiResponses({@ApiResponse(code = 200, message = "操作成功"), @ApiResponse(code = 500, message = "修改失败")})
    @PreAuthorize("@customSs.hasPermission('system:dict:edit')")
    @Log(title = "字典数据表", businessType = BusinessType.UPDATE)
    @PostMapping(value = "/update/{dictDataId}")
    public R update(@PathVariable Long dictDataId,
            @ApiParam(value = "字典数据信息", required = true) @RequestBody SysDictDataEntity sysDictDataEntity) {
        sysDictDataEntity.setDictDataId(dictDataId);
        boolean success = xlDictDataService.update(sysDictDataEntity);
        if (success) {
            return R.ok();
        }
        return R.error("修改失败");
    }

    /**
     * 删除字典数据表
     */
    @ApiOperation(value = "删除字典数据", notes = "根据ID批量删除字典数据项")
    @ApiImplicitParam(name = "dictDataIds", value = "字典数据ID数组", required = true, dataType = "Long[]", paramType = "path")
    @ApiResponses({@ApiResponse(code = 200, message = "操作成功", response = Boolean.class)})
    @PreAuthorize("@customSs.hasPermission('system:dict:remove')")
    @Log(title = "字典数据表", businessType = BusinessType.DELETE)
    @DeleteMapping("/{dictDataIds}")
    public R remove(@PathVariable Long[] dictDataIds) {
        return R.ok(xlDictDataService.deleteXlDictDataByDictDataIds(dictDataIds));
    }

    /**
     * 根据字典类型查询字典数据信息
     */
    @ApiOperation(value = "根据类型查询字典数据", notes = "根据字典类型查询对应的字典数据项列表")
    @ApiImplicitParam(name = "dictType", value = "字典类型", required = true, dataType = "String", paramType = "path")
    @ApiResponses({
            @ApiResponse(code = 200, message = "操作成功", response = SysDictDataEntity.class, responseContainer = "List")})
    @GetMapping(value = "/type/{dictType}")
    public R getDictDataByType(@PathVariable String dictType) {
        List<SysDictDataEntity> list = xlDictDataService.selectDictDataByType(dictType);
        if (CollectionUtil.isEmpty(list)) {
            list = new ArrayList<>();
        }
        return R.ok(list);
    }
}


--- File: play-admin/src/main/java/com/starry/admin/modules/system/controller/SysLoginLogController.java ---
package com.starry.admin.modules.system.controller;

import com.baomidou.mybatisplus.core.metadata.IPage;
import com.starry.admin.modules.system.module.entity.SysLoginLogEntity;
import com.starry.admin.modules.system.module.entity.SysLoginLogVo;
import com.starry.admin.modules.system.service.ISysLoginLogService;
import com.starry.admin.utils.ExcelUtils;
import com.starry.common.annotation.Log;
import com.starry.common.enums.BusinessType;
import com.starry.common.result.R;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiParam;
import io.swagger.annotations.ApiResponse;
import io.swagger.annotations.ApiResponses;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletResponse;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.*;

/**
 * 系统访问日志表Controller
 *
 * @author admin
 */
@Api(tags = "登录日志管理", description = "系统访问日志相关接口")
@RestController
@RequestMapping("/system/loginlog")
public class SysLoginLogController {
    @Resource
    private ISysLoginLogService xlLoginLogService;

    /**
     * 查询系统访问日志表列表
     */
    @ApiOperation(value = "查询日志列表", notes = "分页查询系统访问日志列表")
    @ApiResponses({
            @ApiResponse(code = 200, message = "操作成功", response = SysLoginLogEntity.class, responseContainer = "Page")})
    @PreAuthorize("@customSs.hasPermission('system:loginlog:list')")
    @GetMapping("/list")
    public R list(@ApiParam(value = "查询条件") SysLoginLogVo vo) {
        IPage<SysLoginLogEntity> list = xlLoginLogService.selectXlLoginLogList(vo);
        return R.ok(list);
    }

    /**
     * 导出系统访问日志表列表
     */
    @ApiOperation(value = "导出日志", notes = "导出系统访问日志表列表")
    @ApiResponses({@ApiResponse(code = 200, message = "操作成功")})
    @PreAuthorize("@customSs.hasPermission('system:loginlog:export')")
    @Log(title = "登录日志", businessType = BusinessType.EXPORT)
    @PostMapping("/export")
    public void export(HttpServletResponse response, @ApiParam(value = "查询条件") SysLoginLogVo vo) {
        IPage<SysLoginLogEntity> list = xlLoginLogService.selectXlLoginLogList(vo);
        ExcelUtils.exportEasyExcel(response, SysLoginLogEntity.class, list.getRecords(), "系统访问日志表数据");
    }

    /**
     * 获取系统访问日志表详细信息
     */
    @ApiOperation(value = "获取日志详情", notes = "根据ID获取系统访问日志详细信息")
    @ApiImplicitParam(name = "loginId", value = "日志ID", required = true, dataType = "Long", paramType = "path")
    @ApiResponses({@ApiResponse(code = 200, message = "操作成功", response = SysLoginLogEntity.class)})
    @PreAuthorize("@customSs.hasPermission('system:log:query')")
    @GetMapping(value = "/{loginId}")
    public R getInfo(@PathVariable("loginId") Long loginId) {
        return R.ok(xlLoginLogService.selectXlLoginLogByLoginId(loginId));
    }

    /**
     * 删除系统访问日志表
     */
    @ApiOperation(value = "删除日志", notes = "根据ID批量删除系统访问日志")
    @ApiImplicitParam(name = "loginIds", value = "日志ID数组", required = true, dataType = "Long[]", paramType = "path")
    @ApiResponses({@ApiResponse(code = 200, message = "操作成功", response = Boolean.class)})
    @PreAuthorize("@customSs.hasPermission('system:loginlog:remove')")
    @Log(title = "登录日志", businessType = BusinessType.DELETE)
    @DeleteMapping("/{loginIds}")
    public R remove(@PathVariable Long[] loginIds) {
        return R.ok(xlLoginLogService.deleteXlLoginLogByLoginIds(loginIds));
    }

    @ApiOperation(value = "清空日志", notes = "清空所有系统访问日志")
    @ApiResponses({@ApiResponse(code = 200, message = "操作成功")})
    @PreAuthorize("@customSs.hasPermission('monitor:logininfor:clean')")
    @Log(title = "登录日志", businessType = BusinessType.CLEAN)
    @DeleteMapping("/clean")
    public R clean() {
        xlLoginLogService.cleanLoginlog();
        return R.ok();
    }
}


--- File: play-admin/src/main/java/com/starry/admin/modules/system/controller/SysMenuController.java ---
package com.starry.admin.modules.system.controller;

import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.starry.admin.common.domain.TreeSelect;
import com.starry.admin.modules.system.module.entity.SysMenuEntity;
import com.starry.admin.modules.system.service.SysMenuService;
import com.starry.admin.modules.system.vo.SimpleMenu;
import com.starry.admin.utils.SecurityUtils;
import com.starry.common.constant.UserConstants;
import com.starry.common.result.R;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiImplicitParams;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiParam;
import io.swagger.annotations.ApiResponse;
import io.swagger.annotations.ApiResponses;
import java.util.*;
import java.util.stream.Collectors;
import javax.annotation.Resource;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.*;

/**
 * <p>
 * 菜单表 前端控制器
 * </p>
 *
 * @author admin
 * @since 2022-07-03
 */
@Api(tags = "菜单管理", description = "系统菜单管理相关接口")
@RestController
@RequestMapping("/ucenter/menu")
public class SysMenuController {

    @Resource
    private SysMenuService menuService;

    @ApiOperation(value = "获取菜单列表", notes = "获取系统所有菜单列表")
    @ApiResponses({
            @ApiResponse(code = 200, message = "操作成功", response = SysMenuEntity.class, responseContainer = "List")})
    @PreAuthorize("@customSs.hasPermission('system:menu:list')")
    @GetMapping("/list")
    public R list(@ApiParam(value = "菜单查询条件") SysMenuEntity menu) {
        // 获取登录人的userId;
        String userId = SecurityUtils.getUserId();
        List<SysMenuEntity> menus = menuService.selectMenuList(menu, userId);
        return R.ok(menus);
    }

    @ApiOperation(value = "分页获取菜单列表", notes = "分页查询系统菜单列表")
    @ApiImplicitParams({
            @ApiImplicitParam(name = "pageSize", value = "每页记录数", defaultValue = "5", dataType = "Integer", paramType = "query"),
            @ApiImplicitParam(name = "pageNum", value = "页码", defaultValue = "1", dataType = "Integer", paramType = "query")})
    @ApiResponses({
            @ApiResponse(code = 200, message = "操作成功", response = SysMenuEntity.class, responseContainer = "Page")})
    @GetMapping("/list-page")
    public R listPage(@ApiParam(value = "菜单查询条件") @RequestParam(value = "menu", required = false) SysMenuEntity menu,
            @RequestParam(value = "pageSize", defaultValue = "5") Integer pageSize,
            @RequestParam(value = "pageNum", defaultValue = "1") Integer pageNum) {
        Page<SysMenuEntity> pageMenu = menuService.listPage(menu, SecurityUtils.getUserId(), pageSize, pageNum);
        return R.ok(pageMenu);
    }

    @ApiOperation(value = "添加菜单", notes = "创建新的系统菜单")
    @ApiResponses({@ApiResponse(code = 200, message = "操作成功"),
            @ApiResponse(code = 500, message = "添加失败，菜单名称已存在或地址格式错误")})
    @PreAuthorize("@customSs.hasPermission('system:menu:create')")
    @PostMapping("/create")
    public R create(@ApiParam(value = "菜单信息", required = true) @RequestBody SysMenuEntity menu) {
        if (UserConstants.NOT_UNIQUE.equals(menuService.checkMenuNameUnique(menu))) {
            return R.error("新增菜单'" + menu.getMenuName() + "'失败，菜单名称已存在");
        } else if (UserConstants.YES_FRAME.equals(menu.getIsFrame())
                && !StrUtil.startWithAny(menu.getPath(), "http://", "https://")) {
            return R.error("新增菜单'" + menu.getMenuName() + "'失败，地址必须以http(s)://开头");
        }
        boolean success = menuService.create(menu);
        if (success) {
            return R.ok();
        }
        return R.error("添加失败");
    }

    @ApiOperation(value = "修改菜单", notes = "根据ID修改菜单信息")
    @ApiImplicitParam(name = "id", value = "菜单ID", required = true, dataType = "Long", paramType = "path")
    @ApiResponses({@ApiResponse(code = 200, message = "操作成功"),
            @ApiResponse(code = 500, message = "修改失败，菜单名称已存在或地址格式错误")})
    @PreAuthorize("@customSs.hasPermission('system:menu:edit')")
    @PostMapping("/update/{id}")
    public R update(@PathVariable Long id, @ApiParam(value = "菜单信息", required = true) @RequestBody SysMenuEntity menu) {
        menu.setMenuId(id);
        if (UserConstants.NOT_UNIQUE.equals(menuService.checkMenuNameUnique(menu))) {
            return R.error("修改菜单'" + menu.getMenuName() + "'失败，菜单名称已存在");
        } else if (UserConstants.YES_FRAME.equals(menu.getIsFrame())
                && !StrUtil.startWithAny(menu.getPath(), "http://", "https://")) {
            return R.error("修改菜单'" + menu.getMenuName() + "'失败，地址必须以http(s)://开头");
        } else if (menu.getMenuId().equals(menu.getParentId())) {
            return R.error("修改菜单'" + menu.getMenuName() + "'失败，上级菜单不能选择自己");
        }
        boolean success = menuService.updateById(menu);
        if (success) {
            return R.ok();
        }
        return R.error("修改失败");
    }

    @ApiOperation(value = "删除菜单", notes = "根据ID删除菜单")
    @ApiImplicitParam(name = "id", value = "菜单ID", required = true, dataType = "Long", paramType = "path")
    @ApiResponses({@ApiResponse(code = 200, message = "操作成功"), @ApiResponse(code = 500, message = "删除失败，存在子菜单")})
    @PreAuthorize("@customSs.hasPermission('system:menu:delete')")
    @PostMapping(value = "/delete/{id}")
    public R delete(@PathVariable("id") Long menuId) {
        if (menuService.hasChildByMenuId(menuId)) {
            return R.error("存在子菜单，不允许删除");
        }
        boolean success = menuService.removeByIds(Collections.singletonList(menuId));
        if (success) {
            return R.ok();
        }
        return R.error("删除失败");
    }

    @ApiOperation(value = "获取菜单下拉树列表", notes = "获取菜单的树形结构")
    @ApiResponses({@ApiResponse(code = 200, message = "操作成功", response = TreeSelect.class, responseContainer = "List")})
    @GetMapping("/treeselect")
    public R treeselect(@ApiParam(value = "菜单查询条件") SysMenuEntity menu) {
        List<SysMenuEntity> menus = menuService.selectMenuList(menu, SecurityUtils.getUserId());
        List<TreeSelect> tree = menuService.buildMenuTreeSelect(menus);
        return R.ok(tree);
    }

    @ApiOperation(value = "获取菜单详情", notes = "根据菜单ID获取菜单详细信息")
    @ApiImplicitParam(name = "menuId", value = "菜单ID", required = true, dataType = "Long", paramType = "path")
    @ApiResponses({@ApiResponse(code = 200, message = "操作成功", response = SysMenuEntity.class)})
    @PreAuthorize("@customSs.hasPermission('system:menu:query')")
    @GetMapping(value = "/{menuId}")
    public R getInfo(@PathVariable Long menuId) {
        return R.ok(menuService.selectMenuById(menuId));
    }

    @ApiOperation(value = "获取角色菜单树", notes = "获取指定角色的菜单树形结构")
    @ApiImplicitParam(name = "roleId", value = "角色ID", required = true, dataType = "Long", paramType = "path")
    @ApiResponses({@ApiResponse(code = 200, message = "操作成功", response = Map.class)})
    @GetMapping(value = "/roleMenuTreeselect/{roleId}")
    public R roleMenuTreeselect(@PathVariable("roleId") Long roleId) {
        List<SysMenuEntity> menus = menuService.selectMenuList(SecurityUtils.getUserId());
        Map<String, Object> resultMap = new HashMap<>();
        resultMap.put("checkedKeys", menuService.selectMenuListByRoleId(roleId));
        resultMap.put("menus", menuService.buildMenuTreeSelect(menus));
        return R.ok(resultMap);
    }

    @ApiOperation(value = "获取菜单精简信息列表", notes = "只包含被开启的菜单，用于【角色分配菜单】功能的选项。在多租户的场景下，会只返回租户所在套餐有的菜单")
    @ApiResponses({@ApiResponse(code = 200, message = "操作成功", response = SimpleMenu.class, responseContainer = "List")})
    @GetMapping("/list-all-simple")
    public R getSimpleMenus() {
        // 获得菜单列表，只要开启状态的
        List<SimpleMenu> menus = menuService.selectSimpleMenuList();
        // TODO 移除公共模块
        List<Long> forbidList = Arrays.asList(26L, 48L, 47L, 19L);
        List<SimpleMenu> menuList = menus.stream()
                .filter(ca -> !forbidList.contains(ca.getId()) || !forbidList.contains(ca.getParentId()))
                .collect(Collectors.toList());
        return R.ok(menus);
    }
}


--- File: play-admin/src/main/java/com/starry/admin/modules/system/controller/SysOperationLogController.java ---
package com.starry.admin.modules.system.controller;

import com.starry.admin.modules.system.module.entity.SysOperationLogEntity;
import com.starry.admin.modules.system.service.ISysOperationLogService;
import com.starry.admin.modules.system.vo.SysOperationLogVo;
import com.starry.admin.utils.ExcelUtils;
import com.starry.common.annotation.Log;
import com.starry.common.enums.BusinessType;
import com.starry.common.result.R;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiParam;
import io.swagger.annotations.ApiResponse;
import io.swagger.annotations.ApiResponses;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletResponse;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.*;

/**
 * 操作日志表Controller
 *
 * @author admin
 */
@Api(tags = "操作日志管理", description = "系统操作日志相关接口")
@RestController
@RequestMapping("/system/operlog")
public class SysOperationLogController {

    @Resource
    private ISysOperationLogService xlOperLogService;

    /**
     * 查询操作日志表列表
     */
    @ApiOperation(value = "查询操作日志列表", notes = "分页查询系统操作日志列表")
    @ApiResponses({
            @ApiResponse(code = 200, message = "操作成功", response = SysOperationLogEntity.class, responseContainer = "Page")})
    @PreAuthorize("@customSs.hasPermission('system:operlog:list')")
    @GetMapping("/list")
    public R list(@ApiParam(value = "查询条件") SysOperationLogVo sysOperationLogEntity) {
        return R.ok(xlOperLogService.selectXlOperLogList(sysOperationLogEntity));
    }

    /**
     * 获取操作日志表详细信息
     */
    @ApiOperation(value = "获取操作日志详情", notes = "根据ID获取操作日志详细信息")
    @ApiImplicitParam(name = "operId", value = "日志ID", required = true, dataType = "Long", paramType = "path")
    @ApiResponses({@ApiResponse(code = 200, message = "操作成功", response = SysOperationLogEntity.class)})
    @PreAuthorize("@customSs.hasPermission('system:operlog:query')")
    @GetMapping(value = "/{operId}")
    public R getInfo(@PathVariable("operId") Long operId) {
        return R.ok(xlOperLogService.selectXlOperLogByOperId(operId));
    }

    /**
     * 新增操作日志表
     */
    @ApiOperation(value = "新增操作日志", notes = "手动创建系统操作日志记录")
    @ApiResponses({@ApiResponse(code = 200, message = "操作成功"), @ApiResponse(code = 500, message = "添加失败")})
    @PreAuthorize("@customSs.hasPermission('system:operlog:create')")
    @Log(title = "操作日志表", businessType = BusinessType.INSERT)
    @PostMapping
    public R create(
            @ApiParam(value = "操作日志信息", required = true) @RequestBody SysOperationLogEntity sysOperationLogEntity) {
        boolean success = xlOperLogService.create(sysOperationLogEntity);
        if (success) {
            return R.ok();
        }
        return R.error("添加失败");
    }

    /**
     * 修改操作日志表
     */
    @ApiOperation(value = "修改操作日志", notes = "根据ID修改操作日志信息")
    @ApiImplicitParam(name = "operId", value = "日志ID", required = true, dataType = "Long", paramType = "path")
    @ApiResponses({@ApiResponse(code = 200, message = "操作成功"), @ApiResponse(code = 500, message = "修改失败")})
    @PreAuthorize("@customSs.hasPermission('system:operlog:edit')")
    @Log(title = "操作日志表", businessType = BusinessType.UPDATE)
    @PostMapping(value = "/update/{operId}")
    public R update(@PathVariable Long operId,
            @ApiParam(value = "操作日志信息", required = true) @RequestBody SysOperationLogEntity sysOperationLogEntity) {
        sysOperationLogEntity.setOperId(operId);
        boolean success = xlOperLogService.updateXlOperLog(sysOperationLogEntity);
        if (success) {
            return R.ok();
        }
        return R.error("修改失败");
    }

    /**
     * 删除操作日志表
     */
    @ApiOperation(value = "删除操作日志", notes = "根据ID批量删除操作日志")
    @ApiImplicitParam(name = "operIds", value = "日志ID数组", required = true, dataType = "Long[]", paramType = "path")
    @ApiResponses({@ApiResponse(code = 200, message = "操作成功", response = Boolean.class)})
    @PreAuthorize("@customSs.hasPermission('system:operlog:remove')")
    @Log(title = "操作日志表", businessType = BusinessType.DELETE)
    @DeleteMapping("/{operIds}")
    public R remove(@PathVariable Long[] operIds) {
        return R.ok(xlOperLogService.deleteXlOperLogByOperIds(operIds));
    }

    /**
     * 导出操作日志表
     */
    @ApiOperation(value = "导出操作日志", notes = "导出系统操作日志表数据")
    @ApiResponses({@ApiResponse(code = 200, message = "操作成功")})
    @PreAuthorize("@customSs.hasPermission('system:operlog:export')")
    @Log(title = "操作日志", businessType = BusinessType.EXPORT)
    @PostMapping("/export")
    public void export(HttpServletResponse response,
            @ApiParam(value = "查询条件") SysOperationLogVo sysOperationLogEntity) {
        ExcelUtils.exportEasyExcel(response, SysOperationLogEntity.class,
                xlOperLogService.selectXlOperLogList(sysOperationLogEntity).getRecords(), "系统操作日志表数据");
    }
}


--- File: play-admin/src/main/java/com/starry/admin/modules/system/controller/SysRoleController.java ---
package com.starry.admin.modules.system.controller;

import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.starry.admin.modules.system.module.entity.SysDeptEntity;
import com.starry.admin.modules.system.module.entity.SysRoleEntity;
import com.starry.admin.modules.system.module.entity.SysUserRoleEntity;
import com.starry.admin.modules.system.service.SysDeptService;
import com.starry.admin.modules.system.service.SysRoleService;
import com.starry.admin.modules.system.service.SysUserService;
import com.starry.admin.modules.system.vo.RoleUserResultVo;
import com.starry.admin.modules.system.vo.SysRoleAddVo;
import com.starry.admin.modules.system.vo.SysUserQueryVo;
import com.starry.common.annotation.Log;
import com.starry.common.constant.UserConstants;
import com.starry.common.enums.BusinessType;
import com.starry.common.result.R;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiImplicitParams;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiParam;
import io.swagger.annotations.ApiResponse;
import io.swagger.annotations.ApiResponses;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.annotation.Resource;
import org.springframework.beans.BeanUtils;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;

/**
 * <p>
 * 角色表 前端控制器
 * </p>
 *
 * @author admin
 * @since 2022-07-01
 */
@Api(tags = "角色管理", description = "系统角色管理相关接口")
@RestController
@RequestMapping("/ucenter/role")
public class SysRoleController {

    @Resource
    private SysRoleService roleService;
    @Resource
    private SysDeptService deptService;
    @Resource
    private SysUserService userService;

    @ApiOperation(value = "添加角色", notes = "创建新的系统角色")
    @ApiResponses({@ApiResponse(code = 200, message = "操作成功"), @ApiResponse(code = 500, message = "操作失败，角色名称或权限已存在")})
    @PreAuthorize("@customSs.hasPermission('system:role:create')")
    @PostMapping("/create")
    public R create(@ApiParam(value = "角色信息", required = true) @Validated @RequestBody SysRoleAddVo roleAddVo) {
        SysRoleEntity role = new SysRoleEntity();
        BeanUtils.copyProperties(roleAddVo, role);
        if (UserConstants.NOT_UNIQUE.equals(roleService.checkRoleNameUnique(role))) {
            return R.error("新增角色'" + role.getRoleName() + "'失败，角色名称已存在");
        } else if (UserConstants.NOT_UNIQUE.equals(roleService.checkRoleKeyUnique(role))) {
            return R.error("新增角色'" + role.getRoleName() + "'失败，角色权限已存在");
        }
        boolean success = roleService.create(role);
        if (success) {
            return R.ok();
        }
        return R.error("添加失败");
    }

    @ApiOperation(value = "修改角色", notes = "根据ID修改角色信息")
    @ApiImplicitParam(name = "id", value = "角色ID", required = true, dataType = "Long", paramType = "path")
    @ApiResponses({@ApiResponse(code = 200, message = "操作成功"), @ApiResponse(code = 500, message = "修改失败，角色名称或权限已存在")})
    @PreAuthorize("@customSs.hasPermission('system:role:edit')")
    @PostMapping("/update/{id}")
    public R update(@PathVariable Long id, @ApiParam(value = "角色信息", required = true) @RequestBody SysRoleEntity role) {
        role.setRoleId(id);
        roleService.checkRoleAllowed(role);
        roleService.checkRoleDataScope(role.getRoleId());
        if (UserConstants.NOT_UNIQUE.equals(roleService.checkRoleNameUnique(role))) {
            return R.error("修改角色'" + role.getRoleName() + "'失败，角色名称已存在");
        } else if (UserConstants.NOT_UNIQUE.equals(roleService.checkRoleKeyUnique(role))) {
            return R.error("修改角色'" + role.getRoleName() + "'失败，角色权限已存在");
        }
        boolean success = roleService.updateById(role);
        if (success) {
            return R.ok();
        }
        return R.error("修改失败");
    }

    @ApiOperation(value = "批量删除角色", notes = "根据ID批量删除角色")
    @ApiImplicitParam(name = "roleIds", value = "角色ID数组", required = true, dataType = "Long[]", paramType = "path")
    @ApiResponses({@ApiResponse(code = 200, message = "操作成功"), @ApiResponse(code = 500, message = "删除失败")})
    @PreAuthorize("@customSs.hasPermission('system:role:remove')")
    @DeleteMapping("/{roleIds}")
    public R delete(@PathVariable Long[] roleIds) {
        boolean success = roleService.delete(roleIds);
        if (success) {
            return R.ok();
        }
        return R.error("删除失败");
    }

    @ApiOperation(value = "获取所有角色", notes = "获取系统中所有角色列表")
    @ApiResponses({
            @ApiResponse(code = 200, message = "操作成功", response = SysRoleEntity.class, responseContainer = "List")})
    @GetMapping("/list-all")
    public R listAll() {
        List<SysRoleEntity> list = roleService.list();
        return R.ok(list);
    }

    @ApiOperation(value = "分页获取角色列表", notes = "根据角色名称分页获取角色列表")
    @ApiImplicitParams({@ApiImplicitParam(name = "keyword", value = "搜索关键词", dataType = "String", paramType = "query"),
            @ApiImplicitParam(name = "pageSize", value = "每页记录数", defaultValue = "5", dataType = "Integer", paramType = "query"),
            @ApiImplicitParam(name = "pageNum", value = "页码", defaultValue = "1", dataType = "Integer", paramType = "query")})
    @ApiResponses({
            @ApiResponse(code = 200, message = "操作成功", response = SysRoleEntity.class, responseContainer = "Page")})
    @GetMapping("/list-page")
    public R listPage(@RequestParam(value = "keyword", required = false) String keyword,
            @RequestParam(value = "pageSize", defaultValue = "5") Integer pageSize,
            @RequestParam(value = "pageNum", defaultValue = "1") Integer pageNum) {
        Page<SysRoleEntity> rolePage = roleService.list(keyword, pageSize, pageNum);
        return R.ok(rolePage);
    }

    @ApiOperation(value = "修改角色状态", notes = "启用或禁用角色")
    @ApiImplicitParams({
            @ApiImplicitParam(name = "id", value = "角色ID", required = true, dataType = "Long", paramType = "path"),
            @ApiImplicitParam(name = "status", value = "状态(0-正常,1-停用)", required = true, dataType = "Integer", paramType = "query")})
    @ApiResponses({@ApiResponse(code = 200, message = "操作成功"), @ApiResponse(code = 500, message = "修改失败")})
    @PreAuthorize("@customSs.hasPermission('system:role:edit')")
    @PostMapping("/update-status/{id}")
    public R updateStatus(@PathVariable Long id, @RequestParam(value = "status") Integer status) {
        SysRoleEntity role = new SysRoleEntity();
        role.setRoleId(id);
        role.setStatus(status);
        boolean success = roleService.updateById(role);
        if (success) {
            return R.ok();
        }
        return R.error("修改失败");
    }

    @ApiOperation(value = "分配菜单权限", notes = "为角色分配菜单权限")
    @ApiResponses({@ApiResponse(code = 200, message = "操作成功"), @ApiResponse(code = 500, message = "分配失败")})
    @PreAuthorize("@customSs.hasPermission('system:role:auth')")
    @PostMapping("/auth-role-menu")
    public R authRoleMenu(@ApiParam(value = "角色信息", required = true) @RequestBody SysRoleEntity role) {
        boolean success = roleService.authRoleMenu(role);
        if (success) {
            return R.ok();
        }
        return R.error("分配失败");
    }

    /**
     * 根据角色编号获取详细信息
     */
    @ApiOperation(value = "获取角色详情", notes = "根据角色ID获取角色详细信息")
    @ApiImplicitParam(name = "roleId", value = "角色ID", required = true, dataType = "Long", paramType = "path")
    @ApiResponses({@ApiResponse(code = 200, message = "操作成功", response = SysRoleEntity.class)})
    @GetMapping(value = "/{roleId}")
    public R getInfo(@PathVariable Long roleId) {
        roleService.checkRoleDataScope(roleId);
        return R.ok(roleService.getById(roleId));
    }

    /**
     * 获取对应角色部门树列表
     */
    @ApiOperation(value = "获取角色部门树列表", notes = "获取角色关联的部门树结构")
    @ApiImplicitParam(name = "roleId", value = "角色ID", required = true, dataType = "Long", paramType = "path")
    @ApiResponses({@ApiResponse(code = 200, message = "操作成功", response = Map.class)})
    @GetMapping(value = "/deptTree/{roleId}")
    public R deptTree(@PathVariable("roleId") Long roleId) {
        Map<String, Object> resultMap = new HashMap<>(8);
        resultMap.put("checkedKeys", deptService.selectDeptListByRoleId(roleId));
        resultMap.put("depts", deptService.selectDeptTreeList(new SysDeptEntity()));
        return R.ok(resultMap);
    }

    /**
     * 修改保存数据权限
     */
    @ApiOperation(value = "保存数据权限", notes = "修改角色的数据权限范围")
    @ApiResponses({@ApiResponse(code = 200, message = "操作成功", response = Boolean.class)})
    @Log(title = "角色管理-保存数据权限", businessType = BusinessType.UPDATE)
    @PostMapping("/dataScope")
    public R dataScope(@ApiParam(value = "角色信息", required = true) @RequestBody SysRoleEntity role) {
        roleService.checkRoleAllowed(role);
        roleService.checkRoleDataScope(role.getRoleId());
        return R.ok(roleService.authDataScope(role));
    }

    /**
     * 查询已分配用户角色列表
     */
    @ApiOperation(value = "查询已分配用户角色列表", notes = "获取已经分配给指定角色的用户列表")
    @ApiResponses({
            @ApiResponse(code = 200, message = "操作成功", response = RoleUserResultVo.class, responseContainer = "Page")})
    @PreAuthorize("@customSs.hasPermission('system:role:list')")
    @GetMapping("/authUser/allocatedList")
    public R allocatedList(SysUserQueryVo user) {
        IPage<RoleUserResultVo> list = userService.selectAllocatedList(user);
        return R.ok(list);
    }

    /**
     * 查询未分配用户角色列表
     */
    @ApiOperation(value = "查询未分配用户角色列表", notes = "获取尚未分配给指定角色的用户列表")
    @ApiResponses({
            @ApiResponse(code = 200, message = "操作成功", response = RoleUserResultVo.class, responseContainer = "Page")})
    @PreAuthorize("@customSs.hasPermission('system:role:list')")
    @GetMapping("/authUser/unallocatedList")
    public R unallocatedList(SysUserQueryVo user) {
        return R.ok(userService.selectUnallocatedList(user));
    }

    /**
     * 取消授权用户
     */
    @ApiOperation(value = "取消授权用户", notes = "取消指定用户的角色授权")
    @ApiResponses({@ApiResponse(code = 200, message = "操作成功", response = Boolean.class)})
    @PreAuthorize("@customSs.hasPermission('system:role:edit')")
    @Log(title = "角色管理", businessType = BusinessType.GRANT)
    @PutMapping("/authUser/cancel")
    public R cancelAuthUser(@ApiParam(value = "用户角色关系", required = true) @RequestBody SysUserRoleEntity userRole) {
        return R.ok(roleService.deleteAuthUser(userRole));
    }

    /**
     * 批量取消授权用户
     */
    @ApiOperation(value = "批量取消授权用户", notes = "批量取消多个用户的角色授权")
    @ApiImplicitParams({
            @ApiImplicitParam(name = "roleId", value = "角色ID", required = true, dataType = "Long", paramType = "query"),
            @ApiImplicitParam(name = "userIds", value = "用户ID数组", required = true, dataType = "String[]", paramType = "query")})
    @ApiResponses({@ApiResponse(code = 200, message = "操作成功", response = Boolean.class)})
    @PreAuthorize("@customSs.hasPermission('system:role:edit')")
    @Log(title = "角色管理", businessType = BusinessType.GRANT)
    @PutMapping("/authUser/cancelAll")
    public R cancelAuthUserAll(Long roleId, String[] userIds) {
        return R.ok(roleService.deleteAuthUsers(roleId, userIds));
    }

    /**
     * 批量选择用户授权
     */
    @ApiOperation(value = "批量选择用户授权", notes = "为角色批量添加用户授权")
    @ApiImplicitParams({
            @ApiImplicitParam(name = "roleId", value = "角色ID", required = true, dataType = "Long", paramType = "query"),
            @ApiImplicitParam(name = "userIds", value = "用户ID数组", required = true, dataType = "String[]", paramType = "query")})
    @ApiResponses({@ApiResponse(code = 200, message = "操作成功", response = Boolean.class)})
    @PreAuthorize("@customSs.hasPermission('system:role:edit')")
    @Log(title = "角色管理", businessType = BusinessType.GRANT)
    @PutMapping("/authUser/selectAll")
    public R selectAuthUserAll(Long roleId, String[] userIds) {
        roleService.checkRoleDataScope(roleId);
        return R.ok(roleService.insertAuthUsers(roleId, userIds));
    }
}


--- File: play-admin/src/main/java/com/starry/admin/modules/system/controller/SysSmsController.java ---
package com.starry.admin.modules.system.controller;

import com.starry.admin.modules.system.module.entity.SysTenantEntity;
import com.starry.admin.modules.system.service.ISysTenantService;
import com.starry.admin.utils.SmsUtils;
import com.starry.common.result.R;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiResponse;
import io.swagger.annotations.ApiResponses;
import javax.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

/**
 * 动态接口
 *
 * @author admin
 * @since 2024/5/20 下午11:19
 **/
@Api(tags = "短信管理", description = "系统短信发送相关接口")
@Slf4j
@RestController
@RequestMapping("/system/sms")
public class SysSmsController {
    @Resource
    private SmsUtils smsUtils;
    @Resource
    private ISysTenantService tenantService;

    @ApiOperation(value = "发送短信", notes = "向指定手机号发送短信验证码")
    @ApiImplicitParam(name = "phone", value = "手机号码", required = true, dataType = "String", paramType = "query")
    @ApiResponses({@ApiResponse(code = 200, message = "操作成功")})
    @GetMapping("/send")
    public R sendSms(@RequestParam("phone") String phone) {
        smsUtils.sendSmsApi(phone);
        return R.ok();
    }

    @ApiOperation(value = "测试接口", notes = "测试租户角色初始化")
    @ApiResponses({@ApiResponse(code = 200, message = "操作成功", response = Boolean.class)})
    @GetMapping("/test")
    public R test() {
        SysTenantEntity sysTenantEntity = tenantService.selectSysTenantByTenantId("y5PNjqco");
        tenantService.initRole(sysTenantEntity);
        return R.ok(true);
    }

}


--- File: play-admin/src/main/java/com/starry/admin/modules/system/controller/SysTenantController.java ---
package com.starry.admin.modules.system.controller;

import com.baomidou.mybatisplus.core.metadata.IPage;
import com.starry.admin.modules.clerk.service.IPlayClerkUserInfoService;
import com.starry.admin.modules.order.module.entity.PlayOrderInfoEntity;
import com.starry.admin.modules.order.service.IPlayOrderInfoService;
import com.starry.admin.modules.system.module.entity.SysTenantEntity;
import com.starry.admin.modules.system.service.ISysTenantService;
import com.starry.admin.modules.system.vo.SysTenantAddVo;
import com.starry.admin.modules.system.vo.SysTenantQueryVo;
import com.starry.admin.modules.system.vo.SysTenantReturnVo;
import com.starry.common.annotation.Log;
import com.starry.common.enums.BusinessType;
import com.starry.common.result.R;
import com.starry.common.utils.ConvertUtil;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiParam;
import io.swagger.annotations.ApiResponse;
import io.swagger.annotations.ApiResponses;
import java.math.BigDecimal;
import java.util.List;
import javax.annotation.Resource;
import org.springframework.beans.BeanUtils;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;

/**
 * 租户表Controller
 *
 * @author admin
 */
@Api(tags = "租户管理", description = "系统租户相关接口")
@RestController
@RequestMapping("/platform/tenant")
public class SysTenantController {
    @Resource
    private ISysTenantService sysTenantService;

    @Resource
    private IPlayClerkUserInfoService playClerkUserInfoService;

    @Resource
    private IPlayOrderInfoService playOrderInfoService;

    /**
     * 新增租户表
     */
    @ApiOperation(value = "新增租户", notes = "创建新的系统租户")
    @ApiResponses({@ApiResponse(code = 200, message = "操作成功"), @ApiResponse(code = 500, message = "添加失败")})
    // @PreAuthorize("@customSs.hasPermission('platform:tenant:create')")
    @Log(title = "租户表", businessType = BusinessType.INSERT)
    @PostMapping("/create")
    public R create(@ApiParam(value = "租户信息", required = true) @Validated @RequestBody SysTenantAddVo vo) {
        SysTenantEntity entity = ConvertUtil.entityToVo(vo, SysTenantEntity.class);
        return sysTenantService.addTenant(entity);
    }

    /**
     * 查询租户表列表
     */
    @ApiOperation(value = "分页查询租户列表", notes = "根据条件分页查询租户列表")
    @ApiResponses({
            @ApiResponse(code = 200, message = "操作成功", response = SysTenantReturnVo.class, responseContainer = "Page")})
    @PreAuthorize("@customSs.hasPermission('platform:tenant:list')")
    @PostMapping("/listByPage")
    public R listByPage(@ApiParam(value = "查询条件", required = true) @RequestBody SysTenantQueryVo vo) {
        IPage<SysTenantEntity> page = sysTenantService.selectSysTenantList(vo);
        IPage<SysTenantReturnVo> voPage = page.convert(u -> {
            SysTenantReturnVo returnVo = new SysTenantReturnVo();
            List<PlayOrderInfoEntity> orderInfoEntities = playOrderInfoService.getTotalOrderInfo(u.getTenantId());
            BigDecimal rechargeAmount = BigDecimal.ZERO;
            for (PlayOrderInfoEntity orderInfoEntity : orderInfoEntities) {
                if ("0".equals(orderInfoEntity.getOrderType())) {
                    rechargeAmount = rechargeAmount.add(orderInfoEntity.getFinalAmount());
                }
            }

            BeanUtils.copyProperties(u, returnVo);
            returnVo.setClerkNumber(playClerkUserInfoService.getTotalClerkUser(u.getTenantId()));
            returnVo.setOrderNumber((long) orderInfoEntities.size());
            returnVo.setRechargeAmount(rechargeAmount);
            return returnVo;
        });

        return R.ok(voPage);
    }

    /**
     * 获取租户表详细信息
     */
    @ApiOperation(value = "获取租户详情", notes = "根据租户ID获取租户详细信息")
    @ApiImplicitParam(name = "tenantId", value = "租户ID", required = true, dataType = "String", paramType = "path")
    @ApiResponses({@ApiResponse(code = 200, message = "操作成功", response = SysTenantEntity.class)})
    @PreAuthorize("@customSs.hasPermission('platform:tenant:query')")
    @GetMapping(value = "/{tenantId}")
    public R getInfo(@PathVariable("tenantId") String tenantId) {
        return R.ok(sysTenantService.selectSysTenantByTenantId(tenantId));
    }

    /**
     * 修改租户表
     */
    @ApiOperation(value = "修改租户", notes = "根据租户ID修改租户信息")
    @ApiImplicitParam(name = "tenantId", value = "租户ID", required = true, dataType = "String", paramType = "path")
    @ApiResponses({@ApiResponse(code = 200, message = "操作成功"), @ApiResponse(code = 500, message = "修改失败")})
    @PreAuthorize("@customSs.hasPermission('platform:tenant:edit')")
    @Log(title = "租户表", businessType = BusinessType.UPDATE)
    @PostMapping(value = "/update/{tenantId}")
    public R update(@PathVariable String tenantId,
            @ApiParam(value = "租户信息", required = true) @RequestBody SysTenantEntity sysTenantEntity) {
        return sysTenantService.updateTenant(sysTenantEntity);
    }

    /**
     * 删除租户表
     */
    @ApiOperation(value = "删除租户", notes = "根据租户ID批量删除租户")
    @ApiImplicitParam(name = "tenantIds", value = "租户ID数组", required = true, dataType = "String[]", paramType = "path")
    @ApiResponses({@ApiResponse(code = 200, message = "操作成功", response = Boolean.class)})
    @PreAuthorize("@customSs.hasPermission('platform:tenant:remove')")
    @Log(title = "租户表", businessType = BusinessType.DELETE)
    @DeleteMapping("/{tenantIds}")
    public R remove(@PathVariable String[] tenantIds) {
        return R.ok(sysTenantService.deleteSysTenantByTenantIds(tenantIds));
    }
}


--- File: play-admin/src/main/java/com/starry/admin/modules/system/controller/SysTenantPackageController.java ---
package com.starry.admin.modules.system.controller;

import com.starry.admin.modules.system.module.entity.SysTenantPackageEntity;
import com.starry.admin.modules.system.service.ISysTenantPackageService;
import com.starry.admin.modules.system.vo.SimplePackage;
import com.starry.admin.modules.system.vo.SysTenantPackageVo;
import com.starry.admin.utils.ExcelUtils;
import com.starry.common.annotation.Log;
import com.starry.common.enums.BusinessType;
import com.starry.common.result.R;
import com.starry.common.utils.ConvertUtil;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiParam;
import io.swagger.annotations.ApiResponse;
import io.swagger.annotations.ApiResponses;
import java.util.List;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletResponse;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.*;

/**
 * 租户套餐表Controller
 *
 * @author admin
 */
@Api(tags = "租户套餐管理", description = "系统租户套餐相关接口")
@RestController
@RequestMapping("/platform/package")
public class SysTenantPackageController {
    @Resource
    private ISysTenantPackageService SysTenantPackageService;

    /**
     * 查询租户套餐表列表
     */
    @ApiOperation(value = "查询租户套餐列表", notes = "分页查询租户套餐列表")
    @ApiResponses({
            @ApiResponse(code = 200, message = "操作成功", response = SysTenantPackageEntity.class, responseContainer = "Page")})
    @PreAuthorize("@customSs.hasPermission('platform:package:list')")
    @GetMapping("/list")
    public R list(@ApiParam(value = "查询条件") SysTenantPackageVo sysTenantPackageEntity) {
        return R.ok(SysTenantPackageService.selectSysTenantPackageList(sysTenantPackageEntity));
    }

    /**
     * 导出租户套餐表列表
     */
    @ApiOperation(value = "导出租户套餐", notes = "导出租户套餐表数据到Excel")
    @ApiResponses({@ApiResponse(code = 200, message = "操作成功")})
    @PreAuthorize("@customSs.hasPermission('platform:package:export')")
    @Log(title = "租户套餐表", businessType = BusinessType.EXPORT)
    @PostMapping("/export")
    public void export(HttpServletResponse response,
            @ApiParam(value = "查询条件") SysTenantPackageVo sysTenantPackageEntity) {
        ExcelUtils.exportEasyExcel(response, SysTenantPackageEntity.class,
                SysTenantPackageService.selectSysTenantPackageList(sysTenantPackageEntity).getRecords(), "租户套餐表数据");
    }

    /**
     * 获取租户套餐表详细信息
     */
    @ApiOperation(value = "获取租户套餐详情", notes = "根据ID获取租户套餐详细信息")
    @ApiImplicitParam(name = "packageId", value = "套餐ID", required = true, dataType = "String", paramType = "path")
    @ApiResponses({@ApiResponse(code = 200, message = "操作成功", response = SysTenantPackageEntity.class)})
    @PreAuthorize("@customSs.hasPermission('platform:package:query')")
    @GetMapping(value = "/{packageId}")
    public R getInfo(@PathVariable("packageId") String packageId) {
        return R.ok(SysTenantPackageService.selectSysTenantPackageByPackageId(packageId));
    }

    /**
     * 新增租户套餐表
     */
    @ApiOperation(value = "新增租户套餐", notes = "创建新的租户套餐")
    @ApiResponses({@ApiResponse(code = 200, message = "操作成功"), @ApiResponse(code = 500, message = "添加失败")})
    @PreAuthorize("@customSs.hasPermission('platform:package:create')")
    @Log(title = "租户套餐表", businessType = BusinessType.INSERT)
    @PostMapping("/create")
    public R create(@ApiParam(value = "套餐信息", required = true) @RequestBody SysTenantPackageEntity vo) {
        SysTenantPackageEntity entity = ConvertUtil.entityToVo(vo, SysTenantPackageEntity.class);
        boolean success = SysTenantPackageService.create(entity);
        if (success) {
            return R.ok();
        }
        return R.error("添加失败");
    }

    /**
     * 修改租户套餐表
     */
    @ApiOperation(value = "修改租户套餐", notes = "根据ID修改租户套餐信息")
    @ApiImplicitParam(name = "packageId", value = "套餐ID", required = true, dataType = "String", paramType = "path")
    @ApiResponses({@ApiResponse(code = 200, message = "操作成功"), @ApiResponse(code = 500, message = "修改失败")})
    @PreAuthorize("@customSs.hasPermission('platform:package:edit')")
    @Log(title = "租户套餐表", businessType = BusinessType.UPDATE)
    @PostMapping(value = "/update/{packageId}")
    public R update(@PathVariable String packageId,
            @ApiParam(value = "套餐信息", required = true) @RequestBody SysTenantPackageEntity sysTenantPackageEntity) {

        sysTenantPackageEntity.setPackageId(packageId);
        boolean success = SysTenantPackageService.update(sysTenantPackageEntity);
        if (success) {
            return R.ok();
        }
        return R.error("修改失败");
    }

    /**
     * 删除租户套餐表
     */
    @ApiOperation(value = "删除租户套餐", notes = "根据ID批量删除租户套餐")
    @ApiImplicitParam(name = "packageIds", value = "套餐ID数组", required = true, dataType = "String[]", paramType = "path")
    @ApiResponses({@ApiResponse(code = 200, message = "操作成功", response = Boolean.class)})
    @PreAuthorize("@customSs.hasPermission('platform:package:remove')")
    @Log(title = "租户套餐表", businessType = BusinessType.DELETE)
    @DeleteMapping("/{packageIds}")
    public R remove(@PathVariable String[] packageIds) {
        return R.ok(SysTenantPackageService.deleteSysTenantPackageByPackageIds(packageIds));
    }

    /**
     * 查询租户套餐精简列表
     */
    @ApiOperation(value = "查询套餐精简列表", notes = "获取租户套餐的精简信息列表")
    @ApiResponses({
            @ApiResponse(code = 200, message = "操作成功", response = SimplePackage.class, responseContainer = "List")})
    @GetMapping("/get-simple-list")
    public R getSimpleList() {
        List<SimplePackage> list = SysTenantPackageService.getSimpleList();
        return R.ok(list);
    }
}


--- File: play-admin/src/main/java/com/starry/admin/modules/system/controller/SysTenantRechargeInfoController.java ---
package com.starry.admin.modules.system.controller;

import com.starry.admin.modules.system.module.entity.SysTenantRechargeInfoEntity;
import com.starry.admin.modules.system.module.vo.SysTenantRechargeInfoAddVo;
import com.starry.admin.modules.system.module.vo.SysTenantRechargeInfoQueryVo;
import com.starry.admin.modules.system.service.ISysTenantRechargeInfoService;
import com.starry.common.annotation.Log;
import com.starry.common.enums.BusinessType;
import com.starry.common.result.R;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiParam;
import io.swagger.annotations.ApiResponse;
import io.swagger.annotations.ApiResponses;
import javax.annotation.Resource;
import org.springframework.beans.BeanUtils;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;

/**
 * 租户充值信息Controller
 *
 * @author admin
 * @since 2024-09-05
 */
@Api(tags = "租户充值管理", description = "租户充值记录相关接口")
@RestController
@RequestMapping("/platform/recharge")
public class SysTenantRechargeInfoController {
    @Resource
    private ISysTenantRechargeInfoService sysTenantRechargeInfoService;

    /**
     * 查询租户充值信息列表
     */
    @ApiOperation(value = "查询充值记录", notes = "分页查询租户充值记录列表")
    @ApiResponses({
            @ApiResponse(code = 200, message = "操作成功", response = SysTenantRechargeInfoEntity.class, responseContainer = "Page")})
    // @PreAuthorize("@customSs.hasPermission('play:info:list')")
    @PostMapping("/listByPage")
    public R list(@ApiParam(value = "查询条件", required = true) @Validated @RequestBody SysTenantRechargeInfoQueryVo vo) {
        return R.ok(sysTenantRechargeInfoService.selectByPage(vo));
    }

    /**
     * 获取租户充值信息详细信息
     */
    @ApiOperation(value = "获取充值详情", notes = "根据ID获取租户充值记录详细信息")
    @ApiImplicitParam(name = "id", value = "充值记录ID", required = true, dataType = "String", paramType = "path")
    @ApiResponses({@ApiResponse(code = 200, message = "操作成功", response = SysTenantRechargeInfoEntity.class)})
    // @PreAuthorize("@customSs.hasPermission('play:info:query')")
    @GetMapping(value = "/{id}")
    public R getInfo(@PathVariable("id") String id) {
        return R.ok(sysTenantRechargeInfoService.selectSysTenantRechargeInfoById(id));
    }

    /**
     * 新增租户充值信息
     */
    @ApiOperation(value = "新增充值记录", notes = "创建租户充值记录")
    @ApiResponses({@ApiResponse(code = 200, message = "操作成功"), @ApiResponse(code = 500, message = "添加失败")})
    // @PreAuthorize("@customSs.hasPermission('play:info:create')")
    @Log(title = "租户充值信息", businessType = BusinessType.INSERT)
    @PostMapping("/create")
    public R create(
            @ApiParam(value = "充值信息", required = true) @Validated @RequestBody SysTenantRechargeInfoAddVo addVo) {
        SysTenantRechargeInfoEntity entity = new SysTenantRechargeInfoEntity();
        BeanUtils.copyProperties(addVo, entity);
        boolean success = sysTenantRechargeInfoService.create(entity);
        if (success) {
            return R.ok();
        }
        return R.error("添加失败");
    }

    /**
     * 修改租户充值信息
     */
    @ApiOperation(value = "修改充值记录", notes = "根据ID修改租户充值记录")
    @ApiImplicitParam(name = "id", value = "充值记录ID", required = true, dataType = "String", paramType = "path")
    @ApiResponses({@ApiResponse(code = 200, message = "操作成功"), @ApiResponse(code = 500, message = "修改失败")})
    @PreAuthorize("@customSs.hasPermission('play:info:edit')")
    @Log(title = "租户充值信息", businessType = BusinessType.UPDATE)
    @PostMapping(value = "/update/{id}")
    public R update(@PathVariable String id,
            @ApiParam(value = "充值信息", required = true) @RequestBody SysTenantRechargeInfoEntity sysTenantRechargeInfo) {
        sysTenantRechargeInfo.setId(id);
        boolean success = sysTenantRechargeInfoService.update(sysTenantRechargeInfo);
        if (success) {
            return R.ok();
        }
        return R.error("修改失败");
    }

    /**
     * 删除租户充值信息
     */
    @ApiOperation(value = "删除充值记录", notes = "根据ID批量删除租户充值记录")
    @ApiImplicitParam(name = "ids", value = "充值记录ID数组", required = true, dataType = "String[]", paramType = "path")
    @ApiResponses({@ApiResponse(code = 200, message = "操作成功", response = Boolean.class)})
    // @PreAuthorize("@customSs.hasPermission('play:info:remove')")
    @Log(title = "租户充值信息", businessType = BusinessType.DELETE)
    @DeleteMapping("/{ids}")
    public R remove(@PathVariable String[] ids) {
        return R.ok(sysTenantRechargeInfoService.deleteSysTenantRechargeInfoByIds(ids));
    }
}


--- File: play-admin/src/main/java/com/starry/admin/modules/system/controller/SysUserController.java ---
package com.starry.admin.modules.system.controller;

import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.starry.admin.common.component.JwtToken;
import com.starry.admin.common.domain.LoginUser;
import com.starry.admin.common.oss.service.IOssFileService;
import com.starry.admin.modules.system.module.entity.SysRoleEntity;
import com.starry.admin.modules.system.module.entity.SysUserEntity;
import com.starry.admin.modules.system.service.SysRoleService;
import com.starry.admin.modules.system.service.SysUserService;
import com.starry.admin.modules.system.vo.*;
import com.starry.admin.utils.SecurityUtils;
import com.starry.common.annotation.Log;
import com.starry.common.constant.UserConstants;
import com.starry.common.enums.BusinessType;
import com.starry.common.result.R;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiParam;
import io.swagger.annotations.ApiResponse;
import io.swagger.annotations.ApiResponses;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import javax.annotation.Resource;
import javax.validation.Valid;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;

/**
 * <p>
 * 后台用户表 前端控制器
 * </p>
 *
 * @author admin
 * @since 2021-09-03
 */
@Api(tags = "用户管理", description = "系统用户管理相关接口")
@RestController
@RequestMapping("/ucenter/user")
public class SysUserController {

    @Resource
    private SysUserService userService;
    @Resource
    private SysRoleService roleService;
    @Resource
    private JwtToken tokenService;

    @Resource
    private IOssFileService ossFileService;

    @ApiOperation(value = "注册用户", notes = "新用户注册接口")
    @ApiResponses({@ApiResponse(code = 200, message = "操作成功", response = SysUserEntity.class),
            @ApiResponse(code = 500, message = "操作失败")})
    @PostMapping(value = "register")
    public R register(@ApiParam(value = "用户信息", required = true) @RequestBody SysUserEntity user) {
        SysUserEntity sysUserEntity = userService.register(user);
        if (sysUserEntity == null) {
            return R.error("注册失败,用户名已存在");
        }
        return R.ok(sysUserEntity);
    }

    @ApiOperation(value = "分页用户列表", notes = "分页查询用户列表信息")
    @ApiResponses({
            @ApiResponse(code = 200, message = "操作成功", response = SysUserEntity.class, responseContainer = "Page")})
    @GetMapping(value = "/list-page")
    public R listPage(UserQueryVo queryVo) {
        IPage<SysUserEntity> page = userService.listMemberPage(queryVo);
        return R.ok(page);
    }

    @ApiOperation(value = "分页用户列表(V1)", notes = "分页查询用户列表信息，返回格式化后的结果")
    @ApiResponses({
            @ApiResponse(code = 200, message = "操作成功", response = SysUserEntityListVo.class, responseContainer = "Page")})
    @GetMapping(value = "/list-page/v1")
    public R listPageV1(UserQueryVo queryVo) {
        IPage<SysUserEntity> page = userService.listMemberPage(queryVo);
        List<SysUserEntity> records = page.getRecords();
        List<SysUserEntityListVo> list = userService.generatePageData(records);
        IPage<SysUserEntityListVo> result = new Page<>();
        result.setTotal(page.getTotal());
        result.setRecords(list);
        return R.ok(result);
    }

    @ApiOperation(value = "添加用户", notes = "创建新的系统用户")
    @ApiResponses({@ApiResponse(code = 200, message = "操作成功"), @ApiResponse(code = 500, message = "操作失败，登录账号已存在")})
    @PostMapping(value = "add")
    public R add(@ApiParam(value = "用户信息", required = true) @RequestBody SysUserEntity user) {
        if (UserConstants.NOT_UNIQUE.equals(userService.checkUserNameUnique(user.getUserCode()))) {
            return R.error("新增用户'" + user.getUserCode() + "'失败，登录账号已存在");
        }
        boolean success = userService.create(user);
        if (success) {
            return R.ok();
        }
        return R.error("添加失败");
    }

    @ApiOperation(value = "添加用户(V1)", notes = "创建新的系统用户，使用参数对象")
    @ApiResponses({@ApiResponse(code = 200, message = "操作成功")})
    @PostMapping(value = "add/v1")
    public R addV1(@ApiParam(value = "用户添加参数", required = true) @Valid @RequestBody UserAddParam param) {
        boolean success = userService.addV1(param);
        return R.ok();
    }

    @ApiOperation(value = "修改用户", notes = "根据ID修改用户信息")
    @ApiImplicitParam(name = "id", value = "用户ID", required = true, dataType = "String", paramType = "path")
    @ApiResponses({@ApiResponse(code = 200, message = "操作成功"), @ApiResponse(code = 500, message = "修改失败")})
    @PostMapping(value = "/update/{id}")
    public R update(@PathVariable("id") String id,
            @ApiParam(value = "用户信息", required = true) @RequestBody SysUserEntity user) {
        user.setUserId(id);
        boolean flag = userService.update(user);
        if (flag) {
            return R.ok();
        }
        return R.error("修改失败");
    }

    @ApiOperation(value = "修改用户密码", notes = "修改当前用户的密码")
    @ApiResponses({@ApiResponse(code = 200, message = "操作成功")})
    @PostMapping(value = "/update/password")
    public R updatePassword(
            @ApiParam(value = "密码修改参数", required = true) @Valid @RequestBody UpdateUserPasswordParam param) {
        userService.updatePassword(param);
        return R.ok();
    }

    @ApiOperation(value = "修改用户密码", notes = "修改当前用户的密码")
    @ApiResponses({@ApiResponse(code = 200, message = "操作成功")})
    @PostMapping(value = "/profile/updatePwd")
    public R updatePasswordV2(
            @ApiParam(value = "密码修改参数", required = true) @Valid @RequestBody UpdateUserPasswordParamV2 param) {
        userService.updatePasswordV2(param);
        return R.ok();
    }

    @ApiOperation(value = "修改用户手机号", notes = "修改当前用户的手机号")
    @ApiResponses({@ApiResponse(code = 200, message = "操作成功")})
    @PostMapping(value = "/update/mobile")
    public R updateMobile(
            @ApiParam(value = "手机号修改参数", required = true) @Valid @RequestBody UpdateUserMobileParam param) {
        userService.updateMobile(param);
        return R.ok();
    }

    @ApiOperation(value = "删除用户", notes = "根据ID删除用户")
    @ApiImplicitParam(name = "id", value = "用户ID", required = true, dataType = "String", paramType = "path")
    @ApiResponses({@ApiResponse(code = 200, message = "操作成功"), @ApiResponse(code = 500, message = "操作失败")})
    @PostMapping(value = "/delete/{id}")
    public R delete(@PathVariable("id") String id) {
        boolean flag = userService.delete(Collections.singletonList(id));
        if (flag) {
            return R.ok();
        }
        return R.error();
    }

    @ApiOperation(value = "登出功能", notes = "用户登出系统")
    @ApiResponses({@ApiResponse(code = 200, message = "操作成功")})
    @PostMapping(value = "logout")
    public R logout() {
        return R.ok();
    }

    @ApiOperation(value = "获取用户详情", notes = "根据用户ID获取用户详细信息，不传ID则获取当前用户")
    @ApiImplicitParam(name = "userId", value = "用户ID", required = false, dataType = "String", paramType = "path")
    @ApiResponses({@ApiResponse(code = 200, message = "操作成功", response = Map.class)})
    @GetMapping(value = {"/", "/{userId}"})
    public R getInfo(@PathVariable(value = "userId", required = false) String userId) {
        SysUserEntity user = userService.selectUserById(userId);
        Map<String, Object> map = new HashMap<>(8);
        List<SysRoleEntity> roles = roleService.selectRoleList(new SysRoleEntity());
        map.put("roles",
                SysUserEntity.isAdmin(user)
                        ? roles
                        : roles.stream().filter(r -> !r.isAdmin()).collect(Collectors.toList()));
        map.put("roleIds", user.getRoles().stream().map(SysRoleEntity::getRoleId).collect(Collectors.toList()));
        map.put("user", user);
        return R.ok(map);
    }

    @ApiOperation(value = "获取个人信息", notes = "获取当前登录用户的个人信息")
    @ApiResponses({@ApiResponse(code = 200, message = "操作成功", response = Map.class)})
    @GetMapping(value = "/profile")
    public R getProfile() {
        Map<String, Object> map = new HashMap<>(8);
        SysUserEntity loginUser = userService.selectUserById(SecurityUtils.getUserId());
        map.put("user", loginUser);
        map.put("roleGroup", userService.selectUserRoleGroup(loginUser.getUserId()));
        return R.ok(map);
    }

    @ApiOperation(value = "用户头像上传", notes = "上传用户头像图片")
    @ApiResponses({@ApiResponse(code = 200, message = "操作成功", response = String.class),
            @ApiResponse(code = 500, message = "上传头像异常")})
    @Log(title = "用户头像上传", businessType = BusinessType.UPDATE)
    @PostMapping("/profile/avatar")
    public R uploadAvatar(@ApiParam(value = "头像文件", required = true) @RequestParam("avatarfile") MultipartFile file)
            throws Exception {
        if (!file.isEmpty()) {
            String avatar = ossFileService.upload(file.getInputStream(), "avatar", file.getOriginalFilename());
            if (userService.updateUserAvatar(SecurityUtils.getUserId(), avatar)) {
                // 更新缓存用户头像
                LoginUser loginUser = SecurityUtils.getLoginUser();
                loginUser.getUser().setAvatar(avatar);
                tokenService.refreshToken(loginUser);
                return R.ok(avatar);
            }
        }
        return R.error("上传头像异常，请联系管理员");
    }
}


--- File: play-admin/src/main/java/com/starry/admin/modules/system/controller/UserOnlineController.java ---
package com.starry.admin.modules.system.controller;

import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.starry.admin.common.domain.LoginUser;
import com.starry.admin.modules.system.module.entity.UserOnline;
import com.starry.admin.modules.system.service.UserOnlineService;
import com.starry.common.annotation.Log;
import com.starry.common.constant.CacheConstants;
import com.starry.common.enums.BusinessType;
import com.starry.common.redis.RedisCache;
import com.starry.common.result.R;
import com.starry.common.utils.StringUtils;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiImplicitParams;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiResponse;
import io.swagger.annotations.ApiResponses;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import javax.annotation.Resource;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.*;

/**
 * @author huoqiang 在线用户监控
 * @since 2022/10/18
 */
@Api(tags = "在线用户监控", description = "系统在线用户监控相关接口")
@RestController
@RequestMapping("/monitor/online")
public class UserOnlineController {

    @Resource
    private RedisCache redisCache;
    @Resource
    private UserOnlineService userOnlineService;

    @ApiOperation(value = "查询在线用户列表", notes = "根据IP地址和用户名查询在线用户")
    @ApiImplicitParams({@ApiImplicitParam(name = "ipaddr", value = "IP地址", dataType = "String", paramType = "query"),
            @ApiImplicitParam(name = "userName", value = "用户名", dataType = "String", paramType = "query")})
    @ApiResponses({@ApiResponse(code = 200, message = "操作成功", response = UserOnline.class, responseContainer = "Page")})
    @PreAuthorize("@customSs.hasPermission('monitor:online:list')")
    @GetMapping("/list")
    public R list(String ipaddr, String userName) {
        Collection<String> keys = redisCache.keys(CacheConstants.LOGIN_TOKEN_KEY + "*");
        List<UserOnline> userOnlineList = new ArrayList<>();
        for (String key : keys) {
            LoginUser user = redisCache.getCacheObject(key);
            if (StrUtil.isNotBlank(ipaddr) && StrUtil.isNotBlank(userName)) {
                if (StringUtils.equals(ipaddr, user.getIpaddr()) && StringUtils.equals(userName, user.getUsername())) {
                    userOnlineList.add(userOnlineService.selectOnlineByInfo(ipaddr, userName, user));
                }
            } else if (StrUtil.isNotBlank(ipaddr)) {
                if (StringUtils.equals(ipaddr, user.getIpaddr())) {
                    userOnlineList.add(userOnlineService.selectOnlineByIpaddr(ipaddr, user));
                }
            } else if (StrUtil.isNotBlank(userName) && StringUtils.isNotNull(user.getUser())) {
                if (StringUtils.equals(userName, user.getUsername())) {
                    userOnlineList.add(userOnlineService.selectOnlineByUserName(userName, user));
                }
            } else {
                userOnlineList.add(userOnlineService.setUserOnline(user));
            }
        }
        IPage<UserOnline> page = new Page<>();
        page.setRecords(userOnlineList);
        page.setTotal(userOnlineList.size());
        return R.ok(page);
    }

    /**
     * 强退用户
     */
    @ApiOperation(value = "强制退出用户", notes = "强制指定token的用户下线")
    @ApiImplicitParam(name = "tokenId", value = "用户token", required = true, dataType = "String", paramType = "path")
    @ApiResponses({@ApiResponse(code = 200, message = "操作成功")})
    @PreAuthorize("@customSs.hasPermission('monitor:online:forceLogout')")
    @Log(title = "在线用户", businessType = BusinessType.FORCE)
    @DeleteMapping("/{tokenId}")
    public R forceLogout(@PathVariable String tokenId) {
        redisCache.deleteObject(CacheConstants.LOGIN_TOKEN_KEY + tokenId);
        return R.ok();
    }
}


--- File: play-admin/src/main/java/com/starry/admin/modules/system/mapper/SysAdministrativeAreaDictInfoMapper.java ---
package com.starry.admin.modules.system.mapper;

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.starry.admin.modules.system.module.entity.SysAdministrativeAreaDictInfoEntity;
import org.apache.ibatis.annotations.Mapper;

;

/**
 * 行政区域字典信息Mapper接口
 *
 * @author admin
 * @since 2024-04-03
 */
@Mapper
public interface SysAdministrativeAreaDictInfoMapper extends BaseMapper<SysAdministrativeAreaDictInfoEntity> {

}


--- File: play-admin/src/main/java/com/starry/admin/modules/system/mapper/SysDeptMapper.java ---
package com.starry.admin.modules.system.mapper;

import com.baomidou.mybatisplus.annotation.InterceptorIgnore;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.starry.admin.modules.system.module.entity.SysDeptEntity;
import java.util.List;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;

/**
 * <p>
 * 部门表 Mapper 接口
 * </p>
 *
 * @author admin
 * @since 2022-07-12
 */
@Mapper
public interface SysDeptMapper extends BaseMapper<SysDeptEntity> {

    /**
     * 查询部门管理数据
     *
     * @param dept
     *            部门信息
     * @return 部门信息集合
     */
    List<SysDeptEntity> selectDeptList(@Param(value = "dept") SysDeptEntity dept);

    /**
     * 根据ID查询所有子部门
     *
     * @param deptId
     *            部门ID
     * @return 部门列表
     */
    List<SysDeptEntity> selectChildrenDeptById(Long deptId);

    /**
     * 删除部门信息-根据租户ID
     *
     * @param ids
     *            租户id
     * @return 结果
     */
    @InterceptorIgnore(tenantLine = "1")
    int deleteDeptByTenantId(String[] ids);

    /**
     * 根据角色ID查询部门树信息
     *
     * @param roleId
     *            角色ID
     * @param deptCheckStrictly
     *            部门树选择项是否关联显示
     * @return 选中部门列表
     */
    List<Long> selectDeptListByRoleId(@Param("roleId") Long roleId,
            @Param("deptCheckStrictly") boolean deptCheckStrictly);
}


--- File: play-admin/src/main/java/com/starry/admin/modules/system/mapper/SysDictDataMapper.java ---
package com.starry.admin.modules.system.mapper;

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.starry.admin.modules.system.module.entity.SysDictDataEntity;
import com.starry.admin.modules.system.vo.SysDictDataVo;
import java.util.List;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;

/**
 * 字典数据表Mapper接口
 *
 * @author admin
 */
@Mapper
public interface SysDictDataMapper extends BaseMapper<SysDictDataEntity> {
    /**
     * 查询字典数据表
     *
     * @param dictDataId
     *            字典数据表主键
     * @return 字典数据表
     */
    SysDictDataEntity selectXlDictDataByDictDataId(Long dictDataId);

    /**
     * 查询字典数据表列表
     *
     * @param sysDictDataEntity
     *            字典数据表
     * @return 字典数据表集合
     */
    IPage<SysDictDataEntity> selectXlDictDataList(Page<SysDictDataEntity> page,
            @Param("vo") SysDictDataVo sysDictDataEntity);

    /**
     * 根据字典类型查询字典数据
     *
     * @param dictType
     *            字典类型
     * @return 字典数据集合信息
     */
    List<SysDictDataEntity> selectDictDataByType(String dictType);
}


--- File: play-admin/src/main/java/com/starry/admin/modules/system/mapper/SysDictMapper.java ---
package com.starry.admin.modules.system.mapper;

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.starry.admin.modules.system.module.entity.SysDictEntity;
import com.starry.admin.modules.system.vo.SysDictVo;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;

/**
 * 字典表Mapper接口
 *
 * @author admin
 */
@Mapper
public interface SysDictMapper extends BaseMapper<SysDictEntity> {
    /**
     * 查询字典表
     *
     * @param dictId
     *            字典表主键
     * @return 字典表
     */
    SysDictEntity selectXlDictByDictId(Long dictId);

    /**
     * 查询字典表列表
     *
     * @param sysDictEntity
     *            字典表
     * @return 字典表集合
     */
    Page<SysDictEntity> selectXlDictList(IPage<SysDictEntity> page,
            @Param(value = "sysDictVo") SysDictVo sysDictEntity);

}


--- File: play-admin/src/main/java/com/starry/admin/modules/system/mapper/SysLoginLogMapper.java ---
package com.starry.admin.modules.system.mapper;

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.starry.admin.modules.system.module.entity.SysLoginLogEntity;
import com.starry.admin.modules.system.module.entity.SysLoginLogVo;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;

/**
 * 系统访问日志表Mapper接口
 *
 * @author admin
 */
@Mapper
public interface SysLoginLogMapper extends BaseMapper<SysLoginLogEntity> {
    /**
     * 查询系统访问日志表
     *
     * @param loginId
     *            系统访问日志表主键
     * @return 系统访问日志表
     */
    SysLoginLogEntity selectXlLoginLogByLoginId(Long loginId);

    /**
     * 查询系统访问日志表列表
     *
     * @param sysLoginLogEntity
     *            系统访问日志表
     * @return 系统访问日志表集合
     */
    IPage<SysLoginLogEntity> selectXlLoginLogList(Page<SysLoginLogEntity> page,
            @Param("vo") SysLoginLogVo sysLoginLogEntity);

    /**
     * 清空系统登录日志
     *
     * @return 结果
     */
    int cleanLoginlog();

}


--- File: play-admin/src/main/java/com/starry/admin/modules/system/mapper/SysMenuMapper.java ---
package com.starry.admin.modules.system.mapper;

import com.baomidou.mybatisplus.annotation.InterceptorIgnore;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.starry.admin.modules.system.module.entity.SysMenuEntity;
import com.starry.admin.modules.system.vo.SimpleMenu;
import java.util.List;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;

/**
 * <p>
 * 菜单表 Mapper 接口
 * </p>
 *
 * @author admin
 * @since 2022-07-03
 */
@Mapper
public interface SysMenuMapper extends BaseMapper<SysMenuEntity> {

    List<SysMenuEntity> selectMenuListByUserId(@Param(value = "menu") SysMenuEntity menu,
            @Param(value = "userId") String userId, Page page);

    /**
     * 查询系统菜单列表
     *
     * @param menu
     *            菜单信息
     * @return 菜单列表
     */
    List<SysMenuEntity> selectMenuList(SysMenuEntity menu);

    List<SysMenuEntity> selectMenuListByUserId(@Param(value = "menu") SysMenuEntity menu,
            @Param(value = "userId") String userId);

    /**
     * 根据角色ID查询菜单树信息
     *
     * @param roleId
     *            角色ID
     * @param menuCheckStrictly
     *            菜单树选择项是否关联显示
     * @return 选中菜单列表
     */
    List<Long> selectMenuListByRoleId(@Param("roleId") Long roleId,
            @Param("menuCheckStrictly") boolean menuCheckStrictly);

    /**
     * 根据用户ID查询权限
     *
     * @param userId
     *            用户ID
     * @return 权限列表
     */
    @InterceptorIgnore(tenantLine = "1")
    List<String> selectMenuPermsByUserId(String userId);

    /**
     * 查询菜单
     *
     * @return 菜单列表
     */
    List<SysMenuEntity> selectMenuTreeAll();

    /**
     * 根据用户ID查询菜单
     *
     * @param userId
     *            用户ID
     * @return 菜单列表
     */
    List<SysMenuEntity> selectMenuTreeByUserId(String userId);

    /**
     * 查询所有开启状态菜单精简信息
     *
     * @return 菜单列表
     */
    List<SimpleMenu> selectSimpleMenuList();
}


--- File: play-admin/src/main/java/com/starry/admin/modules/system/mapper/SysOperationLogMapper.java ---
package com.starry.admin.modules.system.mapper;

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.starry.admin.modules.system.module.entity.SysOperationLogEntity;
import com.starry.admin.modules.system.vo.SysOperationLogVo;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;

/**
 * 操作日志表Mapper接口
 *
 * @author admin
 */
@Mapper
public interface SysOperationLogMapper extends BaseMapper<SysOperationLogEntity> {
    /**
     * 查询操作日志表
     *
     * @param operId
     *            操作日志表主键
     * @return 操作日志表
     */
    SysOperationLogEntity selectXlOperLogByOperId(Long operId);

    /**
     * 查询操作日志表列表
     *
     * @param sysOperationLogEntity
     *            操作日志表
     * @return 操作日志表集合
     */
    IPage<SysOperationLogEntity> selectXlOperLogList(Page<SysOperationLogEntity> page,
            @Param("vo") SysOperationLogVo sysOperationLogEntity);

}


--- File: play-admin/src/main/java/com/starry/admin/modules/system/mapper/SysRoleDeptMapper.java ---
package com.starry.admin.modules.system.mapper;

import com.baomidou.mybatisplus.annotation.InterceptorIgnore;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.starry.admin.modules.system.module.entity.SysRoleDeptEntity;
import java.util.List;
import org.apache.ibatis.annotations.Mapper;

/**
 * <p>
 * 角色和部门关联表 Mapper 接口
 * </p>
 *
 * @author admin
 * @since 2022-07-06
 */
@Mapper
public interface SysRoleDeptMapper extends BaseMapper<SysRoleDeptEntity> {

    /**
     * 通过租户ID删除角色和部门关联
     *
     * @param tenantId
     *            租户ID
     * @return 结果
     */
    @InterceptorIgnore(tenantLine = "1")
    int deleteRoleDeptByTenantId(Long tenantId);

    /**
     * 通过角色ID删除角色和部门关联
     *
     * @param roleId
     *            角色ID
     * @return 结果
     */
    int deleteRoleDeptByRoleId(Long roleId);

    /**
     * 批量新增角色部门信息
     *
     * @param roleDeptList
     *            角色部门列表
     * @return 结果
     */
    int batchRoleDept(List<SysRoleDeptEntity> roleDeptList);

    /**
     * 批量删除角色部门关联信息
     *
     * @param ids
     *            需要删除的数据ID
     * @return 结果
     */
    int deleteRoleDept(Long[] ids);
}


--- File: play-admin/src/main/java/com/starry/admin/modules/system/mapper/SysRoleMapper.java ---
package com.starry.admin.modules.system.mapper;

import com.baomidou.mybatisplus.annotation.InterceptorIgnore;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.starry.admin.modules.system.module.entity.SysRoleEntity;
import java.util.List;
import org.apache.ibatis.annotations.Mapper;

/**
 * <p>
 * 角色表 Mapper 接口
 * </p>
 *
 * @author admin
 * @since 2022-07-01
 */
@Mapper
public interface SysRoleMapper extends BaseMapper<SysRoleEntity> {

    /**
     * 通过角色ID查询角色
     *
     * @param roleId
     *            角色ID
     * @return 角色对象信息
     */
    SysRoleEntity selectRoleById(Long roleId);

    /**
     * 校验角色名称是否唯一
     *
     * @param roleName
     *            角色名称
     * @return 角色信息
     */
    SysRoleEntity checkRoleNameUnique(String roleName);

    /**
     * 校验角色权限是否唯一
     *
     * @param roleKey
     *            角色权限
     * @return 角色信息
     */
    SysRoleEntity checkRoleKeyUnique(String roleKey);

    /**
     * 根据用户ID查询角色
     *
     * @param userId
     *            用户ID
     * @return 角色列表
     */
    List<SysRoleEntity> selectRolePermissionByUserId(String userId);

    /**
     * 根据条件分页查询角色数据
     *
     * @param role
     *            角色信息
     * @return 角色数据集合信息
     */
    List<SysRoleEntity> selectRoleList(SysRoleEntity role);

    /**
     * 根据租户ID查询默认管理员角色
     *
     * @param tenantId
     *            租户ID
     * @return 角色
     */
    @InterceptorIgnore(tenantLine = "1")
    List<SysRoleEntity> queryAdminRole(String tenantId);

    /**
     * 批量删除角色信息-根据租户
     *
     * @param ids
     *            需要删除的租户id
     * @return 结果
     */
    @InterceptorIgnore(tenantLine = "1")
    int deleteRoleByTenantId(String[] ids);

    /**
     * 通过角色ID查询角色使用数量
     *
     * @param roleId
     *            角色ID
     * @return 结果
     */
    int countUserRoleByRoleId(Long roleId);

}


--- File: play-admin/src/main/java/com/starry/admin/modules/system/mapper/SysRoleMenuMapper.java ---
package com.starry.admin.modules.system.mapper;

import com.baomidou.mybatisplus.annotation.InterceptorIgnore;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.starry.admin.modules.system.module.entity.SysRoleMenuEntity;
import org.apache.ibatis.annotations.Mapper;

/**
 * <p>
 * 角色和菜单关联表 Mapper 接口
 * </p>
 *
 * @author admin
 * @since 2022-07-06
 */
@Mapper
public interface SysRoleMenuMapper extends BaseMapper<SysRoleMenuEntity> {

    /**
     * 通过租户ID删除角色和菜单关联
     *
     * @param tenantId
     *            租户ID
     * @return 结果
     */
    @InterceptorIgnore(tenantLine = "1")
    int deleteRoleMenuByTenantId(String tenantId);

    /**
     * 通过租户ID删除角色和菜单关联
     *
     * @param ids
     *            租户ID
     * @return 结果
     */
    @InterceptorIgnore(tenantLine = "1")
    int deleteRoleMenuByTenantIds(String[] ids);

    /**
     * 批量删除角色菜单关联信息
     *
     * @param ids
     *            需要删除的数据ID
     * @return 结果
     */
    int deleteRoleMenu(Long[] ids);
}


--- File: play-admin/src/main/java/com/starry/admin/modules/system/mapper/SysTenantMapper.java ---
package com.starry.admin.modules.system.mapper;

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.starry.admin.modules.system.module.entity.SysTenantEntity;
import com.starry.admin.modules.system.vo.SysTenantQueryVo;
import com.starry.admin.modules.system.vo.TenantResultVo;
import org.apache.ibatis.annotations.Param;

/**
 * 租户表Mapper接口
 *
 * @author admin
 * @since 2023-03-03
 */
public interface SysTenantMapper extends BaseMapper<SysTenantEntity> {
    /**
     * 查询租户表
     *
     * @param tenantId
     *            租户表主键
     * @return 租户表
     */
    SysTenantEntity selectSysTenantByTenantId(String tenantId);

    /**
     * 查询租户表列表
     *
     * @param page
     *            分页参数
     * @param sysTenantEntity
     *            租户表
     * @return 租户表集合
     */
    IPage<TenantResultVo> selectSysTenantList(Page<TenantResultVo> page, @Param("vo") SysTenantQueryVo sysTenantEntity);

    /**
     * 批量删除租户管理
     *
     * @param ids
     *            需要删除的数据主键集合
     * @return 结果
     */
    int deleteTenantByIds(String[] ids);

}


--- File: play-admin/src/main/java/com/starry/admin/modules/system/mapper/SysTenantPackageMapper.java ---
package com.starry.admin.modules.system.mapper;

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.starry.admin.modules.system.module.entity.SysTenantPackageEntity;
import com.starry.admin.modules.system.vo.SimplePackage;
import com.starry.admin.modules.system.vo.SysTenantPackageVo;
import java.util.List;
import org.apache.ibatis.annotations.Param;

/**
 * 租户套餐表Mapper接口
 *
 * @author admin
 */
public interface SysTenantPackageMapper extends BaseMapper<SysTenantPackageEntity> {
    /**
     * 查询租户套餐表
     *
     * @param packageId
     *            租户套餐表主键
     * @return 租户套餐表
     */
    SysTenantPackageEntity selectSysTenantPackageByPackageId(String packageId);

    /**
     * 查询租户套餐表列表
     *
     * @param sysTenantPackageEntity
     *            租户套餐表
     * @return 租户套餐表集合
     */
    IPage<SysTenantPackageEntity> selectSysTenantPackageList(Page<SysTenantPackageEntity> page,
            @Param("vo") SysTenantPackageVo sysTenantPackageEntity);

    /**
     * 查询租户套餐
     *
     * @return 租户套餐
     */
    List<SimplePackage> getSimpleList();

}


--- File: play-admin/src/main/java/com/starry/admin/modules/system/mapper/SysTenantRechargeInfoMapper.java ---
package com.starry.admin.modules.system.mapper;

import com.github.yulichang.base.MPJBaseMapper;
import com.starry.admin.modules.system.module.entity.SysTenantRechargeInfoEntity;

/**
 * 租户充值信息Mapper接口
 *
 * @author admin
 * @since 2024-09-05
 */
public interface SysTenantRechargeInfoMapper extends MPJBaseMapper<SysTenantRechargeInfoEntity> {

}


--- File: play-admin/src/main/java/com/starry/admin/modules/system/mapper/SysUserMapper.java ---
package com.starry.admin.modules.system.mapper;

import com.baomidou.mybatisplus.annotation.InterceptorIgnore;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.github.yulichang.base.MPJBaseMapper;
import com.starry.admin.modules.system.module.entity.SysUserEntity;
import com.starry.admin.modules.system.vo.RoleUserResultVo;
import com.starry.admin.modules.system.vo.SysUserQueryVo;
import com.starry.admin.modules.system.vo.UserQueryVo;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;

/**
 * <p>
 * 后台用户表 Mapper 接口
 * </p>
 *
 * @author admin
 * @since 2021-09-03
 */
@Mapper
public interface SysUserMapper extends MPJBaseMapper<SysUserEntity> {

    /**
     * 通过用户ID查询用户
     *
     * @param userId
     *            用户ID
     * @return 用户对象信息
     */
    SysUserEntity selectUserById(String userId);

    /**
     * 根据条件分页查询用户列表
     *
     * @param queryVo
     *            条件信息
     * @param page
     *            分页信息
     * @return 用户信息集合信息
     */
    IPage<SysUserEntity> selectUserList(IPage<SysUserEntity> page, @Param("user") UserQueryVo queryVo);

    /**
     * 通过用户名查询用户
     *
     * @param userName
     *            用户名
     * @return 用户对象信息
     */
    @InterceptorIgnore(tenantLine = "1")
    SysUserEntity selectUserByUserName(String userName);

    /**
     * 校验用户名称是否唯一
     *
     * @param userName
     *            用户名称
     * @return 结果
     */
    @InterceptorIgnore(tenantLine = "1")
    int checkUserNameUnique(String userName);

    /**
     * 批量删除用户信息-根据租户
     *
     * @param ids
     *            需要删除的租户ID
     * @return 结果
     */
    @InterceptorIgnore(tenantLine = "1")
    int deleteUserByTenantId(String[] ids);

    /**
     * 根据条件分页查询已配用户角色列表
     *
     * @param user
     *            用户信息
     * @return 用户信息集合信息
     */
    IPage<RoleUserResultVo> selectAllocatedList(Page<RoleUserResultVo> page, @Param("query") SysUserQueryVo user);

    /**
     * 根据条件分页查询未配用户角色列表
     *
     * @param user
     *            用户信息
     * @return 用户信息集合信息
     */
    IPage<RoleUserResultVo> selectUnallocatedList(Page<RoleUserResultVo> page, @Param("query") SysUserQueryVo user);

    @InterceptorIgnore(tenantLine = "1")
    SysUserEntity selectUserByUserNameAndTenantId(@Param("userName") String userName,
            @Param("tenantId") String tenantId);
}


--- File: play-admin/src/main/java/com/starry/admin/modules/system/mapper/SysUserRoleMapper.java ---
package com.starry.admin.modules.system.mapper;

import com.baomidou.mybatisplus.annotation.InterceptorIgnore;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.starry.admin.modules.system.module.entity.SysUserRoleEntity;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;

/**
 * <p>
 * 用户角色关联表 Mapper 接口
 * </p>
 *
 * @author admin
 * @since 2022-07-08
 */
@Mapper
public interface SysUserRoleMapper extends BaseMapper<SysUserRoleEntity> {

    /**
     * 批量删除用户和角色关联-根据租户
     *
     * @param ids
     *            需要删除的用户租户id
     * @return 结果
     */
    @InterceptorIgnore(tenantLine = "1")
    int deleteUserRoleByTenantId(String[] ids);

    /**
     * 删除用户和角色关联信息
     *
     * @param userRole
     *            用户和角色关联信息
     * @return 结果
     */
    int deleteUserRoleInfo(SysUserRoleEntity userRole);

    /**
     * 批量取消授权用户角色
     *
     * @param roleId
     *            角色ID
     * @param userIds
     *            需要删除的用户数据ID
     * @return 结果
     */
    int deleteUserRoleInfos(@Param("roleId") Long roleId, @Param("userIds") String[] userIds);
}


--- File: play-admin/src/main/java/com/starry/admin/modules/system/module/entity/Cache.java ---
package com.starry.admin.modules.system.module.entity;

import com.starry.common.utils.StringUtils;
import lombok.Data;

/**
 * @author admin 缓存信息
 * @since 2022/10/18
 */
@Data
public class Cache {

    /**
     * 缓存名称
     */
    private String cacheName = "";

    /**
     * 缓存键名
     */
    private String cacheKey = "";

    /**
     * 缓存内容
     */
    private String cacheValue = "";

    /**
     * 备注
     */
    private String remark = "";

    public Cache() {

    }

    public Cache(String cacheName, String remark) {
        this.cacheName = cacheName;
        this.remark = remark;
    }

    public Cache(String cacheName, String cacheKey, String cacheValue) {
        this.cacheName = StringUtils.replace(cacheName, ":", "");
        this.cacheKey = StringUtils.replace(cacheKey, cacheName, "");
        this.cacheValue = cacheValue;
    }
}


--- File: play-admin/src/main/java/com/starry/admin/modules/system/module/entity/Cpu.java ---
package com.starry.admin.modules.system.module.entity;

import com.starry.common.utils.Arith;
import lombok.Data;

/**
 * @author admin cpu相关信息
 * @since 2022/10/17
 */
@Data
public class Cpu {

    /**
     * 核心数
     */
    private int cpuNum;

    /**
     * CPU总的使用率
     */
    private double total;

    /**
     * CPU系统使用率
     */
    private double sys;

    /**
     * CPU用户使用率
     */
    private double used;

    /**
     * CPU当前等待率
     */
    private double wait;

    /**
     * CPU当前空闲率
     */
    private double free;

    public int getCpuNum() {
        return cpuNum;
    }

    public void setCpuNum(int cpuNum) {
        this.cpuNum = cpuNum;
    }

    public double getTotal() {
        return Arith.round(Arith.mul(total, 100), 2);
    }

    public void setTotal(double total) {
        this.total = total;
    }

    public double getSys() {
        return Arith.round(Arith.mul(sys / total, 100), 2);
    }

    public void setSys(double sys) {
        this.sys = sys;
    }

    public double getUsed() {
        return Arith.round(Arith.mul(used / total, 100), 2);
    }

    public void setUsed(double used) {
        this.used = used;
    }

    public double getWait() {
        return Arith.round(Arith.mul(wait / total, 100), 2);
    }

    public void setWait(double wait) {
        this.wait = wait;
    }

    public double getFree() {
        return Arith.round(Arith.mul(free / total, 100), 2);
    }

    public void setFree(double free) {
        this.free = free;
    }
}


--- File: play-admin/src/main/java/com/starry/admin/modules/system/module/entity/Jvm.java ---
package com.starry.admin.modules.system.module.entity;

import cn.hutool.core.date.DateUtil;
import com.starry.common.utils.Arith;
import java.lang.management.ManagementFactory;
import java.util.Date;

/**
 * @author admin 虚拟机相关信息
 * @since 2022/10/17
 */

public class Jvm {

    /**
     * 当前JVM占用的内存总数(M)
     */
    private double total;

    /**
     * JVM最大可用内存总数(M)
     */
    private double max;

    /**
     * JVM空闲内存(M)
     */
    private double free;

    /**
     * JDK版本
     */
    private String version;

    /**
     * JDK路径
     */
    private String home;

    public double getTotal() {
        return Arith.div(total, (1024 * 1024), 2);
    }

    public void setTotal(double total) {
        this.total = total;
    }

    public double getMax() {
        return Arith.div(max, (1024 * 1024), 2);
    }

    public void setMax(double max) {
        this.max = max;
    }

    public double getFree() {
        return Arith.div(free, (1024 * 1024), 2);
    }

    public void setFree(double free) {
        this.free = free;
    }

    public double getUsed() {
        return Arith.div(total - free, (1024 * 1024), 2);
    }

    public double getUsage() {
        return Arith.mul(Arith.div(total - free, total, 4), 100);
    }

    /**
     * 获取JDK名称
     */
    public String getName() {
        return ManagementFactory.getRuntimeMXBean().getVmName();
    }

    public String getVersion() {
        return version;
    }

    public void setVersion(String version) {
        this.version = version;
    }

    public String getHome() {
        return home;
    }

    public void setHome(String home) {
        this.home = home;
    }

    /**
     * JDK启动时间
     */
    public String getStartTime() {
        return DateUtil.format(new Date(ManagementFactory.getRuntimeMXBean().getStartTime()), "yyyy-MM-dd HH:mm:ss");
    }

    /**
     * JDK运行时间
     */
    public String getRunTime() {
        return DateUtil.formatBetween(new Date(), new Date(ManagementFactory.getRuntimeMXBean().getStartTime()));
    }

    /**
     * 运行参数
     */
    public String getInputArgs() {
        return ManagementFactory.getRuntimeMXBean().getInputArguments().toString();
    }
}


--- File: play-admin/src/main/java/com/starry/admin/modules/system/module/entity/Mem.java ---
package com.starry.admin.modules.system.module.entity;

import com.starry.common.utils.Arith;
import lombok.Data;

/**
 * @author admin 内存相关信息
 * @since 2022/10/17
 */
@Data
public class Mem {

    /**
     * 内存总量
     */
    private double total;

    /**
     * 已用内存
     */
    private double used;

    /**
     * 剩余内存
     */
    private double free;

    public double getTotal() {
        return Arith.div(total, (1024 * 1024 * 1024), 2);
    }

    public void setTotal(long total) {
        this.total = total;
    }

    public double getUsed() {
        return Arith.div(used, (1024 * 1024 * 1024), 2);
    }

    public void setUsed(long used) {
        this.used = used;
    }

    public double getFree() {
        return Arith.div(free, (1024 * 1024 * 1024), 2);
    }

    public void setFree(long free) {
        this.free = free;
    }

    public double getUsage() {
        return Arith.mul(Arith.div(used, total, 4), 100);
    }
}


--- File: play-admin/src/main/java/com/starry/admin/modules/system/module/entity/Server.java ---
package com.starry.admin.modules.system.module.entity;

import com.starry.common.utils.Arith;
import com.starry.common.utils.ip.IpUtils;
import java.net.UnknownHostException;
import java.util.LinkedList;
import java.util.List;
import java.util.Properties;
import lombok.Data;
import oshi.SystemInfo;
import oshi.hardware.CentralProcessor;
import oshi.hardware.GlobalMemory;
import oshi.hardware.HardwareAbstractionLayer;
import oshi.software.os.FileSystem;
import oshi.software.os.OSFileStore;
import oshi.software.os.OperatingSystem;
import oshi.util.Util;

/**
 * @author admin 服务器相关信息
 * @since 2022/10/17
 */
@Data
public class Server {

    /**
     * CPU相关信息
     */
    private Cpu cpu = new Cpu();

    /**
     * 內存相关信息
     */
    private Mem mem = new Mem();

    /**
     * JVM相关信息
     */
    private Jvm jvm = new Jvm();

    /**
     * 系统相关信息
     */
    private Sys sys = new Sys();

    /**
     * 磁盘相关信息
     */
    private List<SysFile> sysFiles = new LinkedList<>();

    public void setServeInfo() throws Exception {
        // 系统信息
        SystemInfo si = new SystemInfo();
        // 获取硬件抽象层
        HardwareAbstractionLayer hal = si.getHardware();
        setCpuInfo(hal.getProcessor());
        setMemInfo(hal.getMemory());
        setSysInfo();
        setJvmInfo();
        setSysFiles(si.getOperatingSystem());
    }

    /**
     * 设置CPU信息
     */
    private void setCpuInfo(CentralProcessor processor) {
        // CPU信息
        long[] prevTicks = processor.getSystemCpuLoadTicks();
        Util.sleep(1000);
        long[] ticks = processor.getSystemCpuLoadTicks();
        long nice = ticks[CentralProcessor.TickType.NICE.getIndex()]
                - prevTicks[CentralProcessor.TickType.NICE.getIndex()];
        long irq = ticks[CentralProcessor.TickType.IRQ.getIndex()]
                - prevTicks[CentralProcessor.TickType.IRQ.getIndex()];
        long softirq = ticks[CentralProcessor.TickType.SOFTIRQ.getIndex()]
                - prevTicks[CentralProcessor.TickType.SOFTIRQ.getIndex()];
        long steal = ticks[CentralProcessor.TickType.STEAL.getIndex()]
                - prevTicks[CentralProcessor.TickType.STEAL.getIndex()];
        long cSys = ticks[CentralProcessor.TickType.SYSTEM.getIndex()]
                - prevTicks[CentralProcessor.TickType.SYSTEM.getIndex()];
        long user = ticks[CentralProcessor.TickType.USER.getIndex()]
                - prevTicks[CentralProcessor.TickType.USER.getIndex()];
        long iowait = ticks[CentralProcessor.TickType.IOWAIT.getIndex()]
                - prevTicks[CentralProcessor.TickType.IOWAIT.getIndex()];
        long idle = ticks[CentralProcessor.TickType.IDLE.getIndex()]
                - prevTicks[CentralProcessor.TickType.IDLE.getIndex()];
        long totalCpu = user + nice + cSys + idle + iowait + irq + softirq + steal;
        cpu.setCpuNum(processor.getLogicalProcessorCount());
        cpu.setTotal(totalCpu);
        cpu.setSys(cSys);
        cpu.setUsed(user);
        cpu.setWait(iowait);
        cpu.setFree(idle);
    }

    /**
     * 设置内存信息
     */
    private void setMemInfo(GlobalMemory memory) {
        mem.setTotal(memory.getTotal());
        mem.setUsed(memory.getTotal() - memory.getAvailable());
        mem.setFree(memory.getAvailable());
    }

    /**
     * 设置服务器信息
     */
    private void setSysInfo() {
        Properties properties = System.getProperties();
        sys.setComputerName(IpUtils.getHostName());
        sys.setComputerIp(IpUtils.getHostIp());
        sys.setOsName(properties.getProperty("os.name"));
        sys.setOsArch(properties.getProperty("os.arch"));
        sys.setUserDir(properties.getProperty("user.dir"));
    }

    /**
     * 设置Java虚拟机
     */
    private void setJvmInfo() throws UnknownHostException {
        Properties props = System.getProperties();
        jvm.setTotal(Runtime.getRuntime().totalMemory());
        jvm.setMax(Runtime.getRuntime().maxMemory());
        jvm.setFree(Runtime.getRuntime().freeMemory());
        jvm.setVersion(props.getProperty("java.version"));
        jvm.setHome(props.getProperty("java.home"));
    }

    /**
     * 设置磁盘信息
     */
    private void setSysFiles(OperatingSystem os) {
        FileSystem fileSystem = os.getFileSystem();
        List<OSFileStore> fsArray = fileSystem.getFileStores();
        for (OSFileStore fs : fsArray) {
            long free = fs.getUsableSpace();
            long total = fs.getTotalSpace();
            long used = total - free;
            SysFile sysFile = new SysFile();
            sysFile.setDirName(fs.getMount());
            sysFile.setSysTypeName(fs.getType());
            sysFile.setTypeName(fs.getName());
            sysFile.setTotal(convertFileSize(total));
            sysFile.setFree(convertFileSize(free));
            sysFile.setUsed(convertFileSize(used));
            sysFile.setUsage(Arith.mul(Arith.div(used, total, 4), 100));
            sysFiles.add(sysFile);
        }
    }

    /**
     * 字节转换
     *
     * @param size
     *            字节大小
     * @return 转换后值
     */
    public String convertFileSize(long size) {
        long kb = 1024;
        long mb = kb * 1024;
        long gb = mb * 1024;
        if (size >= gb) {
            return String.format("%.1f GB", (float) size / gb);
        } else if (size >= mb) {
            float f = (float) size / mb;
            return String.format(f > 100 ? "%.0f MB" : "%.1f MB", f);
        } else if (size >= kb) {
            float f = (float) size / kb;
            return String.format(f > 100 ? "%.0f KB" : "%.1f KB", f);
        } else {
            return String.format("%d B", size);
        }
    }
}


--- File: play-admin/src/main/java/com/starry/admin/modules/system/module/entity/Sys.java ---
package com.starry.admin.modules.system.module.entity;

import lombok.Data;

/**
 * @author admin 系统相关信息
 * @since 2022/10/17
 */
@Data
public class Sys {

    /**
     * 服务器名称
     */
    private String computerName;

    /**
     * 服务器Ip
     */
    private String computerIp;

    /**
     * 项目路径
     */
    private String userDir;

    /**
     * 操作系统
     */
    private String osName;

    /**
     * 系统架构
     */
    private String osArch;
}


--- File: play-admin/src/main/java/com/starry/admin/modules/system/module/entity/SysAdministrativeAreaDictInfoEntity.java ---
package com.starry.admin.modules.system.module.entity;

import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableName;
import com.starry.common.domain.BaseEntity;
import java.util.List;
import lombok.Data;
import lombok.EqualsAndHashCode;

/**
 * 行政区域字典信息对象 play_administrative_area_dict_info
 *
 * @author admin
 * @since 2024-04-03
 */
@Data
@EqualsAndHashCode(callSuper = false)
@TableName("sys_administrative_area_dict_info")
public class SysAdministrativeAreaDictInfoEntity extends BaseEntity<SysAdministrativeAreaDictInfoEntity> {

    /**
     * UUID
     */
    private String id;

    /**
     * 行政单位编码
     */
    private String code;

    /**
     * 新增单位名称
     */
    private String name;

    /**
     * 上级单位编码
     */
    private String pCode;

    /**
     * 行政单位等级
     **/
    private String level;

    /**
     * 子数据
     *
     * @since 2024/4/10 15:09
     **/
    @TableField(exist = false)
    private List<SysAdministrativeAreaDictInfoEntity> child;

}


--- File: play-admin/src/main/java/com/starry/admin/modules/system/module/entity/SysDeptEntity.java ---
package com.starry.admin.modules.system.module.entity;

import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import com.starry.common.domain.BaseEntity;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import java.util.ArrayList;
import java.util.List;
import lombok.Data;
import lombok.EqualsAndHashCode;

/**
 * <p>
 * 部门表
 * </p>
 *
 * @author admin
 * @since 2022-07-12
 */
@EqualsAndHashCode(callSuper = true)
@Data
@TableName("sys_dept")
@ApiModel(value = "XlDept对象", description = "部门表")
public class SysDeptEntity extends BaseEntity<SysDeptEntity> {

    private static final long serialVersionUID = 1L;

    @ApiModelProperty("主键ID")
    @TableId(value = "dept_id", type = IdType.AUTO)
    private Long deptId;

    @ApiModelProperty("父部门id")
    private Long parentId;

    @ApiModelProperty("祖级列表")
    private String ancestors;

    @ApiModelProperty("部门名称")
    private String deptName;

    @ApiModelProperty("显示顺序")
    private Integer sort;

    @ApiModelProperty("负责人")
    private String leader;

    @ApiModelProperty("联系电话")
    private String phone;

    @ApiModelProperty("邮箱")
    private String email;

    @ApiModelProperty("部门状态（0正常 1停用）")
    private Integer status;

    @ApiModelProperty("部门级别")
    private Integer deptLevel;

    @ApiModelProperty("租户ID")
    private String tenantId;

    /**
     * 子部门
     */
    @TableField(exist = false)
    private List<SysDeptEntity> children = new ArrayList<>();
}


--- File: play-admin/src/main/java/com/starry/admin/modules/system/module/entity/SysDictDataEntity.java ---
package com.starry.admin.modules.system.module.entity;

import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import com.starry.common.domain.BaseEntity;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.experimental.Accessors;

/**
 * 字典数据表对象 sys_dict_data
 *
 * @author admin
 */
@Data
@EqualsAndHashCode(callSuper = true)
@Accessors(chain = true)
@TableName("sys_dict_data")
public class SysDictDataEntity extends BaseEntity<SysDictDataEntity> {

    private static final long serialVersionUID = 1L;

    /**
     * 主键ID
     */
    @ApiModelProperty("主键ID")
    @TableId(value = "dict_data_id", type = IdType.AUTO)
    private Long dictDataId;

    /**
     * 字典类型
     */
    private String dictType;

    /**
     * 字典名称
     */
    private String dictLabel;

    /**
     * 字典值
     */
    private String dictValue;

    /**
     * 显示顺序
     */
    private Long sort;

    /**
     * 是否默认（Y是 N否）
     */
    private String isDefault;

    /**
     * 表格回显样式
     */
    private String listClass;

    /**
     * 状态（0正常 1停用）
     */
    private Integer status;

    /**
     * 备注
     */
    private String remark;

}


--- File: play-admin/src/main/java/com/starry/admin/modules/system/module/entity/SysDictEntity.java ---
package com.starry.admin.modules.system.module.entity;

import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import com.starry.common.domain.BaseEntity;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.experimental.Accessors;

/**
 * 字典表对象 sys_dict
 *
 * @author admin
 * @since 2022-08-09
 */
@Data
@EqualsAndHashCode(callSuper = true)
@Accessors(chain = true)
@TableName("sys_dict")
public class SysDictEntity extends BaseEntity<SysDictEntity> {

    private static final long serialVersionUID = 1L;

    /**
     * 主键ID
     */
    @ApiModelProperty("主键ID")
    @TableId(value = "dict_id", type = IdType.AUTO)
    private Long dictId;

    /**
     * 字典类型
     */
    private String dictType;

    /**
     * 字典名称
     */
    private String dictName;

    /**
     * 状态（0正常 1停用）
     */
    private Integer status;

    /**
     * 备注
     */
    private String remark;

}


--- File: play-admin/src/main/java/com/starry/admin/modules/system/module/entity/SysFile.java ---
package com.starry.admin.modules.system.module.entity;

import lombok.Data;

/**
 * @author admin 系统文件相关信息
 */
@Data
public class SysFile {

    /**
     * 盘符路径
     */
    private String dirName;

    /**
     * 盘符类型
     */
    private String sysTypeName;

    /**
     * 文件类型
     */
    private String typeName;

    /**
     * 总大小
     */
    private String total;

    /**
     * 剩余大小
     */
    private String free;

    /**
     * 已经使用量
     */
    private String used;

    /**
     * 资源的使用率
     */
    private double usage;
}


--- File: play-admin/src/main/java/com/starry/admin/modules/system/module/entity/SysLoginLogEntity.java ---
package com.starry.admin.modules.system.module.entity;

import com.alibaba.excel.annotation.ExcelIgnoreUnannotated;
import com.alibaba.excel.annotation.ExcelProperty;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import com.starry.admin.modules.system.vo.StatusConverter;
import com.starry.common.domain.BaseEntity;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import java.util.Date;
import lombok.Data;
import lombok.EqualsAndHashCode;

/**
 * 系统访问日志表对象 sys_login_log
 *
 * @author admin
 */
@EqualsAndHashCode(callSuper = true)
@ExcelIgnoreUnannotated
@Data
@TableName("sys_login_log")
@ApiModel(value = "XlLoginLog对象", description = "登录日志表")
public class SysLoginLogEntity extends BaseEntity<SysLoginLogEntity> {

    private static final long serialVersionUID = 1L;

    /**
     * 主键ID
     */
    @ApiModelProperty("主键ID")
    @TableId(value = "login_id", type = IdType.AUTO)
    private Long loginId;

    /**
     * 用户账号
     */
    @ExcelProperty(value = "用户账号")
    private String userCode;

    /**
     * 登录IP地址
     */
    @ExcelProperty(value = "登录IP地址")
    private String ipaddr;

    /**
     * 登录地点
     */
    @ExcelProperty(value = "登录地点")
    private String loginLocation;

    /**
     * 浏览器类型
     */
    @ExcelProperty(value = "浏览器类型")
    private String browser;

    /**
     * 操作系统
     */
    @ExcelProperty(value = "操作系统")
    private String os;

    /**
     * 提示消息
     */
    private String msg;

    /**
     * 登录状态（0成功 1失败）
     */
    @ExcelProperty(value = "登录状态", converter = StatusConverter.class)
    private Integer status;

    /**
     * 访问时间
     */
    private Date loginTime;

}


--- File: play-admin/src/main/java/com/starry/admin/modules/system/module/entity/SysLoginLogVo.java ---
package com.starry.admin.modules.system.module.entity;

import com.alibaba.excel.annotation.ExcelProperty;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.starry.admin.modules.system.vo.StatusConverter;
import com.starry.common.domain.BasePageEntity;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import java.util.Date;
import lombok.Data;
import lombok.EqualsAndHashCode;

/**
 * 系统访问日志表对象 sys_login_log
 *
 * @author admin
 */
@EqualsAndHashCode(callSuper = true)
@Data
@ApiModel(value = "登录日志查询参数", description = "系统访问日志查询条件")
public class SysLoginLogVo extends BasePageEntity {

    private static final long serialVersionUID = 1L;

    /**
     * 主键ID
     */
    @ApiModelProperty(value = "日志ID", example = "1", notes = "主键ID")
    @TableId(value = "login_id", type = IdType.AUTO)
    private Long loginId;

    /**
     * 用户账号
     */
    @ApiModelProperty(value = "用户账号", example = "admin", notes = "登录的用户账号，支持模糊查询")
    @ExcelProperty(value = "用户账号")
    private String userCode;

    /**
     * 登录IP地址
     */
    @ApiModelProperty(value = "登录IP", example = "127.0.0.1", notes = "用户登录的IP地址")
    @ExcelProperty(value = "登录IP地址")
    private String ipaddr;

    /**
     * 登录地点
     */
    @ApiModelProperty(value = "登录地点", example = "北京", notes = "根据IP解析的登录地理位置")
    @ExcelProperty(value = "登录地点")
    private String loginLocation;

    /**
     * 浏览器类型
     */
    @ApiModelProperty(value = "浏览器类型", example = "Chrome", notes = "用户使用的浏览器")
    @ExcelProperty(value = "浏览器类型")
    private String browser;

    /**
     * 操作系统
     */
    @ApiModelProperty(value = "操作系统", example = "Windows 10", notes = "用户使用的操作系统")
    @ExcelProperty(value = "操作系统")
    private String os;

    /**
     * 提示消息
     */
    @ApiModelProperty(value = "提示消息", example = "登录成功", notes = "登录操作的提示信息")
    private String msg;

    /**
     * 登录状态（0成功 1失败）
     */
    @ApiModelProperty(value = "登录状态", example = "0", notes = "0:成功 1:失败")
    @ExcelProperty(value = "登录状态", converter = StatusConverter.class)
    private Integer status;

    /**
     * 访问时间
     */
    @ApiModelProperty(value = "登录时间", example = "2023-01-01 00:00:00", notes = "用户登录的时间")
    private Date loginTime;

}


--- File: play-admin/src/main/java/com/starry/admin/modules/system/module/entity/SysMenuEntity.java ---
package com.starry.admin.modules.system.module.entity;

import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import com.starry.common.domain.BaseEntity;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import java.util.ArrayList;
import java.util.List;
import lombok.Getter;
import lombok.Setter;

/**
 * <p>
 * 菜单表
 * </p>
 *
 * @author admin
 * @since 2022-07-03
 */
@Getter
@Setter
@TableName("sys_menu")
@ApiModel(value = "XlMenu对象", description = "菜单表")
public class SysMenuEntity extends BaseEntity<SysMenuEntity> {

    private static final long serialVersionUID = 1L;

    @ApiModelProperty("菜单ID")
    @TableId(value = "menu_id", type = IdType.AUTO)
    private Long menuId;

    @ApiModelProperty("菜单名")
    private String menuName;

    @ApiModelProperty("菜单编码")
    private String menuCode;

    @ApiModelProperty("权限字符串")
    private String permission;

    @ApiModelProperty("图标")
    private String icon;

    @ApiModelProperty("菜单级别")
    private Long menuLevel;

    @ApiModelProperty("排序")
    private Integer sort;

    @ApiModelProperty("父ID")
    private Long parentId;

    @ApiModelProperty("菜单类型;0:目录,1:菜单,2:按钮")
    private String menuType;

    @ApiModelProperty("菜单状态;0正常 1停用")
    private Integer status;

    @ApiModelProperty("备注")
    private String remark;

    @ApiModelProperty("路由地址")
    private String path;

    @ApiModelProperty("组件路径")
    private String component;

    @ApiModelProperty("路由参数")
    private String routerQuery;

    @ApiModelProperty("是否为外链（0否 1是）")
    private Integer isFrame;

    @ApiModelProperty("菜单状态;0:隐藏 1:显示")
    private Integer visible;

    /**
     * 子菜单
     */
    @TableField(exist = false)
    private List<SysMenuEntity> children = new ArrayList<>();
}


--- File: play-admin/src/main/java/com/starry/admin/modules/system/module/entity/SysOperationLogEntity.java ---
package com.starry.admin.modules.system.module.entity;

import com.alibaba.excel.annotation.ExcelIgnoreUnannotated;
import com.alibaba.excel.annotation.ExcelProperty;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import com.starry.common.domain.BaseEntity;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import java.util.Date;
import lombok.Data;
import lombok.EqualsAndHashCode;

/**
 * 操作日志表对象 sys_operation_log
 *
 * @author admin
 */
@EqualsAndHashCode(callSuper = true)
@ExcelIgnoreUnannotated
@Data
@TableName("sys_operation_log")
@ApiModel(value = "SysOperationLog对象", description = "操作日志表")
public class SysOperationLogEntity extends BaseEntity<SysOperationLogEntity> {

    private static final long serialVersionUID = 1L;

    /**
     * 主键ID
     */
    @ApiModelProperty("主键ID")
    @TableId(value = "oper_id", type = IdType.AUTO)
    private Long operId;

    /**
     * 模块标题
     */
    @ExcelProperty(value = "系统模块")
    private String title;

    /**
     * 业务类型（0其它 1新增 2修改 3删除）
     */
    private Integer businessType;

    /**
     * 业务类型数组
     */
    @TableField(exist = false)
    private Integer[] businessTypes;

    /**
     * 方法名称
     */
    @ExcelProperty(value = "方法名称")
    private String method;

    /**
     * 请求方式
     */
    private String requestMethod;

    /**
     * 操作类别（0其它 1后台用户 2手机端用户）
     */
    private Integer operatorType;

    /**
     * 操作人员
     */
    private String operName;

    /**
     * 请求URL
     */
    private String operUrl;

    /**
     * 主机地址
     */
    private String operIp;

    /**
     * 操作地点
     */
    private String operLocation;

    /**
     * 请求参数
     */
    private String operParam;

    /**
     * 返回参数
     */
    private String jsonResult;

    /**
     * 操作状态（0正常 1异常）
     */
    private Integer status;

    /**
     * 错误消息
     */
    private String errorMsg;

    /**
     * 操作时间
     */
    private Date operTime;

    /**
     * 部门名称
     */
    private String deptName;

    /**
     * 部门id
     */
    private Long deptId;

    /**
     * 租户ID
     */
    private Long tenantId;
}


--- File: play-admin/src/main/java/com/starry/admin/modules/system/module/entity/SysRoleDeptEntity.java ---
package com.starry.admin.modules.system.module.entity;

import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import com.starry.common.domain.BaseEntity;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.experimental.Accessors;

/**
 * 角色部门表对象 sys_dict_data
 *
 * @author admin
 */
@Data
@EqualsAndHashCode(callSuper = true)
@Accessors(chain = true)
@TableName("sys_role_dept")
public class SysRoleDeptEntity extends BaseEntity<SysRoleDeptEntity> {

    private static final long serialVersionUID = 1L;

    /**
     * 主键ID
     */
    @ApiModelProperty("主键ID")
    @TableId(value = "role_dept_id", type = IdType.AUTO)
    private Long roleDeptId;

    @ApiModelProperty("角色ID")
    private Long roleId;

    @ApiModelProperty("菜单ID")
    private Long deptId;

    @ApiModelProperty("租户ID")
    private Long tenantId;

}


--- File: play-admin/src/main/java/com/starry/admin/modules/system/module/entity/SysRoleEntity.java ---
package com.starry.admin.modules.system.module.entity;

import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import com.starry.common.domain.BaseEntity;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import java.util.Set;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;

/**
 * <p>
 * 角色表
 * </p>
 *
 * @author admin
 * @since 2022-07-01
 */
@EqualsAndHashCode(callSuper = true)
@Data
@TableName("sys_role")
@NoArgsConstructor
@ApiModel(value = "XlRole对象", description = "角色表")
public class SysRoleEntity extends BaseEntity<SysRoleEntity> {

    private static final long serialVersionUID = 1L;

    @ApiModelProperty("角色ID")
    @TableId(value = "role_id", type = IdType.AUTO)
    private Long roleId;

    @ApiModelProperty("角色名称")
    private String roleName;

    @ApiModelProperty("角色描述")
    private String description;

    @ApiModelProperty("状态;0正常 1停用")
    private Integer status;

    @ApiModelProperty("数据范围（1：所有数据权限；2：自定义数据权限；3：本部门数据权限；4：本部门及以下数据权限；5：仅本人数据权限） ")
    private String dataScope;

    @ApiModelProperty("菜单树选择项是否关联显示（ 0：父子不互相关联显示 1：父子互相关联显示）")
    private boolean menuCheckStrictly;

    @ApiModelProperty("部门树选择项是否关联显示（0：父子不互相关联显示 1：父子互相关联显示 ）")
    private boolean deptCheckStrictly;

    @ApiModelProperty("角色权限")
    private String roleKey;

    @ApiModelProperty("租户ID")
    private String tenantId;

    /**
     * 菜单组
     */
    @TableField(exist = false)
    private Long[] menuIds;

    /**
     * 部门组（数据权限）
     */
    @TableField(exist = false)
    private Long[] deptIds;

    /**
     * 角色菜单权限
     */
    @TableField(exist = false)
    private Set<String> permissions;

    public SysRoleEntity(Long roleId) {
        this.roleId = roleId;
    }

    public static boolean isAdmin(Long roleId) {
        return roleId != null && 1L == roleId;
    }

    public boolean isAdmin() {
        return isAdmin(this.roleId);
    }
}


--- File: play-admin/src/main/java/com/starry/admin/modules/system/module/entity/SysRoleMenuEntity.java ---
package com.starry.admin.modules.system.module.entity;

import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import com.starry.common.domain.BaseEntity;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Getter;
import lombok.Setter;

/**
 * <p>
 * 角色和菜单关联表
 * </p>
 *
 * @author admin
 * @since 2022-07-06
 */
@Getter
@Setter
@TableName("sys_role_menu")
@ApiModel(value = "SysRoleMenu对象", description = "角色和菜单关联表")
public class SysRoleMenuEntity extends BaseEntity<SysRoleMenuEntity> {

    private static final long serialVersionUID = 1L;

    @ApiModelProperty("主键id")
    @TableId(value = "role_menu_id", type = IdType.AUTO)
    private Long roleMenuId;

    @ApiModelProperty("角色ID")
    private Long roleId;

    @ApiModelProperty("菜单ID")
    private Long menuId;

    @ApiModelProperty("租户ID")
    private String tenantId;

}


--- File: play-admin/src/main/java/com/starry/admin/modules/system/module/entity/SysTenantEntity.java ---
package com.starry.admin.modules.system.module.entity;

import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import com.starry.common.domain.BaseEntity;
import io.swagger.annotations.ApiModelProperty;
import java.util.Date;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.experimental.Accessors;

/**
 * 租户表对象 sys_tenant
 *
 * @author admin
 */
@Data
@EqualsAndHashCode(callSuper = true)
@Accessors(chain = true)
@TableName("sys_tenant")
public class SysTenantEntity extends BaseEntity<SysTenantEntity> {

    private static final long serialVersionUID = 1L;

    /**
     * 租户ID
     */
    @ApiModelProperty("主键ID")
    @TableId(value = "tenant_id", type = IdType.AUTO)
    private String tenantId;

    /**
     * 租户类型;0:公司;1:部门;2:个人;3:其他
     */
    private String tenantType;

    /**
     * 用户状态;0:正常,1:停用
     */
    private String tenantStatus;

    /**
     * 编码
     */
    private String tenantCode;

    /**
     * 名称
     */
    private String tenantName;

    /**
     * logo图
     */
    private String tenantLogo;

    /**
     * 显示顺序
     */
    private Long sortOrder;

    /**
     * 官方地址
     */
    private String homeUrl;

    /**
     * 电话
     */
    private String phone;

    /**
     * 传真
     */
    private String fax;

    /**
     * 邮箱
     */
    private String email;

    /**
     * 地址
     */
    private String address;

    /**
     * 管理员账号
     */
    private String userName;

    /**
     * 管理员密码
     */
    private String userPwd;

    /**
     * 租户套餐
     */
    private String packageId;

    /**
     * 租赁到期时间
     */
    private Date tenantTime;

    /**
     * 租户权限标识
     */
    private String tenantKey;

    /**
     * 微信公众号ID
     */
    private String appId;

    /**
     * 微信公众号的app secret
     */
    private String secret;

    /**
     * 微信商户ID
     */
    private String mchId;

    /**
     * 设置微信商户秘钥
     */
    private String mchKey;

    /**
     * 商户秘钥
     */
    private String certificatePath;

    /**
     * 备注
     */
    private String remarks;

    // 充值成功通知模板id
    private String czcgtzTemplateId;
    // 下单通知模板id
    private String xdcgtzTemplateId;
    // 收到新订单通知模板id
    private String sdxddtzTemplateId;
    // 认证申请审核通知模板id
    private String rzsqshtxTemplateId;
    // 认证申请通知for管理员
    private String rzsqAdminTemplateId;

    private String dingdantousuchuliTemplateId;
    private String jiedanchenggongtongzhiTemplateId;
    private String quxiaodingdantongzhiTemplateId;
    private String dingdanwanchengtongzhiTemplateId;
    private String chongzhichenggongTemplateId;
    private String xindingdanshoulitongzhiTemplateId;

    private Integer profitsharingRate = 0;

}


--- File: play-admin/src/main/java/com/starry/admin/modules/system/module/entity/SysTenantPackageEntity.java ---
package com.starry.admin.modules.system.module.entity;

import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import com.starry.common.domain.BaseEntity;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.experimental.Accessors;

/**
 * 租户套餐表对象 sys_tenant_package
 *
 * @author admin
 */
@Data
@EqualsAndHashCode(callSuper = true)
@Accessors(chain = true)
@TableName("sys_tenant_package")
public class SysTenantPackageEntity extends BaseEntity<SysTenantPackageEntity> {

    private static final long serialVersionUID = 1L;

    /**
     * 套餐id
     */
    @ApiModelProperty("主键ID")
    @TableId(value = "package_id", type = IdType.AUTO)
    private String packageId;

    /**
     * 名称
     */
    private String packageName;

    /**
     * 关联菜单id
     */
    private String menuIds;

    /**
     * 状态;0:正常,1:停用
     */
    private String status;

    /**
     * 备注
     */
    private String remarks;

}


--- File: play-admin/src/main/java/com/starry/admin/modules/system/module/entity/SysTenantRechargeInfoEntity.java ---
package com.starry.admin.modules.system.module.entity;

import com.baomidou.mybatisplus.annotation.TableName;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.starry.common.domain.BaseEntity;
import java.time.LocalDateTime;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.springframework.format.annotation.DateTimeFormat;

/**
 * 租户充值信息对象 sys_tenant_recharge_info
 *
 * @author admin
 * @since 2024-09-05
 */
@Data
@EqualsAndHashCode(callSuper = false)
@TableName("sys_tenant_recharge_info")
public class SysTenantRechargeInfoEntity extends BaseEntity<SysTenantRechargeInfoEntity> {

    /**
     * UUID
     */
    private String id;

    /**
     * 租户ID
     */
    private String tenantId;

    /**
     * 充值金额
     */
    private String rechargeAmount;

    /**
     * 充值用户
     */
    private String rechargeUser;

    /**
     * 充值时间
     */
    @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
    private LocalDateTime rechargeTime;

    /**
     * 支付方式，1:微信支付,2:支付宝支付;3:银行卡支付
     */
    private String payMethod;

    /**
     * 支付账号
     */
    private String payAccount;

    /**
     * 备注
     */
    private String remark;

}


--- File: play-admin/src/main/java/com/starry/admin/modules/system/module/entity/SysUserEntity.java ---
package com.starry.admin.modules.system.module.entity;

import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.starry.common.domain.BaseEntity;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import java.time.LocalDateTime;
import java.util.Date;
import java.util.List;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.experimental.Accessors;
import org.springframework.format.annotation.DateTimeFormat;

/**
 * <p>
 * 用户表
 * </p>
 *
 * @author admin
 */
@Data
@EqualsAndHashCode(callSuper = false)
@Accessors(chain = true)
@ApiModel(value = "SysUser对象", description = "用户表")
@TableName("sys_user")
public class SysUserEntity extends BaseEntity<SysUserEntity> {

    private static final long serialVersionUID = 1L;

    @ApiModelProperty(value = "用户ID")
    @TableId(value = "user_id", type = IdType.AUTO)
    private String userId;

    @ApiModelProperty(value = "用户类型;1:admin;2:普通，废弃字段")
    private Integer userType;

    @ApiModelProperty(value = "是否为超管")
    private Boolean superAdmin;

    @ApiModelProperty(value = "用户名")
    private String userCode;

    @ApiModelProperty(value = "密码")
    private String passWord;

    @ApiModelProperty(value = "姓名")
    private String realName;

    @ApiModelProperty(value = "用户昵称")
    private String userNickname;

    @ApiModelProperty(value = "邮箱")
    private String userEmail;

    @ApiModelProperty(value = "用户头像")
    private String avatar;

    @ApiModelProperty(value = "中国手机不带国家代码，国际手机号格式为：国家代码-手机号")
    private String mobile;

    @ApiModelProperty(value = "性别 0：未知；1：男；2：女")
    private Integer sex;

    @ApiModelProperty(value = "出生年月")
    private Date birthday;

    @ApiModelProperty(value = "用户状态;0正常 1停用")
    private Integer status;

    @ApiModelProperty(value = "最后登录时间")
    private Date lastLoginTime;

    @ApiModelProperty(value = "最后登录ip")
    private String lastLoginIp;

    @ApiModelProperty(value = "所属部门id")
    private Long deptId;

    @ApiModelProperty(value = "租户id")
    private String tenantId;

    @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
    private LocalDateTime addTime;

    private String clerkId;

    /**
     * 角色对象
     */
    @TableField(exist = false)
    private List<SysRoleEntity> roles;

    /**
     * 角色组
     */
    @TableField(exist = false)
    private Long[] roleIds;

    /**
     * 部门对象
     */
    @TableField(exist = false)
    private SysDeptEntity dept;

    /**
     * 角色ID
     */
    @TableField(exist = false)
    private Long roleId;

    public static boolean isAdmin(SysUserEntity user) {
        return user.getSuperAdmin();
    }

}


--- File: play-admin/src/main/java/com/starry/admin/modules/system/module/entity/SysUserRoleEntity.java ---
package com.starry.admin.modules.system.module.entity;

import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import com.starry.common.domain.BaseEntity;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Getter;
import lombok.Setter;

/**
 * <p>
 * 用户角色关联表
 * </p>
 *
 * @author admin
 * @since 2022-07-08
 */
@Getter
@Setter
@TableName("sys_user_role")
@ApiModel(value = "SysUserRole对象", description = "用户角色关联表")
public class SysUserRoleEntity extends BaseEntity<SysUserRoleEntity> {

    private static final long serialVersionUID = 1L;

    @ApiModelProperty("主键ID")
    @TableId(value = "user_role_id", type = IdType.AUTO)
    private Long userRoleId;

    @ApiModelProperty("用户ID")
    private String userId;

    @ApiModelProperty("角色ID")
    private Long roleId;

    @ApiModelProperty(value = "租户id")
    private String tenantId;
}


--- File: play-admin/src/main/java/com/starry/admin/modules/system/module/entity/UserOnline.java ---
package com.starry.admin.modules.system.module.entity;

import lombok.Data;

/**
 * @author admin 当前在线会话
 */
@Data
public class UserOnline {
    /**
     * 会话编号
     */
    private String tokenId;

    /**
     * 部门名称
     */
    private String deptName;

    /**
     * 用户名称
     */
    private String userName;

    /**
     * 登录IP地址
     */
    private String ipaddr;

    /**
     * 登录地址
     */
    private String loginLocation;

    /**
     * 浏览器类型
     */
    private String browser;

    /**
     * 操作系统
     */
    private String os;

    /**
     * 登录时间
     */
    private Long loginTime;
}


--- File: play-admin/src/main/java/com/starry/admin/modules/system/module/vo/SysTenantRechargeInfoAddVo.java ---
package com.starry.admin.modules.system.module.vo;

import java.time.LocalDateTime;
import lombok.Data;

/**
 * @author admin
 * @since 2024/9/5 下午3:44
 **/
@Data
public class SysTenantRechargeInfoAddVo {

    /**
     * 充值金额
     */
    private String rechargeAmount;

    /**
     * 充值用户
     */
    private String rechargeUser;

    /**
     * 充值时间
     */
    private LocalDateTime rechargeTime = LocalDateTime.now();

    /**
     * 支付方式，1:微信支付,2:支付宝支付;3:银行卡支付
     */
    private String payMethod;

    /**
     * 支付账号
     */
    private String payAccount;

    /**
     * 备注
     */
    private String remark;
}


--- File: play-admin/src/main/java/com/starry/admin/modules/system/module/vo/SysTenantRechargeInfoQueryVo.java ---
package com.starry.admin.modules.system.module.vo;

import com.starry.common.domain.BasePageEntity;
import lombok.Data;
import lombok.EqualsAndHashCode;

/**
 * @author admin
 * @since 2024/9/5 下午3:44
 **/
@EqualsAndHashCode(callSuper = true)
@Data
public class SysTenantRechargeInfoQueryVo extends BasePageEntity {

}


--- File: play-admin/src/main/java/com/starry/admin/modules/system/module/vo/SysTenantRechargeInfoReturnVo.java ---
package com.starry.admin.modules.system.module.vo;

import com.fasterxml.jackson.annotation.JsonFormat;
import java.time.LocalDateTime;
import lombok.Data;
import org.springframework.format.annotation.DateTimeFormat;

/**
 * @author admin
 * @since 2024/9/5 下午3:44
 **/
@Data
public class SysTenantRechargeInfoReturnVo {
    private String id;

    /**
     * 充值金额
     */
    private String rechargeAmount;

    /**
     * 充值用户
     */
    private String rechargeUser;

    /**
     * 充值用户
     */
    private String rechargeName;

    /**
     * 充值时间
     */
    @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
    private LocalDateTime rechargeTime;

    /**
     * 支付方式，1:微信支付,2:支付宝支付;3:银行卡支付
     */
    private String payMethod;

    /**
     * 支付账号
     */
    private String payAccount;

    /**
     * 备注
     */
    private String remark;

}


--- File: play-admin/src/main/java/com/starry/admin/modules/system/service/ISysAdministrativeAreaDictInfoService.java ---
package com.starry.admin.modules.system.service;

import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.service.IService;
import com.starry.admin.modules.system.module.entity.SysAdministrativeAreaDictInfoEntity;
import com.starry.admin.modules.system.vo.AdministrativeAreaQueryReturnVo;
import java.util.List;

/**
 * 行政区域字典信息Service接口
 *
 * @author admin
 * @since 2024-04-03
 */
public interface ISysAdministrativeAreaDictInfoService extends IService<SysAdministrativeAreaDictInfoEntity> {

    /**
     * 查询行政区域字典信息
     *
     * @param id
     *            行政区域字典信息主键
     * @return 行政区域字典信息
     */
    SysAdministrativeAreaDictInfoEntity selectPlayAdministrativeAreaDictInfoById(String id);

    /**
     * 查询树形接口的区域信息
     *
     * @param level
     *            行政区域等级（1：省级别,2:城市级别:4:区县级别）
     * @return List<AdministrativeAreaQueryReturnVo>
     * @author admin
     * @since 2024/4/10 14:47
     **/
    List<AdministrativeAreaQueryReturnVo> selectTree(String level);

    /**
     * 查询所有行政区域字典信息列表
     *
     * @return 行政区域字典信息集合
     */
    List<SysAdministrativeAreaDictInfoEntity> selectAll();

    /**
     * 查询行政区域字典信息列表
     *
     * @param playAdministrativeAreaDictInfo
     *            行政区域字典信息
     * @return 行政区域字典信息集合
     */
    IPage<SysAdministrativeAreaDictInfoEntity> selectPlayAdministrativeAreaDictInfoByPage(
            SysAdministrativeAreaDictInfoEntity playAdministrativeAreaDictInfo);

    /**
     * 新增行政区域字典信息
     *
     * @param playAdministrativeAreaDictInfo
     *            行政区域字典信息
     * @return 结果
     */
    boolean create(SysAdministrativeAreaDictInfoEntity playAdministrativeAreaDictInfo);

    boolean create(List<SysAdministrativeAreaDictInfoEntity> list);

    /**
     * 修改行政区域字典信息
     *
     * @param playAdministrativeAreaDictInfo
     *            行政区域字典信息
     * @return 结果
     */
    boolean update(SysAdministrativeAreaDictInfoEntity playAdministrativeAreaDictInfo);

    /**
     * 批量删除行政区域字典信息
     *
     * @param ids
     *            需要删除的行政区域字典信息主键集合
     * @return 结果
     */
    int deletePlayAdministrativeAreaDictInfoByIds(String[] ids);

    /**
     * 删除行政区域字典信息信息
     *
     * @param id
     *            行政区域字典信息主键
     * @return 结果
     */
    int deletePlayAdministrativeAreaDictInfoById(String id);
}


--- File: play-admin/src/main/java/com/starry/admin/modules/system/service/ISysDictDataService.java ---
package com.starry.admin.modules.system.service;

import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.service.IService;
import com.starry.admin.modules.system.module.entity.SysDictDataEntity;
import com.starry.admin.modules.system.vo.SysDictDataVo;
import java.util.List;

/**
 * 字典数据表Service接口
 *
 * @author admin
 */
public interface ISysDictDataService extends IService<SysDictDataEntity> {
    /**
     * 查询字典数据表
     *
     * @param dictDataId
     *            字典数据表主键
     * @return 字典数据表
     */
    SysDictDataEntity selectXlDictDataByDictDataId(Long dictDataId);

    /**
     * 查询字典数据表列表
     *
     * @param sysDictDataEntity
     *            字典数据表
     * @return 字典数据表集合
     */
    IPage<SysDictDataEntity> selectXlDictDataList(SysDictDataVo sysDictDataEntity);

    /**
     * 新增字典数据表
     *
     * @param sysDictDataEntity
     *            字典数据表
     * @return 结果
     */
    boolean create(SysDictDataEntity sysDictDataEntity);

    /**
     * 修改字典数据表
     *
     * @param sysDictDataEntity
     *            字典数据表
     * @return 结果
     */
    boolean update(SysDictDataEntity sysDictDataEntity);

    /**
     * 批量删除字典数据表
     *
     * @param dictDataIds
     *            需要删除的字典数据表主键集合
     * @return 结果
     */
    int deleteXlDictDataByDictDataIds(Long[] dictDataIds);

    /**
     * 删除字典数据表信息
     *
     * @param dictDataId
     *            字典数据表主键
     * @return 结果
     */
    int deleteXlDictDataByDictDataId(Long dictDataId);

    /**
     * 根据字典类型查询字典数据
     *
     * @param dictType
     *            字典类型
     * @return 字典数据集合信息
     */
    List<SysDictDataEntity> selectDictDataByType(String dictType);
}


--- File: play-admin/src/main/java/com/starry/admin/modules/system/service/ISysDictService.java ---
package com.starry.admin.modules.system.service;

import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.IService;
import com.starry.admin.modules.system.module.entity.SysDictEntity;
import com.starry.admin.modules.system.vo.SysDictVo;
import java.util.List;

/**
 * 字典表Service接口
 *
 * @author admin
 * @since 2022-08-09
 */
public interface ISysDictService extends IService<SysDictEntity> {
    /**
     * 查询字典表
     *
     * @param dictId
     *            字典表主键
     * @return 字典表
     */
    SysDictEntity selectXlDictByDictId(Long dictId);

    /**
     * 查询字典表列表
     *
     * @param sysDictVo
     *            字典表
     * @return 字典表集合
     */
    Page<SysDictEntity> selectXlDictList(SysDictVo sysDictVo);

    /**
     * 新增字典表
     *
     * @param sysDictEntity
     *            字典表
     * @return 结果
     */
    boolean create(SysDictEntity sysDictEntity);

    /**
     * 修改字典表
     *
     * @param sysDictEntity
     *            字典表
     * @return 结果
     */
    boolean update(SysDictEntity sysDictEntity);

    /**
     * 批量删除字典表
     *
     * @param dictIds
     *            需要删除的字典表主键集合
     * @return 结果
     */
    int deleteXlDictByDictIds(Long[] dictIds);

    /**
     * 删除字典表信息
     *
     * @param dictId
     *            字典表主键
     * @return 结果
     */
    int deleteXlDictByDictId(Long dictId);

    /**
     * 根据所有字典类型
     *
     * @return 字典类型集合信息
     */
    List<SysDictEntity> selectDictTypeAll();

    /**
     * 加载字典缓存数据
     */
    void loadingDictCache();

    /**
     * 清空字典缓存数据
     */
    void clearDictCache();

    /**
     * 重置字典缓存数据
     */
    void resetDictCache();

}


--- File: play-admin/src/main/java/com/starry/admin/modules/system/service/ISysLoginLogService.java ---
package com.starry.admin.modules.system.service;

import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.service.IService;
import com.starry.admin.modules.system.module.entity.SysLoginLogEntity;
import com.starry.admin.modules.system.module.entity.SysLoginLogVo;

/**
 * 系统访问日志表Service接口
 *
 * @author admin
 */
public interface ISysLoginLogService extends IService<SysLoginLogEntity> {
    /**
     * 查询系统访问日志表
     *
     * @param loginId
     *            系统访问日志表主键
     * @return 系统访问日志表
     */
    SysLoginLogEntity selectXlLoginLogByLoginId(Long loginId);

    /**
     * 查询系统访问日志表列表
     *
     * @param sysLoginLogEntity
     *            系统访问日志表
     * @return 系统访问日志表集合
     */
    IPage<SysLoginLogEntity> selectXlLoginLogList(SysLoginLogVo sysLoginLogEntity);

    /**
     * 新增系统访问日志表
     *
     * @param sysLoginLogEntity
     *            系统访问日志表
     * @return 结果
     */
    boolean create(SysLoginLogEntity sysLoginLogEntity);

    /**
     * 修改系统访问日志表
     *
     * @param sysLoginLogEntity
     *            系统访问日志表
     * @return 结果
     */
    boolean update(SysLoginLogEntity sysLoginLogEntity);

    /**
     * 批量删除系统访问日志表
     *
     * @param loginIds
     *            需要删除的系统访问日志表主键集合
     * @return 结果
     */
    int deleteXlLoginLogByLoginIds(Long[] loginIds);

    /**
     * 删除系统访问日志表信息
     *
     * @param loginId
     *            系统访问日志表主键
     * @return 结果
     */
    int deleteXlLoginLogByLoginId(Long loginId);

    /**
     * 清空系统登录日志
     */
    void cleanLoginlog();
}


--- File: play-admin/src/main/java/com/starry/admin/modules/system/service/ISysOperationLogService.java ---
package com.starry.admin.modules.system.service;

import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.service.IService;
import com.starry.admin.modules.system.module.entity.SysOperationLogEntity;
import com.starry.admin.modules.system.vo.SysOperationLogVo;

/**
 * 操作日志表Service接口
 *
 * @author admin
 */
public interface ISysOperationLogService extends IService<SysOperationLogEntity> {
    /**
     * 查询操作日志表
     *
     * @param operId
     *            操作日志表主键
     * @return 操作日志表
     */
    SysOperationLogEntity selectXlOperLogByOperId(Long operId);

    /**
     * 查询操作日志表列表
     *
     * @param sysOperationLogEntity
     *            操作日志表
     * @return 操作日志表集合
     */
    IPage<SysOperationLogEntity> selectXlOperLogList(SysOperationLogVo sysOperationLogEntity);

    /**
     * 新增操作日志表
     *
     * @param sysOperationLogEntity
     *            操作日志表
     * @return 结果
     */
    boolean create(SysOperationLogEntity sysOperationLogEntity);

    /**
     * 修改操作日志表
     *
     * @param sysOperationLogEntity
     *            操作日志表
     * @return 结果
     */
    boolean updateXlOperLog(SysOperationLogEntity sysOperationLogEntity);

    /**
     * 批量删除操作日志表
     *
     * @param operIds
     *            需要删除的操作日志表主键集合
     * @return 结果
     */
    int deleteXlOperLogByOperIds(Long[] operIds);

    /**
     * 删除操作日志表信息
     *
     * @param operId
     *            操作日志表主键
     * @return 结果
     */
    int deleteXlOperLogByOperId(Long operId);
}


--- File: play-admin/src/main/java/com/starry/admin/modules/system/service/ISysTenantPackageService.java ---
package com.starry.admin.modules.system.service;

import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.service.IService;
import com.starry.admin.modules.system.module.entity.SysTenantPackageEntity;
import com.starry.admin.modules.system.vo.SimplePackage;
import com.starry.admin.modules.system.vo.SysTenantPackageVo;
import java.util.List;

/**
 * 租户套餐表Service接口
 *
 * @author admin
 */
public interface ISysTenantPackageService extends IService<SysTenantPackageEntity> {
    /**
     * 查询租户套餐表
     *
     * @param packageId
     *            租户套餐表主键
     * @return 租户套餐表
     */
    SysTenantPackageEntity selectSysTenantPackageByPackageId(String packageId);

    /**
     * 查询租户套餐表列表
     *
     * @param sysTenantPackageEntity
     *            租户套餐表
     * @return 租户套餐表集合
     */
    IPage<SysTenantPackageEntity> selectSysTenantPackageList(SysTenantPackageVo sysTenantPackageEntity);

    /**
     * 新增租户套餐表
     *
     * @param sysTenantPackageEntity
     *            租户套餐表
     * @return 结果
     */
    boolean create(SysTenantPackageEntity sysTenantPackageEntity);

    /**
     * 修改租户套餐表
     *
     * @param sysTenantPackageEntity
     *            租户套餐表
     * @return 结果
     */
    boolean update(SysTenantPackageEntity sysTenantPackageEntity);

    /**
     * 批量删除租户套餐表
     *
     * @param packageIds
     *            需要删除的租户套餐表主键集合
     * @return 结果
     */
    int deleteSysTenantPackageByPackageIds(String[] packageIds);

    /**
     * 删除租户套餐表信息
     *
     * @param packageId
     *            租户套餐表主键
     * @return 结果
     */
    int deleteSysTenantPackageByPackageId(Long packageId);

    /**
     * 查询租户套餐
     *
     * @return 租户套餐
     */
    List<SimplePackage> getSimpleList();

}


--- File: play-admin/src/main/java/com/starry/admin/modules/system/service/ISysTenantRechargeInfoService.java ---
package com.starry.admin.modules.system.service;

import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.service.IService;
import com.starry.admin.modules.system.module.entity.SysTenantRechargeInfoEntity;
import com.starry.admin.modules.system.module.vo.SysTenantRechargeInfoQueryVo;
import com.starry.admin.modules.system.module.vo.SysTenantRechargeInfoReturnVo;

/**
 * 租户充值信息Service接口
 *
 * @author admin
 * @since 2024-09-05
 */
public interface ISysTenantRechargeInfoService extends IService<SysTenantRechargeInfoEntity> {
    /**
     * 查询租户充值信息
     *
     * @param id
     *            租户充值信息主键
     * @return 租户充值信息
     */
    SysTenantRechargeInfoEntity selectSysTenantRechargeInfoById(String id);

    /**
     * 查询租户充值信息列表
     *
     * @param queryVo
     *            租户充值信息分页查询对象
     * @return 租户充值信息集合
     */
    IPage<SysTenantRechargeInfoReturnVo> selectByPage(SysTenantRechargeInfoQueryVo queryVo);

    /**
     * 新增租户充值信息
     *
     * @param sysTenantRechargeInfo
     *            租户充值信息
     * @return 结果
     */
    boolean create(SysTenantRechargeInfoEntity sysTenantRechargeInfo);

    /**
     * 修改租户充值信息
     *
     * @param sysTenantRechargeInfo
     *            租户充值信息
     * @return 结果
     */
    boolean update(SysTenantRechargeInfoEntity sysTenantRechargeInfo);

    /**
     * 批量删除租户充值信息
     *
     * @param ids
     *            需要删除的租户充值信息主键集合
     * @return 结果
     */
    int deleteSysTenantRechargeInfoByIds(String[] ids);

    /**
     * 删除租户充值信息信息
     *
     * @param id
     *            租户充值信息主键
     * @return 结果
     */
    int deleteSysTenantRechargeInfoById(String id);
}


--- File: play-admin/src/main/java/com/starry/admin/modules/system/service/ISysTenantService.java ---
package com.starry.admin.modules.system.service;

import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.service.IService;
import com.starry.admin.modules.system.module.entity.SysTenantEntity;
import com.starry.admin.modules.system.vo.SysTenantQueryVo;
import com.starry.common.result.R;
import java.util.List;

/**
 * 租户表Service接口
 *
 * @author admin
 */
public interface ISysTenantService extends IService<SysTenantEntity> {

    /**
     * 获得所有的租户信息
     *
     * @return 租户表
     */
    List<SysTenantEntity> listAll();

    /**
     * 根据租户权限标识查询租户
     *
     * @param tenantKey
     *            租户权限标识
     * @return 租户表
     */
    SysTenantEntity selectByTenantKey(String tenantKey);

    /**
     * 查询租户表
     *
     * @param tenantId
     *            租户表主键
     * @return 租户表
     */
    SysTenantEntity selectSysTenantByTenantId(String tenantId);

    /**
     * 查询租户表列表
     *
     * @param sysTenantEntity
     *            租户表
     * @return 租户表集合
     */
    IPage<SysTenantEntity> selectSysTenantList(SysTenantQueryVo sysTenantEntity);

    /**
     * 新增租户表
     *
     * @param sysTenantEntity
     *            租户表
     * @return 结果
     */
    boolean create(SysTenantEntity sysTenantEntity);

    /**
     * 修改租户表
     *
     * @param sysTenantEntity
     *            租户表
     * @return 结果
     */
    boolean update(SysTenantEntity sysTenantEntity);

    /**
     * 批量删除租户表
     *
     * @param tenantIds
     *            需要删除的租户表主键集合
     * @return 结果
     */
    int deleteSysTenantByTenantIds(String[] tenantIds);

    /**
     * 删除租户表信息
     *
     * @param tenantId
     *            租户表主键
     * @return 结果
     */
    int deleteSysTenantByTenantId(Long tenantId);

    /**
     * 新增租户
     *
     * @param sysTenantEntity
     *            租户表
     * @return 结果
     */
    R addTenant(SysTenantEntity sysTenantEntity);

    /**
     * 修改租户
     *
     * @param sysTenantEntity
     *            租户表
     * @return 结果
     */
    R updateTenant(SysTenantEntity sysTenantEntity);

    /**
     * 根据套餐ID查询租户
     *
     * @param packageId
     *            套餐ID
     * @return
     */
    List<SysTenantEntity> queryByPackage(String packageId);

    void initRole(SysTenantEntity sysTenantEntity);

}


--- File: play-admin/src/main/java/com/starry/admin/modules/system/service/LoginService.java ---
package com.starry.admin.modules.system.service;

import com.starry.admin.common.domain.LoginUser;
import com.starry.admin.modules.system.module.entity.SysUserEntity;
import java.util.Set;

/**
 * @author admin
 */
public interface LoginService {

    /**
     * 登录功能
     *
     * @param username
     *            用户名
     * @param password
     *            密码
     * @return 生成的JWT的token
     */
    String login(String username, String password);

    /**
     * 获取菜单数据权限
     *
     * @param user
     *            用户信息
     * @return 菜单权限信息
     */
    Set<String> getMenuPermission(SysUserEntity user);

    /**
     * 获取角色数据权限
     *
     * @param user
     *            用户信息
     * @return 角色权限信息
     */
    Set<String> getRolePermission(SysUserEntity user);

    /**
     * 租户登录功能
     *
     * @param tenantkey
     *            租户标识
     * @param username
     *            用户名
     * @param password
     *            密码
     * @return 生成的JWT的token
     */
    LoginUser tenantLogin(String tenantkey, String username, String password);

    /**
     * 登录功能
     *
     * @param username
     *            用户名
     * @param password
     *            密码
     * @param tenantKey
     * @return 生成的JWT的token
     */
    LoginUser newLogin(String username, String password, String tenantKey);

    /**
     * 新登录功能，增加租户相关
     *
     * @param userName
     *            用户名
     * @param tenantId
     * @return 获取信息
     */
    LoginUser getLoginUserInfo(String userName, String tenantId);

    LoginUser getLoginUserInfo(String userId);
}


--- File: play-admin/src/main/java/com/starry/admin/modules/system/service/SysDeptService.java ---
package com.starry.admin.modules.system.service;

import com.baomidou.mybatisplus.extension.service.IService;
import com.starry.admin.common.domain.TreeSelect;
import com.starry.admin.modules.system.module.entity.SysDeptEntity;
import java.util.List;

/**
 * <p>
 * 部门表 服务类
 * </p>
 *
 * @author admin
 * @since 2022-07-12
 */
public interface SysDeptService extends IService<SysDeptEntity> {

    /**
     * 查询部门管理数据
     *
     * @param dept
     *            部门信息
     * @return 部门信息集合
     */
    List<SysDeptEntity> selectDeptList(SysDeptEntity dept);

    /**
     * 校验部门名称是否唯一
     *
     * @param dept
     *            部门信息
     * @return 结果
     */
    String checkDeptNameUnique(SysDeptEntity dept);

    /**
     * 添加部门
     *
     * @param dept
     * @return boolean
     **/
    boolean create(SysDeptEntity dept);

    /**
     * 修改部门
     *
     * @param dept
     * @return boolean
     **/
    boolean update(SysDeptEntity dept);

    /**
     * 批量删除部门
     *
     * @param ids
     * @return boolean
     **/
    boolean delete(List<Long> ids);

    /**
     * 是否存在部门子节点
     *
     * @param deptId
     *            部门ID
     * @return 结果
     */
    boolean hasChildByDeptId(Long deptId);

    /**
     * 构建前端所需要树结构
     *
     * @param depts
     *            部门列表
     * @return 树结构列表
     */
    List<TreeSelect> buildDeptTreeSelect(List<SysDeptEntity> depts);

    /**
     * 根据角色ID查询部门树信息
     *
     * @param roleId
     *            角色ID
     * @return 选中部门列表
     */
    List<Long> selectDeptListByRoleId(Long roleId);

    /**
     * 查询部门树结构信息
     *
     * @param dept
     *            部门信息
     * @return 部门树信息集合
     */
    List<TreeSelect> selectDeptTreeList(SysDeptEntity dept);
}


--- File: play-admin/src/main/java/com/starry/admin/modules/system/service/SysMenuService.java ---
package com.starry.admin.modules.system.service;

import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.IService;
import com.starry.admin.common.domain.TreeSelect;
import com.starry.admin.modules.system.module.entity.SysMenuEntity;
import com.starry.admin.modules.system.vo.RouterVo;
import com.starry.admin.modules.system.vo.SimpleMenu;
import java.util.List;
import java.util.Set;

/**
 * <p>
 * 菜单表 服务类
 * </p>
 *
 * @author admin
 * @since 2022-07-03
 */
public interface SysMenuService extends IService<SysMenuEntity> {

    /**
     * 添加菜单
     *
     * @param menu
     * @return boolean
     **/
    boolean create(SysMenuEntity menu);

    /**
     * 根据用户查询系统菜单列表
     *
     * @param menu
     *            菜单
     * @param userId
     *            用户ID
     * @param pageSize
     *            页大小
     * @param pageNum
     *            页数
     * @return 分页菜单列表
     */
    Page<SysMenuEntity> listPage(SysMenuEntity menu, String userId, Integer pageSize, Integer pageNum);

    /**
     * 根据用户查询系统菜单列表
     *
     * @param menu
     *            菜单
     * @param userId
     *            用户ID
     * @return 菜单列表
     */
    List<SysMenuEntity> selectMenuList(SysMenuEntity menu, String userId);

    /**
     * 根据用户查询系统菜单列表
     *
     * @param userId
     *            用户ID
     * @return 菜单列表
     */
    List<SysMenuEntity> selectMenuList(String userId);

    /**
     * 校验菜单名称是否唯一
     *
     * @param menu
     *            菜单信息
     * @return 结果
     */
    String checkMenuNameUnique(SysMenuEntity menu);

    /**
     * 是否存在菜单子节点
     *
     * @param menuId
     *            菜单ID
     * @return 结果 true 存在 false 不存在
     */
    boolean hasChildByMenuId(Long menuId);

    /**
     * 构建前端所需要下拉树结构
     *
     * @param menus
     *            菜单列表
     * @return 下拉树结构列表
     */
    List<TreeSelect> buildMenuTreeSelect(List<SysMenuEntity> menus);

    /**
     * 根据菜单ID查询信息
     *
     * @param menuId
     *            菜单ID
     * @return 菜单信息
     */
    SysMenuEntity selectMenuById(Long menuId);

    /**
     * 根据角色ID查询菜单树信息
     *
     * @param roleId
     *            角色ID
     * @return 选中菜单列表
     */
    List<Long> selectMenuListByRoleId(Long roleId);

    /**
     * 根据用户ID查询权限
     *
     * @param userId
     *            用户ID
     * @return 权限列表
     */
    Set<String> selectMenuPermsByUserId(String userId);

    /**
     * 根据用户ID查询菜单树信息
     *
     * @param userId
     *            用户ID
     * @return 菜单列表
     */
    List<SysMenuEntity> selectMenuTreeByUserId(String userId);

    /**
     * 构建前端路由所需要的菜单
     *
     * @param menus
     *            菜单列表
     * @return 路由列表
     */
    List<RouterVo> buildMenus(List<SysMenuEntity> menus);

    /**
     * 查询所有开启状态菜单精简信息
     *
     * @return 菜单列表
     */
    List<SimpleMenu> selectSimpleMenuList();

}


--- File: play-admin/src/main/java/com/starry/admin/modules/system/service/SysRoleMenuService.java ---
package com.starry.admin.modules.system.service;

import com.baomidou.mybatisplus.extension.service.IService;
import com.starry.admin.modules.system.module.entity.SysRoleMenuEntity;

/**
 * <p>
 * 角色和菜单关联表 服务类
 * </p>
 *
 * @author admin
 * @since 2022-07-06
 */
public interface SysRoleMenuService extends IService<SysRoleMenuEntity> {

}


--- File: play-admin/src/main/java/com/starry/admin/modules/system/service/SysRoleService.java ---
package com.starry.admin.modules.system.service;

import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.IService;
import com.starry.admin.modules.system.module.entity.SysRoleEntity;
import com.starry.admin.modules.system.module.entity.SysUserRoleEntity;
import java.util.List;
import java.util.Set;

/**
 * <p>
 * 角色表 服务类
 * </p>
 *
 * @author admin
 * @since 2022-07-01
 */
public interface SysRoleService extends IService<SysRoleEntity> {

    /**
     * 根据roleId 查询角色
     *
     * @param roleId
     *            角色ID
     * @return 角色
     */
    SysRoleEntity selectByRoleId(String roleId);

    /**
     * 根据roleKey 查询角色
     *
     * @param roleKey
     *            角色标识
     * @return 角色
     */
    SysRoleEntity selectByRoleKey(String roleKey);
    /**
     * 校验角色名称是否唯一
     *
     * @param role
     *            角色信息
     * @return 结果
     */
    String checkRoleNameUnique(SysRoleEntity role);

    /**
     * 校验角色权限是否唯一
     *
     * @param role
     *            角色信息
     * @return 结果
     */
    String checkRoleKeyUnique(SysRoleEntity role);

    /**
     * 添加角色
     *
     * @param role
     * @return boolean
     **/
    boolean create(SysRoleEntity role);

    /**
     * 修改角色
     *
     * @param role
     * @return boolean
     **/
    boolean updateRole(SysRoleEntity role);

    /**
     * 通过角色ID查询角色使用数量
     *
     * @param roleId
     *            角色ID
     * @return 结果
     */
    int countUserRoleByRoleId(Long roleId);

    /**
     * 批量删除角色
     *
     * @param roleIds
     * @return boolean
     **/
    boolean delete(Long[] roleIds);

    /**
     * 分页获取角色列表
     *
     * @param keyword
     * @param pageSize
     * @param pageNum
     * @return Page
     **/
    Page<SysRoleEntity> list(String keyword, Integer pageSize, Integer pageNum);

    /**
     * 分配菜单权限信息
     *
     * @param role
     *            角色信息
     * @return 结果
     */
    boolean authRoleMenu(SysRoleEntity role);

    /**
     * 根据用户ID查询角色权限
     *
     * @param userId
     *            用户ID
     * @return 权限列表
     */
    Set<String> selectRolePermissionByUserId(String userId);

    /**
     * 根据条件分页查询角色数据
     *
     * @param role
     *            角色信息
     * @return 角色数据集合信息
     */
    List<SysRoleEntity> selectRoleList(SysRoleEntity role);

    /**
     * 校验角色是否允许操作
     *
     * @param role
     *            角色信息
     */
    void checkRoleAllowed(SysRoleEntity role);

    /**
     * 校验角色是否有数据权限
     *
     * @param roleId
     *            角色id
     */
    void checkRoleDataScope(Long roleId);

    /**
     * 修改数据权限信息
     *
     * @param role
     *            角色信息
     * @return 结果
     */
    int authDataScope(SysRoleEntity role);

    /**
     * 取消授权用户角色
     *
     * @param userRole
     *            用户和角色关联信息
     * @return 结果
     */
    int deleteAuthUser(SysUserRoleEntity userRole);

    /**
     * 批量取消授权用户角色
     *
     * @param roleId
     *            角色ID
     * @param userIds
     *            需要取消授权的用户数据ID
     * @return 结果
     */
    int deleteAuthUsers(Long roleId, String[] userIds);

    /**
     * 批量选择授权用户角色
     *
     * @param roleId
     *            角色ID
     * @param userIds
     *            需要删除的用户数据ID
     * @return 结果
     */
    int insertAuthUsers(Long roleId, String[] userIds);
}


--- File: play-admin/src/main/java/com/starry/admin/modules/system/service/SysUserRoleService.java ---
package com.starry.admin.modules.system.service;

import com.baomidou.mybatisplus.extension.service.IService;
import com.starry.admin.modules.system.module.entity.SysUserRoleEntity;

/**
 * <p>
 * 用户角色关联表 服务类
 * </p>
 *
 * @author admin
 * @since 2022-07-08
 */
public interface SysUserRoleService extends IService<SysUserRoleEntity> {

    /**
     * 删除用户角色信息
     *
     * @param userId
     *            用户ID
     * @param roleId
     *            角色ID
     */
    void delUserRole(String userId, Long roleId);

    /**
     * 新增用户角色信息
     *
     * @param userId
     *            用户ID
     * @param roleId
     *            角色ID
     */
    void addUserRole(String userId, Long roleId);

}


--- File: play-admin/src/main/java/com/starry/admin/modules/system/service/SysUserService.java ---
package com.starry.admin.modules.system.service;

import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.service.IService;
import com.starry.admin.modules.personnel.module.vo.PlayPersonnelUserInfoQueryVo;
import com.starry.admin.modules.personnel.module.vo.PlayPersonnelUserInfoReturnVo;
import com.starry.admin.modules.system.module.entity.SysUserEntity;
import com.starry.admin.modules.system.vo.*;
import java.util.List;

/**
 * <p>
 * 后台用户表 服务类
 * </p>
 *
 * @author admin
 * @since 2021-09-03
 */
public interface SysUserService extends IService<SysUserEntity> {

    /**
     * 注册功能
     *
     * @param user
     * @return SysUserEntity
     */
    SysUserEntity register(SysUserEntity user);

    /**
     * 查询用户
     *
     * @param userCode
     * @return SysUserEntity
     */
    SysUserEntity getUserByCode(String userCode);

    /**
     * 通过用户名查询用户
     *
     * @param userName
     *            用户名
     * @return 用户对象信息
     */
    SysUserEntity selectUserByUserName(String userName);

    SysUserEntity selectUserByUserNameAndTenantId(String userName, String tenantId);

    /**
     * 查询所有账户
     *
     * @return 查询所有账户
     */
    List<SysUserEntity> selectAll();

    /**
     * 分页查询用户
     *
     * @param vo
     *            查询参数
     * @return page
     */
    IPage<PlayPersonnelUserInfoReturnVo> selectByPage(PlayPersonnelUserInfoQueryVo vo);

    /**
     * 获取列表。分页
     *
     * @param queryVo
     *            查询参数
     * @return page
     */
    IPage<SysUserEntity> listMemberPage(UserQueryVo queryVo);

    /**
     * 校验用户名称是否唯一
     *
     * @param userName
     *            用户名称
     * @return 结果
     */
    String checkUserNameUnique(String userName);

    /**
     * 添加用户
     *
     * @param user
     * @return boolean
     **/
    boolean create(SysUserEntity user);

    /**
     * 修改用户
     *
     * @param user
     * @return boolean
     **/
    boolean update(SysUserEntity user);

    /**
     * 批量删除用户
     *
     * @param ids
     * @return boolean
     **/
    boolean delete(List<String> ids);

    /**
     * 通过用户ID查询用户
     *
     * @param userId
     *            用户ID
     * @return 用户对象信息
     */
    SysUserEntity selectUserById(String userId);

    /**
     * 根据用户ID查询用户所属角色组
     *
     * @param userId
     *            用户id
     * @return 结果
     */
    String selectUserRoleGroup(String userId);

    /**
     * 修改用户头像
     *
     * @param userId
     *            用户id
     * @param avatar
     *            头像地址
     * @return 结果
     */
    boolean updateUserAvatar(String userId, String avatar);

    /**
     * 根据条件分页查询已配用户角色列表
     *
     * @param user
     *            用户信息
     * @return 用户信息集合信息
     */
    IPage<RoleUserResultVo> selectAllocatedList(SysUserQueryVo user);

    /**
     * 根据条件分页查询已配用户角色列表
     *
     * @param user
     *            用户信息
     * @return 用户信息集合信息
     */
    IPage<RoleUserResultVo> selectUnallocatedList(SysUserQueryVo user);

    boolean addV1(UserAddParam param);

    List<SysUserEntityListVo> generatePageData(List<SysUserEntity> records);

    void updatePassword(UpdateUserPasswordParam param);

    void updateMobile(UpdateUserMobileParam param);

    void logoutUser(String uid);

    void updatePasswordV2(UpdateUserPasswordParamV2 param);
}


--- File: play-admin/src/main/java/com/starry/admin/modules/system/service/UserOnlineService.java ---
package com.starry.admin.modules.system.service;

import com.starry.admin.common.domain.LoginUser;
import com.starry.admin.modules.system.module.entity.UserOnline;

/**
 * @author admin
 * @since 2022/10/18
 */
public interface UserOnlineService {

    /**
     * 通过登录地址/用户名称查询信息
     *
     * @param ipaddr
     *            登录地址
     * @param userName
     *            用户名称
     * @param user
     *            用户信息
     * @return 在线用户信息
     */
    UserOnline selectOnlineByInfo(String ipaddr, String userName, LoginUser user);

    /**
     * 通过登录地址查询信息
     *
     * @param ipaddr
     *            登录地址
     * @param user
     *            用户信息
     * @return 在线用户信息
     */
    UserOnline selectOnlineByIpaddr(String ipaddr, LoginUser user);

    /**
     * 通过用户名称查询信息
     *
     * @param userName
     *            用户名称
     * @param user
     *            用户信息
     * @return 在线用户信息
     */
    UserOnline selectOnlineByUserName(String userName, LoginUser user);

    /**
     * 设置在线用户信息
     *
     * @param user
     *            用户信息
     * @return 在线用户
     */
    UserOnline setUserOnline(LoginUser user);
}


--- File: play-admin/src/main/java/com/starry/admin/modules/system/service/impl/LoginServiceImpl.java ---
package com.starry.admin.modules.system.service.impl;

import cn.hutool.core.convert.Convert;
import com.starry.admin.common.component.JwtToken;
import com.starry.admin.common.domain.LoginUser;
import com.starry.admin.common.exception.ServiceException;
import com.starry.admin.common.security.entity.JwtUser;
import com.starry.admin.manager.AsyncManager;
import com.starry.admin.manager.factory.AsyncFactory;
import com.starry.admin.modules.system.module.entity.SysTenantEntity;
import com.starry.admin.modules.system.module.entity.SysUserEntity;
import com.starry.admin.modules.system.service.*;
import com.starry.admin.utils.SecurityUtils;
import com.starry.common.constant.Constants;
import com.starry.common.constant.UserConstants;
import com.starry.common.utils.StringUtils;
import java.util.Date;
import java.util.HashSet;
import java.util.Set;
import javax.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;

/**
 * @author admin
 */
@Slf4j
@Service
public class LoginServiceImpl implements LoginService {

    @Resource
    private PasswordEncoder passwordEncoder;
    @Resource
    private UserDetailsService userDetailsService;
    @Resource
    private JwtToken jwtTokenUtil;
    @Resource
    private SysMenuService menuService;
    @Resource
    private SysRoleService roleService;
    @Resource
    private SysUserService sysUserService;
    @Resource
    private ISysTenantService SysTenantService;

    @Override
    public String login(String username, String password) {
        String token = null;
        try {
            UserDetails userDetails = userDetailsService.loadUserByUsername(username);
            if (!passwordEncoder.matches(password, userDetails.getPassword())) {
                throw new BadCredentialsException("密码不正确");
            }
            UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(userDetails,
                    null, userDetails.getAuthorities());
            SecurityContextHolder.getContext().setAuthentication(authentication);
            // 登录成功记录日志
            AsyncManager.me().execute(AsyncFactory.recordLoginLog(username, Constants.LOGIN_SUCCESS, "登录成功"));
            JwtUser jwtUser = (JwtUser) authentication.getPrincipal();
            token = jwtTokenUtil.createToken(jwtUser);
        } catch (AuthenticationException e) {
            log.warn("登录异常:{}", e.getMessage());
            // 登录失败记录日志
            AsyncManager.me().execute(AsyncFactory.recordLoginLog(username, Constants.LOGIN_FAIL, e.getMessage()));
        }
        return token;
    }

    @Override
    public Set<String> getMenuPermission(SysUserEntity user) {
        Set<String> perms = new HashSet<>();
        // 超级管理员拥有所有权限
        if (SecurityUtils.isAdmin(user)) {
            perms.add("*:*:*");
        } else {
            perms = menuService.selectMenuPermsByUserId(user.getUserId());
        }
        return perms;
    }

    @Override
    public Set<String> getRolePermission(SysUserEntity user) {
        Set<String> roles = new HashSet<>();
        // 超级管理员拥有所有权限
        if (SecurityUtils.isAdmin(user)) {
            roles.add("admin");
        } else {
            roles = roleService.selectRolePermissionByUserId(user.getUserId());
        }
        return roles;
    }

    @Override
    public LoginUser tenantLogin(String tenantId, String username, String password) {
        // LoginUser userInfo = this.getLoginUserInfo(username);
        return null;
    }

    @Override
    public LoginUser newLogin(String username, String password, String tenantKey) {
        // 用户名或密码为空 错误
        if (StringUtils.isAnyBlank(username, password, tenantKey)) {
            // 登录记录日志
            AsyncManager.me().execute(AsyncFactory.recordLoginLog(username, Constants.LOGIN_FAIL, "用户名/密码必须填写"));
            throw new ServiceException("用户/密码必须填写");
        }
        // 密码如果不在指定范围内 错误
        if (password.length() < UserConstants.PASSWORD_MIN_LENGTH
                || password.length() > UserConstants.PASSWORD_MAX_LENGTH) {
            // 登录记录日志
            AsyncManager.me().execute(AsyncFactory.recordLoginLog(username, Constants.LOGIN_FAIL, "用户密码不在指定范围"));
            throw new ServiceException("用户密码不在指定范围");
        }
        // 用户名不在指定范围内 错误
        if (username.length() < UserConstants.USERNAME_MIN_LENGTH
                || username.length() > UserConstants.USERNAME_MAX_LENGTH) {
            // 登录记录日志
            AsyncManager.me().execute(AsyncFactory.recordLoginLog(username, Constants.LOGIN_FAIL, "用户名不在指定范围"));
            throw new ServiceException("用户名不在指定范围");
        }
        SysTenantEntity tenant = SysTenantService.selectByTenantKey(tenantKey);
        LoginUser userInfo = this.getLoginUserInfo(username, tenant.getTenantId());
        if (userInfo == null) {
            // 登录记录日志
            AsyncManager.me().execute(AsyncFactory.recordLoginLog(username, Constants.LOGIN_FAIL, "用户名不存在"));
            throw new ServiceException("用户名不存在");
        }
        SysUserEntity user = userInfo.getUser();
        if (!SecurityUtils.matchesPassword(password, user.getPassWord())) {
            // 登录记录日志
            AsyncManager.me().execute(AsyncFactory.recordLoginLog(username, Constants.LOGIN_FAIL, "密码错误"));
            throw new ServiceException("密码错误");
        }
        // 线程塞入租户ID
        SecurityUtils.setTenantId(Convert.toStr(user.getTenantId()));
        if (user.getStatus() == 1) {
            // 登录记录日志
            AsyncManager.me().execute(AsyncFactory.recordLoginLog(username, Constants.LOGIN_FAIL, "用户已停用，请联系管理员"));
            throw new ServiceException("对不起，您的账号：" + username + " 已停用，请联系管理员");
        }
        // 先查询是否被停用了租户
        if (userInfo.getTenantStatus() != null && userInfo.getTenantStatus() == 1) {
            // 登录记录日志
            AsyncManager.me().execute(AsyncFactory.recordLoginLog(username, Constants.LOGIN_FAIL, "当前租户已经被停用，请联系管理员"));
            throw new ServiceException("当前租户已经被停用，请联系管理员");
        }
        if (userInfo.getTenantEndDate() != null && userInfo.getTenantEndDate().compareTo(new Date()) < 0) {
            // 登录记录日志
            AsyncManager.me()
                    .execute(AsyncFactory.recordLoginLog(username, Constants.LOGIN_FAIL, "当前租户已超过租赁日期，请联系管理员"));
            throw new ServiceException("当前租户已超过租赁日期，请联系管理员");
        }
        AsyncManager.me().execute(AsyncFactory.recordLoginLog(username, Constants.LOGIN_SUCCESS, "登录成功"));
        return userInfo;
    }

    @Override
    public LoginUser getLoginUserInfo(String userName, String tenantId) {
        SysUserEntity sysUser;
        if (userName.equals("sysadmin")) {
            sysUser = sysUserService.selectUserByUserNameAndTenantId(userName, tenantId);
        } else {
            sysUser = sysUserService.selectUserByUserNameAndTenantId(userName, tenantId);
        }

        if (StringUtils.isNotNull(sysUser)) {
            // SecurityUtils.setTenantId(sysUser.getTenantId());
            // 角色集合
            Set<String> roles = this.getRolePermission(sysUser);
            // 权限集合
            Set<String> permissions = this.getMenuPermission(sysUser);
            // 查询租户信息
            SysTenantEntity tenant = SysTenantService.selectSysTenantByTenantId(sysUser.getTenantId());
            LoginUser sysUserVo = new LoginUser();
            sysUserVo.setUser(sysUser);
            sysUserVo.setRoles(roles);
            sysUserVo.setPermissions(permissions);
            if (tenant != null) {
                sysUserVo.setTenantEndDate(tenant.getTenantTime());
                sysUserVo.setTenantStatus(Integer.valueOf(tenant.getTenantStatus()));
            }
            return sysUserVo;
        }
        return null;
    }

    @Override
    public LoginUser getLoginUserInfo(String userId) {
        SysUserEntity sysUser = sysUserService.selectUserById(userId);
        Set<String> roles = this.getRolePermission(sysUser);
        // 权限集合
        Set<String> permissions = this.getMenuPermission(sysUser);
        // 查询租户信息
        SysTenantEntity tenant = SysTenantService.selectSysTenantByTenantId(sysUser.getTenantId());
        LoginUser sysUserVo = new LoginUser();
        sysUserVo.setUser(sysUser);
        sysUserVo.setRoles(roles);
        sysUserVo.setPermissions(permissions);
        if (tenant != null) {
            sysUserVo.setTenantEndDate(tenant.getTenantTime());
            sysUserVo.setTenantStatus(Integer.valueOf(tenant.getTenantStatus()));
        }
        return sysUserVo;
    }
}


--- File: play-admin/src/main/java/com/starry/admin/modules/system/service/impl/SysAdministrativeAreaDictInfoServiceImpl.java ---
package com.starry.admin.modules.system.service.impl;

import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.starry.admin.modules.system.mapper.SysAdministrativeAreaDictInfoMapper;
import com.starry.admin.modules.system.module.entity.SysAdministrativeAreaDictInfoEntity;
import com.starry.admin.modules.system.service.ISysAdministrativeAreaDictInfoService;
import com.starry.admin.modules.system.vo.AdministrativeAreaQueryReturnVo;
import com.starry.common.utils.ConvertUtil;
import com.starry.common.utils.IdUtils;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import javax.annotation.Resource;
import org.springframework.stereotype.Service;

/**
 * 行政区域字典信息Service业务层处理
 *
 * @author admin
 * @since 2024-04-03
 */
@Service
public class SysAdministrativeAreaDictInfoServiceImpl
        extends
            ServiceImpl<SysAdministrativeAreaDictInfoMapper, SysAdministrativeAreaDictInfoEntity>
        implements
            ISysAdministrativeAreaDictInfoService {
    @Resource
    private SysAdministrativeAreaDictInfoMapper sysAdministrativeAreaDictInfoMapper;

    /**
     * 查询行政区域字典信息
     *
     * @param id
     *            行政区域字典信息主键
     * @return 行政区域字典信息
     */
    @Override
    public SysAdministrativeAreaDictInfoEntity selectPlayAdministrativeAreaDictInfoById(String id) {
        return this.baseMapper.selectById(id);
    }

    @Override
    public List<AdministrativeAreaQueryReturnVo> selectTree(String level) {
        LambdaQueryWrapper<SysAdministrativeAreaDictInfoEntity> lambdaQueryWrapper = new LambdaQueryWrapper<>();
        lambdaQueryWrapper.le(SysAdministrativeAreaDictInfoEntity::getLevel, level);
        List<SysAdministrativeAreaDictInfoEntity> list = this.baseMapper.selectList(lambdaQueryWrapper);
        Map<String, List<SysAdministrativeAreaDictInfoEntity>> collect = list.stream()
                .filter(a -> a != null && a.getPCode() != null)
                .collect(Collectors.groupingBy(SysAdministrativeAreaDictInfoEntity::getPCode));
        return this.assembleTree(collect, collect.get("00"));
    }

    /**
     * 组装数据
     *
     * @param data
     *            数据，key=区域编码,value=区域列表
     * @param list
     *            区域略表
     * @return List<com.starry.admin.modules.system.entity.SysAdministrativeAreaDictInfoEntity>
     * @author admin
     * @since 2024/4/10 15:14
     **/
    public List<AdministrativeAreaQueryReturnVo> assembleTree(
            Map<String, List<SysAdministrativeAreaDictInfoEntity>> data,
            List<SysAdministrativeAreaDictInfoEntity> list) {
        if (list == null) {
            return new ArrayList<>();
        }
        List<AdministrativeAreaQueryReturnVo> result = ConvertUtil.entityToVoList(list,
                AdministrativeAreaQueryReturnVo.class);
        for (AdministrativeAreaQueryReturnVo entity : result) {
            entity.setChild(assembleTree(data, data.get(entity.getCode())));
        }
        return result;
    }

    @Override
    public List<SysAdministrativeAreaDictInfoEntity> selectAll() {
        return this.baseMapper.selectList(new LambdaQueryWrapper<>());
    }

    /**
     * 查询行政区域字典信息列表
     *
     * @param playAdministrativeAreaDictInfo
     *            行政区域字典信息
     * @return 行政区域字典信息
     */
    @Override
    public IPage<SysAdministrativeAreaDictInfoEntity> selectPlayAdministrativeAreaDictInfoByPage(
            SysAdministrativeAreaDictInfoEntity playAdministrativeAreaDictInfo) {
        Page<SysAdministrativeAreaDictInfoEntity> page = new Page<>(1, 10);
        return this.baseMapper.selectPage(page, new LambdaQueryWrapper<>());
    }

    /**
     * 新增行政区域字典信息
     *
     * @param playAdministrativeAreaDictInfo
     *            行政区域字典信息
     * @return 结果
     */
    @Override
    public boolean create(SysAdministrativeAreaDictInfoEntity playAdministrativeAreaDictInfo) {
        if (StrUtil.isBlankIfStr(playAdministrativeAreaDictInfo.getId())) {
            playAdministrativeAreaDictInfo.setId(IdUtils.getUuid());
        }
        return save(playAdministrativeAreaDictInfo);
    }

    @Override
    public boolean create(List<SysAdministrativeAreaDictInfoEntity> list) {
        return this.saveBatch(list);
    }

    /**
     * 修改行政区域字典信息
     *
     * @param playAdministrativeAreaDictInfo
     *            行政区域字典信息
     * @return 结果
     */
    @Override
    public boolean update(SysAdministrativeAreaDictInfoEntity playAdministrativeAreaDictInfo) {
        return updateById(playAdministrativeAreaDictInfo);
    }

    /**
     * 批量删除行政区域字典信息
     *
     * @param ids
     *            需要删除的行政区域字典信息主键
     * @return 结果
     */
    @Override
    public int deletePlayAdministrativeAreaDictInfoByIds(String[] ids) {
        return sysAdministrativeAreaDictInfoMapper.deleteBatchIds(Arrays.asList(ids));
    }

    /**
     * 删除行政区域字典信息信息
     *
     * @param id
     *            行政区域字典信息主键
     * @return 结果
     */
    @Override
    public int deletePlayAdministrativeAreaDictInfoById(String id) {
        return sysAdministrativeAreaDictInfoMapper.deleteById(id);
    }
}


--- File: play-admin/src/main/java/com/starry/admin/modules/system/service/impl/SysDeptServiceImpl.java ---
package com.starry.admin.modules.system.service.impl;

import cn.hutool.core.collection.CollectionUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.starry.admin.common.domain.TreeSelect;
import com.starry.admin.modules.system.mapper.SysDeptMapper;
import com.starry.admin.modules.system.mapper.SysRoleMapper;
import com.starry.admin.modules.system.module.entity.SysDeptEntity;
import com.starry.admin.modules.system.module.entity.SysRoleEntity;
import com.starry.admin.modules.system.service.SysDeptService;
import com.starry.common.annotation.DataScope;
import com.starry.common.constant.UserConstants;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
import javax.annotation.Resource;
import org.springframework.stereotype.Service;

/**
 * <p>
 * 部门表 服务实现类
 * </p>
 *
 * @author admin
 * @since 2022-07-12
 */
@Service
public class SysDeptServiceImpl extends ServiceImpl<SysDeptMapper, SysDeptEntity> implements SysDeptService {

    @Resource
    private SysRoleMapper roleMapper;

    @Override
    @DataScope(deptAlias = "d")
    public List<SysDeptEntity> selectDeptList(SysDeptEntity dept) {
        return baseMapper.selectDeptList(dept);
    }

    @Override
    public String checkDeptNameUnique(SysDeptEntity dept) {
        Long deptId = dept.getDeptId() == null ? -1L : dept.getDeptId();
        List<SysDeptEntity> infos = list(new LambdaQueryWrapper<SysDeptEntity>()
                .eq(SysDeptEntity::getDeptName, dept.getDeptName()).eq(SysDeptEntity::getParentId, dept.getParentId()));
        // 查出有数据 并且不是自己，则不唯一
        if (CollectionUtil.isNotEmpty(infos) && infos.get(0).getDeptId().longValue() != deptId.longValue()) {
            return UserConstants.NOT_UNIQUE;
        }
        return UserConstants.UNIQUE;
    }

    @Override
    public boolean create(SysDeptEntity dept) {
        SysDeptEntity info = getById(dept.getParentId());
        // 若父节点不为正常状态,则不允许新增子节点
        if (!UserConstants.DEPT_NORMAL.equals(String.valueOf(info.getStatus()))) {
            throw new RuntimeException("部门停用，不允许新增");
        }
        dept.setAncestors(info.getAncestors() + "," + dept.getParentId());
        dept.setDeptLevel(info.getDeptLevel() == null ? 0 : info.getDeptLevel() + 1);
        return save(dept);
    }

    @Override
    public boolean update(SysDeptEntity dept) {
        // 新的上级部门
        SysDeptEntity newParentDept = getById(dept.getParentId());
        // 当前部门
        SysDeptEntity oldDept = getById(dept.getDeptId());
        if (newParentDept != null && oldDept != null) {
            String newAncestors = newParentDept.getAncestors() + "," + newParentDept.getDeptId();
            String oldAncestors = oldDept.getAncestors();
            dept.setAncestors(newAncestors);
            updateDeptChildren(dept.getDeptId(), newAncestors, oldAncestors);
        }
        return updateById(dept);
    }

    @Override
    public boolean delete(List<Long> ids) {
        return removeBatchByIds(ids);
    }

    @Override
    public boolean hasChildByDeptId(Long deptId) {
        List<SysDeptEntity> list = baseMapper
                .selectList(new LambdaQueryWrapper<SysDeptEntity>().eq(SysDeptEntity::getParentId, deptId));
        if (CollectionUtil.isNotEmpty(list)) {
            return true;
        }
        return false;
    }

    @Override
    public List<TreeSelect> buildDeptTreeSelect(List<SysDeptEntity> depts) {
        List<SysDeptEntity> deptList = buildDeptTree(depts);
        // 转换成树结构的list
        return deptList.stream().map(TreeSelect::new).collect(Collectors.toList());
    }

    public List<SysDeptEntity> buildDeptTree(List<SysDeptEntity> depts) {
        List<SysDeptEntity> returnList = new ArrayList<>();
        // 所有部门id
        List<Long> tempList = new ArrayList<>();
        for (SysDeptEntity dept : depts) {
            tempList.add(dept.getDeptId());
        }
        for (SysDeptEntity dept : depts) {
            // 如若是顶级节点，遍历该父节点下的所有子节点
            if (!tempList.contains(dept.getParentId())) {
                recursionFn(depts, dept);
                returnList.add(dept);
            }
        }
        if (CollectionUtil.isEmpty(returnList)) {
            returnList = depts;
        }
        return returnList;
    }

    /**
     * 修改子元素关系
     *
     * @param deptId
     *            被修改的部门ID
     * @param newAncestors
     *            新的父ID集合
     * @param oldAncestors
     *            旧的父ID集合
     */
    public void updateDeptChildren(Long deptId, String newAncestors, String oldAncestors) {
        List<SysDeptEntity> childrens = baseMapper.selectChildrenDeptById(deptId);
        if (CollectionUtil.isNotEmpty(childrens)) {
            for (SysDeptEntity child : childrens) {
                child.setAncestors(child.getAncestors().replaceFirst(oldAncestors, newAncestors));
                updateById(child);
            }
        }
    }

    /**
     * 递归列表
     */
    private void recursionFn(List<SysDeptEntity> list, SysDeptEntity t) {
        List<SysDeptEntity> childList = getChildList(list, t);
        t.setChildren(childList);
        for (SysDeptEntity tChild : childList) {
            if (hasChild(list, tChild)) {
                recursionFn(list, tChild);
            }
        }
    }

    /**
     * 得到子节点列表
     */
    private List<SysDeptEntity> getChildList(List<SysDeptEntity> list, SysDeptEntity t) {
        List<SysDeptEntity> tList = new ArrayList<>();
        for (SysDeptEntity n : list) {
            if (n.getParentId() != null && n.getParentId().longValue() == t.getDeptId().longValue()) {
                tList.add(n);
            }
        }
        return tList;
    }

    /**
     * 判断是否还有子节点
     */
    private boolean hasChild(List<SysDeptEntity> list, SysDeptEntity t) {
        return !getChildList(list, t).isEmpty();
    }

    /**
     * 根据角色ID查询部门树信息
     *
     * @param roleId
     *            角色ID
     * @return 选中部门列表
     */
    @Override
    public List<Long> selectDeptListByRoleId(Long roleId) {
        SysRoleEntity role = roleMapper.selectRoleById(roleId);
        return baseMapper.selectDeptListByRoleId(roleId, role.isDeptCheckStrictly());
    }

    /**
     * 查询部门树结构信息
     *
     * @param dept
     *            部门信息
     * @return 部门树信息集合
     */
    @Override
    public List<TreeSelect> selectDeptTreeList(SysDeptEntity dept) {
        List<SysDeptEntity> depts = this.selectDeptList(dept);
        return buildDeptTreeSelect(depts);
    }
}


--- File: play-admin/src/main/java/com/starry/admin/modules/system/service/impl/SysDictDataServiceImpl.java ---
package com.starry.admin.modules.system.service.impl;

import cn.hutool.core.collection.CollectionUtil;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.starry.admin.modules.system.mapper.SysDictDataMapper;
import com.starry.admin.modules.system.module.entity.SysDictDataEntity;
import com.starry.admin.modules.system.service.ISysDictDataService;
import com.starry.admin.modules.system.vo.SysDictDataVo;
import com.starry.admin.utils.DictUtils;
import java.util.Arrays;
import java.util.List;
import javax.annotation.Resource;
import org.springframework.stereotype.Service;

/**
 * 字典数据表Service业务层处理
 *
 * @author admin
 */
@Service
public class SysDictDataServiceImpl extends ServiceImpl<SysDictDataMapper, SysDictDataEntity>
        implements
            ISysDictDataService {
    @Resource
    private SysDictDataMapper sysDictDataMapper;

    /**
     * 查询字典数据表
     *
     * @param dictDataId
     *            字典数据表主键
     * @return 字典数据表
     */
    @Override
    public SysDictDataEntity selectXlDictDataByDictDataId(Long dictDataId) {
        return sysDictDataMapper.selectXlDictDataByDictDataId(dictDataId);
    }

    /**
     * 查询字典数据表列表
     *
     * @param sysDictDataEntity
     *            字典数据表
     * @return 字典数据表
     */
    @Override
    public IPage<SysDictDataEntity> selectXlDictDataList(SysDictDataVo sysDictDataEntity) {
        return sysDictDataMapper.selectXlDictDataList(
                new Page<>(sysDictDataEntity.getPageNum(), sysDictDataEntity.getPageSize()), sysDictDataEntity);
    }

    /**
     * 新增字典数据表
     *
     * @param sysDictDataEntity
     *            字典数据表
     * @return 结果
     */
    @Override
    public boolean create(SysDictDataEntity sysDictDataEntity) {
        return save(sysDictDataEntity);
    }

    /**
     * 修改字典数据表
     *
     * @param sysDictDataEntity
     *            字典数据表
     * @return 结果
     */
    @Override
    public boolean update(SysDictDataEntity sysDictDataEntity) {
        return updateById(sysDictDataEntity);
    }

    /**
     * 批量删除字典数据表
     *
     * @param dictDataIds
     *            需要删除的字典数据表主键
     * @return 结果
     */
    @Override
    public int deleteXlDictDataByDictDataIds(Long[] dictDataIds) {
        return sysDictDataMapper.deleteBatchIds(Arrays.asList(dictDataIds));
    }

    /**
     * 删除字典数据表信息
     *
     * @param dictDataId
     *            字典数据表主键
     * @return 结果
     */
    @Override
    public int deleteXlDictDataByDictDataId(Long dictDataId) {
        return sysDictDataMapper.deleteById(dictDataId);
    }

    /**
     * 根据字典类型查询字典数据
     *
     * @param dictType
     *            字典类型
     * @return 字典数据集合信息
     */
    @Override
    public List<SysDictDataEntity> selectDictDataByType(String dictType) {
        List<SysDictDataEntity> dictDataList = DictUtils.getDictCache(dictType);
        if (CollectionUtil.isNotEmpty(dictDataList)) {
            return dictDataList;
        }
        dictDataList = sysDictDataMapper.selectDictDataByType(dictType);
        if (CollectionUtil.isNotEmpty(dictDataList)) {
            DictUtils.setDictCache(dictType, dictDataList);
            return dictDataList;
        }
        return null;
    }
}


--- File: play-admin/src/main/java/com/starry/admin/modules/system/service/impl/SysDictServiceImpl.java ---
package com.starry.admin.modules.system.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.starry.admin.modules.system.mapper.SysDictDataMapper;
import com.starry.admin.modules.system.mapper.SysDictMapper;
import com.starry.admin.modules.system.module.entity.SysDictDataEntity;
import com.starry.admin.modules.system.module.entity.SysDictEntity;
import com.starry.admin.modules.system.service.ISysDictService;
import com.starry.admin.modules.system.vo.SysDictDataVo;
import com.starry.admin.modules.system.vo.SysDictVo;
import com.starry.admin.utils.DictUtils;
import java.util.Arrays;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import javax.annotation.Resource;
import org.springframework.stereotype.Service;

/**
 * 字典表Service业务层处理
 *
 * @author admin
 */
@Service
public class SysDictServiceImpl extends ServiceImpl<SysDictMapper, SysDictEntity> implements ISysDictService {
    @Resource
    private SysDictMapper sysDictMapper;
    @Resource
    private SysDictDataMapper sysDictDataMapper;

    /**
     * 查询字典表
     *
     * @param dictId
     *            字典表主键
     * @return 字典表
     */
    @Override
    public SysDictEntity selectXlDictByDictId(Long dictId) {
        return sysDictMapper.selectXlDictByDictId(dictId);
    }

    /**
     * 查询字典表列表
     *
     * @param sysDictVo
     *            字典表
     * @return 字典表
     */
    @Override
    public Page<SysDictEntity> selectXlDictList(SysDictVo sysDictVo) {
        return sysDictMapper.selectXlDictList(new Page<>(sysDictVo.getPageNum(), sysDictVo.getPageSize()), sysDictVo);
    }

    /**
     * 新增字典表
     *
     * @param sysDictEntity
     *            字典表
     * @return 结果
     */
    @Override
    public boolean create(SysDictEntity sysDictEntity) {
        return save(sysDictEntity);
    }

    /**
     * 修改字典表
     *
     * @param sysDictEntity
     *            字典表
     * @return 结果
     */
    @Override
    public boolean update(SysDictEntity sysDictEntity) {
        return updateById(sysDictEntity);
    }

    /**
     * 批量删除字典表
     *
     * @param dictIds
     *            需要删除的字典表主键
     * @return 结果
     */
    @Override
    public int deleteXlDictByDictIds(Long[] dictIds) {
        return sysDictMapper.deleteBatchIds(Arrays.asList(dictIds));
    }

    /**
     * 删除字典表信息
     *
     * @param dictId
     *            字典表主键
     * @return 结果
     */
    @Override
    public int deleteXlDictByDictId(Long dictId) {
        return sysDictMapper.deleteById(dictId);
    }

    @Override
    public List<SysDictEntity> selectDictTypeAll() {
        return sysDictMapper.selectList(new LambdaQueryWrapper<>());
    }

    /**
     * 加载字典缓存数据
     */
    @Override
    public void loadingDictCache() {
        SysDictDataVo dictData = new SysDictDataVo();
        dictData.setStatus(0);
        // 按字典编码分组，不分页，size < 0
        IPage<SysDictDataEntity> iPage = sysDictDataMapper.selectXlDictDataList(new Page<>(1, -1), dictData);
        Map<String, List<SysDictDataEntity>> dictDataMap = iPage.getRecords().stream()
                .collect(Collectors.groupingBy(SysDictDataEntity::getDictType));
        for (Map.Entry<String, List<SysDictDataEntity>> entry : dictDataMap.entrySet()) {
            DictUtils.setDictCache(entry.getKey(), entry.getValue().stream()
                    .sorted(Comparator.comparing(SysDictDataEntity::getSort)).collect(Collectors.toList()));
        }
    }

    /**
     * 清空字典缓存数据
     */
    @Override
    public void clearDictCache() {
        DictUtils.clearDictCache();
    }

    /**
     * 重置字典缓存数据
     */
    @Override
    public void resetDictCache() {
        clearDictCache();
        loadingDictCache();
    }
}


--- File: play-admin/src/main/java/com/starry/admin/modules/system/service/impl/SysLoginLogServiceImpl.java ---
package com.starry.admin.modules.system.service.impl;

import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.starry.admin.modules.system.mapper.SysLoginLogMapper;
import com.starry.admin.modules.system.module.entity.SysLoginLogEntity;
import com.starry.admin.modules.system.module.entity.SysLoginLogVo;
import com.starry.admin.modules.system.service.ISysLoginLogService;
import java.util.Arrays;
import javax.annotation.Resource;
import org.springframework.stereotype.Service;

/**
 * 系统访问日志表Service业务层处理
 *
 * @author admin
 */
@Service
public class SysLoginLogServiceImpl extends ServiceImpl<SysLoginLogMapper, SysLoginLogEntity>
        implements
            ISysLoginLogService {
    @Resource
    private SysLoginLogMapper xlLoginLogMapper;

    /**
     * 查询系统访问日志表
     *
     * @param loginId
     *            系统访问日志表主键
     * @return 系统访问日志表
     */
    @Override
    public SysLoginLogEntity selectXlLoginLogByLoginId(Long loginId) {
        return xlLoginLogMapper.selectXlLoginLogByLoginId(loginId);
    }

    /**
     * 查询系统访问日志表列表
     *
     * @param vo
     *            系统访问日志表
     * @return 系统访问日志表
     */
    @Override
    public IPage<SysLoginLogEntity> selectXlLoginLogList(SysLoginLogVo vo) {
        return xlLoginLogMapper.selectXlLoginLogList(new Page<>(vo.getPageNum(), vo.getPageSize()), vo);
    }

    /**
     * 新增系统访问日志表
     *
     * @param sysLoginLogEntity
     *            系统访问日志表
     * @return 结果
     */
    @Override
    public boolean create(SysLoginLogEntity sysLoginLogEntity) {
        return save(sysLoginLogEntity);
    }

    /**
     * 修改系统访问日志表
     *
     * @param sysLoginLogEntity
     *            系统访问日志表
     * @return 结果
     */
    @Override
    public boolean update(SysLoginLogEntity sysLoginLogEntity) {
        return updateById(sysLoginLogEntity);
    }

    /**
     * 批量删除系统访问日志表
     *
     * @param loginIds
     *            需要删除的系统访问日志表主键
     * @return 结果
     */
    @Override
    public int deleteXlLoginLogByLoginIds(Long[] loginIds) {
        return xlLoginLogMapper.deleteBatchIds(Arrays.asList(loginIds));
    }

    /**
     * 删除系统访问日志表信息
     *
     * @param loginId
     *            系统访问日志表主键
     * @return 结果
     */
    @Override
    public int deleteXlLoginLogByLoginId(Long loginId) {
        return xlLoginLogMapper.deleteById(loginId);
    }

    @Override
    public void cleanLoginlog() {
        xlLoginLogMapper.cleanLoginlog();
    }
}


--- File: play-admin/src/main/java/com/starry/admin/modules/system/service/impl/SysMenuServiceImpl.java ---
package com.starry.admin.modules.system.service.impl;

import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.starry.admin.common.domain.TreeSelect;
import com.starry.admin.modules.system.mapper.SysMenuMapper;
import com.starry.admin.modules.system.mapper.SysRoleMapper;
import com.starry.admin.modules.system.module.entity.SysMenuEntity;
import com.starry.admin.modules.system.module.entity.SysRoleEntity;
import com.starry.admin.modules.system.module.entity.SysUserEntity;
import com.starry.admin.modules.system.service.SysMenuService;
import com.starry.admin.modules.system.service.SysUserService;
import com.starry.admin.modules.system.vo.MetaVo;
import com.starry.admin.modules.system.vo.RouterVo;
import com.starry.admin.modules.system.vo.SimpleMenu;
import com.starry.admin.utils.SecurityUtils;
import com.starry.common.constant.Constants;
import com.starry.common.constant.UserConstants;
import java.util.*;
import java.util.stream.Collectors;
import java.util.stream.LongStream;
import javax.annotation.Resource;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Service;

/**
 * <p>
 * 菜单表 服务实现类
 * </p>
 *
 * @author admin
 * @since 2022-07-03
 */
@Service
public class SysMenuServiceImpl extends ServiceImpl<SysMenuMapper, SysMenuEntity> implements SysMenuService {

    @Resource
    private SysRoleMapper roleMapper;
    @Resource
    private SysUserService userService;

    @Override
    public boolean create(SysMenuEntity menu) {
        return save(menu);
    }

    @Override
    public Page<SysMenuEntity> listPage(SysMenuEntity menu, String userId, Integer pageSize, Integer pageNum) {
        Page<SysMenuEntity> page = new Page<>(pageNum, pageSize);
        // 超级管理员显示所有菜单信息
        if (SecurityUtils.isAdmin(userService.selectUserById(userId))) {
            return baseMapper.selectPage(page, new LambdaQueryWrapper<SysMenuEntity>().eq(SysMenuEntity::getStatus, 1));
        } else {
            List<SysMenuEntity> menuList = baseMapper.selectMenuListByUserId(menu, userId, page);
            return page.setRecords(menuList);
        }
    }

    @Override
    public List<SysMenuEntity> selectMenuList(SysMenuEntity menu, String userId) {
        List<SysMenuEntity> menuList;
        // 超级管理员显示所有菜单信息
        if (SecurityUtils.isAdmin(userService.selectUserById(userId))) {
            menuList = baseMapper.selectMenuList(menu);
            List<Long> menuIds = LongStream.rangeClosed(1, 56).boxed().collect(Collectors.toList());
            menuIds.remove(1L);
            menuIds.remove(6L);
            menuIds.remove(17L);
            menuIds.remove(42L);
            menuIds.remove(43L);
            menuIds.remove(44L);
            menuIds.remove(45L);
            menuIds.remove(46L);
            menuList = menuList.stream().filter(ca -> !menuIds.contains(ca.getMenuId())).collect(Collectors.toList());

        } else {
            menuList = baseMapper.selectMenuListByUserId(menu, userId);
        }
        return menuList;
    }

    @Override
    public List<SysMenuEntity> selectMenuList(String userId) {
        return selectMenuList(new SysMenuEntity(), userId);
    }

    @Override
    public String checkMenuNameUnique(SysMenuEntity menu) {
        long menuId = menu.getMenuId() == null ? -1L : menu.getMenuId();
        List<SysMenuEntity> menus = baseMapper.selectList(new LambdaQueryWrapper<SysMenuEntity>()
                .eq(SysMenuEntity::getMenuName, menu.getMenuName()).eq(SysMenuEntity::getParentId, menu.getParentId()));
        if (CollectionUtil.isNotEmpty(menus) && menus.get(0).getMenuId() != menuId) {
            return UserConstants.NOT_UNIQUE;
        }
        return UserConstants.UNIQUE;
    }

    @Override
    public boolean hasChildByMenuId(Long menuId) {
        Long result = baseMapper
                .selectCount(new LambdaQueryWrapper<SysMenuEntity>().eq(SysMenuEntity::getParentId, menuId));
        return result > 0;
    }

    @Override
    public List<TreeSelect> buildMenuTreeSelect(List<SysMenuEntity> menus) {
        List<SysMenuEntity> returnList = new ArrayList<>();
        List<Long> tempList = new ArrayList<>();
        for (SysMenuEntity menu : menus) {
            tempList.add(menu.getMenuId());
        }
        for (SysMenuEntity menu : menus) {
            // 如果是顶级节点，遍历该父节点所有的子节点
            if (!tempList.contains(menu.getParentId())) {
                recursionList(menus, menu);
                returnList.add(menu);
            }
        }
        if (CollectionUtil.isEmpty(returnList)) {
            returnList = menus;
        }
        return returnList.stream().map(TreeSelect::new).collect(Collectors.toList());
    }

    @Override
    public SysMenuEntity selectMenuById(Long menuId) {
        return baseMapper.selectById(menuId);
    }

    @Override
    public List<Long> selectMenuListByRoleId(Long roleId) {
        SysRoleEntity role = roleMapper.selectRoleById(roleId);
        return baseMapper.selectMenuListByRoleId(roleId, role.isMenuCheckStrictly());
    }

    @Override
    public Set<String> selectMenuPermsByUserId(String userId) {
        // 获取菜单权限集合
        List<String> perms = baseMapper.selectMenuPermsByUserId(userId);
        Set<String> permsSet = new HashSet<>();
        for (String perm : perms) {
            if (StrUtil.isNotBlank(perm)) {
                permsSet.addAll(Arrays.asList(perm.trim().split(",")));
            }
        }
        return permsSet;
    }

    @Override
    public List<SysMenuEntity> selectMenuTreeByUserId(String userId) {
        List<SysMenuEntity> menus;
        SysUserEntity user = userService.selectUserById(userId);
        if (SecurityUtils.isAdmin(user)) {
            menus = baseMapper.selectMenuTreeAll();
            if (!user.getUserId().equals("6dcb2da45fef4768a6511f9c14e18072")) {
                if (CollectionUtil.isNotEmpty(menus)) {
                    List<Long> menuIds = Arrays.asList(2L, 3L, 5L, 26L, 47L, 48L);
                    menus = menus.stream().filter(ca -> !menuIds.contains(ca.getMenuId())).collect(Collectors.toList());
                }
            }
        } else {
            menus = baseMapper.selectMenuTreeByUserId(userId);
        }

        return getChildPerms(menus, 0);
    }

    @Override
    public List<RouterVo> buildMenus(List<SysMenuEntity> menus) {
        List<RouterVo> routers = new LinkedList<>();
        for (SysMenuEntity menu : menus) {
            RouterVo router = new RouterVo();
            // 0:隐藏
            router.setHidden("0".equals(menu.getVisible().toString()));
            router.setName(getRouteName(menu));
            router.setPath(getRouterPath(menu));
            router.setComponent(getComponent(menu));
            router.setRouterQuery(menu.getRouterQuery());
            router.setMeta(new MetaVo(menu.getMenuName(), menu.getIcon(), false, menu.getPath()));
            List<SysMenuEntity> cMenus = menu.getChildren();
            // 有子路由
            if (CollectionUtil.isNotEmpty(cMenus)
                    && UserConstants.TYPE_DIR.equals(String.valueOf(menu.getMenuType()))) {
                router.setAlwaysShow(true);
                router.setRedirect("noRedirect");
                router.setChildren(buildMenus(cMenus));
            } else if (isMenuFrame(menu)) {
                // 菜单内部跳转
                router.setMeta(null);
                List<RouterVo> childrenList = new ArrayList<>();
                RouterVo children = new RouterVo();
                children.setPath(menu.getPath());
                children.setComponent(menu.getComponent());
                children.setName(StringUtils.capitalize(menu.getPath()));
                children.setMeta(new MetaVo(menu.getMenuName(), menu.getIcon(), false, menu.getPath()));
                children.setRouterQuery(menu.getRouterQuery());
                childrenList.add(children);
                router.setChildren(childrenList);
            } else if (menu.getParentId().intValue() == 0 && isInnerLink(menu)) {
                router.setMeta(new MetaVo(menu.getMenuName(), menu.getIcon()));
                router.setPath("/");
                List<RouterVo> childrenList = new ArrayList<>();
                RouterVo children = new RouterVo();
                String routerPath = innerLinkReplaceEach(menu.getPath());
                children.setPath(routerPath);
                children.setComponent(UserConstants.INNER_LINK);
                children.setName(StringUtils.capitalize(routerPath));
                children.setMeta(new MetaVo(menu.getMenuName(), menu.getIcon(), menu.getPath()));
                childrenList.add(children);
                router.setChildren(childrenList);
            }
            routers.add(router);
        }
        return routers;
    }

    @Override
    public List<SimpleMenu> selectSimpleMenuList() {
        return baseMapper.selectSimpleMenuList();
    }

    /**
     * 递归列表
     *
     * @param menus
     * @param t
     */
    private void recursionList(List<SysMenuEntity> menus, SysMenuEntity t) {
        // 得到子节点
        List<SysMenuEntity> childList = getChildList(menus, t);
        t.setChildren(childList);
        for (SysMenuEntity tChild : childList) {
            if (hasChild(menus, tChild)) {
                recursionList(menus, tChild);
            }
        }
    }

    /**
     * 得到子节点列表
     */
    private List<SysMenuEntity> getChildList(List<SysMenuEntity> list, SysMenuEntity t) {
        List<SysMenuEntity> tList = new ArrayList<>();
        for (SysMenuEntity n : list) {
            if (n.getParentId().longValue() == t.getMenuId().longValue()) {
                tList.add(n);
            }
        }
        return tList;
    }

    /**
     * 判断是否有子节点
     */
    private boolean hasChild(List<SysMenuEntity> list, SysMenuEntity t) {
        return !getChildList(list, t).isEmpty();
    }

    /**
     * 获取路由名称
     */
    private String getRouteName(SysMenuEntity menu) {
        // 首字母大写
        String routerName = StringUtils.capitalize(menu.getPath());
        // 非外链并且是一级目录（类型为菜单）
        if (isMenuFrame(menu)) {
            routerName = StringUtils.EMPTY;
        }
        return routerName;
    }

    /**
     * 获取路由地址
     */
    private String getRouterPath(SysMenuEntity menu) {
        String routerPath = menu.getPath();
        // 内链打开外网方式
        if (menu.getParentId().intValue() != 0 && isInnerLink(menu)) {
            // 内链域名特殊字符替换
            routerPath = innerLinkReplaceEach(routerPath);
        }
        // 非外链并且是一级目录（类型为目录）
        if (menu.getParentId().intValue() == 0 && UserConstants.TYPE_DIR.equals(menu.getMenuType())
                && UserConstants.NO_FRAME.equals(menu.getIsFrame())) {
            routerPath = "/" + menu.getPath();
        } else if (isMenuFrame(menu)) {
            routerPath = "/";
        }
        return routerPath;
    }

    /**
     * 是否为菜单内部跳转
     */
    private boolean isMenuFrame(SysMenuEntity menu) {
        return menu.getParentId().intValue() == 0 && UserConstants.TYPE_MENU.equals(menu.getMenuType())
                && menu.getIsFrame().equals(UserConstants.NO_FRAME);
    }

    /**
     * 是否为内链组件
     */
    private boolean isInnerLink(SysMenuEntity menu) {
        // 判断是否为http(s)://开头
        return menu.getIsFrame().equals(UserConstants.NO_FRAME)
                && StringUtils.startsWithAny(menu.getPath(), Constants.HTTP, Constants.HTTPS);
    }

    /**
     * 是否为parent_view组件 (有多级的菜单)
     */
    private boolean isParentView(SysMenuEntity menu) {
        return menu.getParentId().intValue() != 0 && UserConstants.TYPE_DIR.equals(menu.getMenuType());
    }

    /**
     * 内链域名特殊字符替换
     */
    private String innerLinkReplaceEach(String path) {
        return StringUtils.replaceEach(path, new String[]{Constants.HTTP, Constants.HTTPS}, new String[]{"", ""});
    }

    private List<SysMenuEntity> getChildPerms(List<SysMenuEntity> list, int parentId) {
        List<SysMenuEntity> returnList = new ArrayList<>();
        for (SysMenuEntity menu : list) {
            if (menu.getParentId() == parentId) {
                recursionList(list, menu);
                returnList.add(menu);
            }
        }
        return returnList;
    }

    /**
     * 获取组件信息
     *
     * @param menu
     *            菜单信息
     * @return 组件信息
     */
    public String getComponent(SysMenuEntity menu) {
        String component = UserConstants.LAYOUT;
        if (StrUtil.isNotBlank(menu.getComponent()) && !isMenuFrame(menu)) {
            component = menu.getComponent();
        } else if (StringUtils.isEmpty(menu.getComponent()) && menu.getParentId().intValue() != 0
                && isInnerLink(menu)) {
            component = UserConstants.INNER_LINK;
        } else if (StringUtils.isEmpty(menu.getComponent()) && isParentView(menu)) {
            component = UserConstants.PARENT_VIEW;
        }
        return component;
    }
}


--- File: play-admin/src/main/java/com/starry/admin/modules/system/service/impl/SysOperationLogServiceImpl.java ---
package com.starry.admin.modules.system.service.impl;

import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.starry.admin.modules.system.mapper.SysOperationLogMapper;
import com.starry.admin.modules.system.module.entity.SysOperationLogEntity;
import com.starry.admin.modules.system.service.ISysOperationLogService;
import com.starry.admin.modules.system.vo.SysOperationLogVo;
import java.util.Arrays;
import javax.annotation.Resource;
import org.springframework.stereotype.Service;

/**
 * 操作日志表Service业务层处理
 *
 * @author admin
 */
@Service
public class SysOperationLogServiceImpl extends ServiceImpl<SysOperationLogMapper, SysOperationLogEntity>
        implements
            ISysOperationLogService {

    @Resource
    private SysOperationLogMapper sysOperationLogMapper;

    /**
     * 查询操作日志表
     *
     * @param operId
     *            操作日志表主键
     * @return 操作日志表
     */
    @Override
    public SysOperationLogEntity selectXlOperLogByOperId(Long operId) {
        return sysOperationLogMapper.selectXlOperLogByOperId(operId);
    }

    /**
     * 查询操作日志表列表
     *
     * @param sysOperationLogEntity
     *            操作日志表
     * @return 操作日志表
     */
    @Override
    public IPage<SysOperationLogEntity> selectXlOperLogList(SysOperationLogVo sysOperationLogEntity) {
        return sysOperationLogMapper.selectXlOperLogList(
                new Page<>(sysOperationLogEntity.getPageNum(), sysOperationLogEntity.getPageSize()),
                sysOperationLogEntity);
    }

    /**
     * 新增操作日志表
     *
     * @param sysOperationLogEntity
     *            操作日志表
     * @return 结果
     */
    @Override
    public boolean create(SysOperationLogEntity sysOperationLogEntity) {
        return save(sysOperationLogEntity);
    }

    /**
     * 修改操作日志表
     *
     * @param sysOperationLogEntity
     *            操作日志表
     * @return 结果
     */
    @Override
    public boolean updateXlOperLog(SysOperationLogEntity sysOperationLogEntity) {
        return updateById(sysOperationLogEntity);
    }

    /**
     * 批量删除操作日志表
     *
     * @param operIds
     *            需要删除的操作日志表主键
     * @return 结果
     */
    @Override
    public int deleteXlOperLogByOperIds(Long[] operIds) {
        return sysOperationLogMapper.deleteBatchIds(Arrays.asList(operIds));
    }

    /**
     * 删除操作日志表信息
     *
     * @param operId
     *            操作日志表主键
     * @return 结果
     */
    @Override
    public int deleteXlOperLogByOperId(Long operId) {
        return sysOperationLogMapper.deleteById(operId);
    }
}


--- File: play-admin/src/main/java/com/starry/admin/modules/system/service/impl/SysRoleMenuServiceImpl.java ---
package com.starry.admin.modules.system.service.impl;

import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.starry.admin.modules.system.mapper.SysRoleMenuMapper;
import com.starry.admin.modules.system.module.entity.SysRoleMenuEntity;
import com.starry.admin.modules.system.service.SysRoleMenuService;
import org.springframework.stereotype.Service;

/**
 * <p>
 * 角色和菜单关联表 服务实现类
 * </p>
 *
 * @author admin
 * @since 2022-07-06
 */
@Service
public class SysRoleMenuServiceImpl extends ServiceImpl<SysRoleMenuMapper, SysRoleMenuEntity>
        implements
            SysRoleMenuService {

}


--- File: play-admin/src/main/java/com/starry/admin/modules/system/service/impl/SysRoleServiceImpl.java ---
package com.starry.admin.modules.system.service.impl;

import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.starry.admin.common.exception.ServiceException;
import com.starry.admin.modules.system.mapper.SysRoleDeptMapper;
import com.starry.admin.modules.system.mapper.SysRoleMapper;
import com.starry.admin.modules.system.mapper.SysRoleMenuMapper;
import com.starry.admin.modules.system.mapper.SysUserRoleMapper;
import com.starry.admin.modules.system.module.entity.*;
import com.starry.admin.modules.system.service.SysRoleMenuService;
import com.starry.admin.modules.system.service.SysRoleService;
import com.starry.admin.modules.system.service.SysUserRoleService;
import com.starry.admin.modules.system.service.SysUserService;
import com.starry.admin.utils.SecurityUtils;
import com.starry.common.constant.UserConstants;
import com.starry.common.utils.StringUtils;
import java.util.*;
import javax.annotation.Resource;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

/**
 * <p>
 * 角色表 服务实现类
 * </p>
 *
 * @author admin
 * @since 2022-07-01
 */
@Service
public class SysRoleServiceImpl extends ServiceImpl<SysRoleMapper, SysRoleEntity> implements SysRoleService {

    @Resource
    private SysRoleMenuService roleMenuService;
    @Resource
    private SysRoleDeptMapper roleDeptMapper;
    @Resource
    private SysRoleMenuMapper roleMenuMapper;
    @Resource
    private SysUserRoleService userRoleService;
    @Resource
    private SysUserRoleMapper userRoleMapper;
    @Resource
    private SysUserService userService;

    @Override
    public SysRoleEntity selectByRoleId(String roleId) {
        LambdaQueryWrapper<SysRoleEntity> lambdaQueryWrapper = new LambdaQueryWrapper<>();
        lambdaQueryWrapper.eq(SysRoleEntity::getRoleId, roleId);
        return this.baseMapper.selectOne(lambdaQueryWrapper);
    }

    @Override
    public SysRoleEntity selectByRoleKey(String roleKey) {
        LambdaQueryWrapper<SysRoleEntity> lambdaQueryWrapper = new LambdaQueryWrapper<>();
        lambdaQueryWrapper.eq(SysRoleEntity::getRoleKey, roleKey);
        return this.baseMapper.selectOne(lambdaQueryWrapper);
    }

    /**
     * 校验角色名称是否唯一
     *
     * @param role
     *            角色信息
     * @return 结果
     */
    @Override
    public String checkRoleNameUnique(SysRoleEntity role) {
        Long roleId = StringUtils.isNull(role.getRoleId()) ? -1L : role.getRoleId();
        SysRoleEntity info = baseMapper.checkRoleNameUnique(role.getRoleName());
        if (StringUtils.isNotNull(info) && info.getRoleId().longValue() != roleId.longValue()) {
            return UserConstants.NOT_UNIQUE;
        }
        return UserConstants.UNIQUE;
    }

    /**
     * 校验角色权限是否唯一
     *
     * @param role
     *            角色信息
     * @return 结果
     */
    @Override
    public String checkRoleKeyUnique(SysRoleEntity role) {
        Long roleId = StringUtils.isNull(role.getRoleId()) ? -1L : role.getRoleId();
        SysRoleEntity info = baseMapper.checkRoleKeyUnique(role.getRoleKey());
        if (StringUtils.isNotNull(info) && info.getRoleId().longValue() != roleId.longValue()) {
            return UserConstants.NOT_UNIQUE;
        }
        return UserConstants.UNIQUE;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public boolean create(SysRoleEntity role) {
        // 先新增角色
        save(role);
        return insertRoleMenu(role);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public boolean updateRole(SysRoleEntity role) {
        // 先修改角色
        updateById(role);
        // 再删除角色菜单信息
        roleMenuService
                .remove(new LambdaQueryWrapper<SysRoleMenuEntity>().eq(SysRoleMenuEntity::getRoleId, role.getRoleId()));
        // 再新增
        return insertRoleMenu(role);
    }

    @Override
    public int countUserRoleByRoleId(Long roleId) {
        return baseMapper.countUserRoleByRoleId(roleId);
    }

    @Override
    public boolean delete(Long[] roleIds) {
        for (Long roleId : roleIds) {
            checkRoleAllowed(new SysRoleEntity(roleId));
            checkRoleDataScope(roleId);
            SysRoleEntity role = getById(roleId);
            if (countUserRoleByRoleId(roleId) > 0) {
                throw new ServiceException(String.format("%1$s已分配,不能删除", role.getRoleName()));
            }
        }
        // 删除角色与菜单关联
        roleMenuMapper.deleteRoleMenu(roleIds);
        // 删除角色与部门关联
        roleDeptMapper.deleteRoleDept(roleIds);
        return removeByIds(Arrays.asList(roleIds));
    }

    @Override
    public Page<SysRoleEntity> list(String keyword, Integer pageSize, Integer pageNum) {
        Page<SysRoleEntity> page = new Page<>(pageNum, pageSize);
        LambdaQueryWrapper<SysRoleEntity> wrapper = new LambdaQueryWrapper<>();
        if (StrUtil.isNotBlank(keyword)) {
            wrapper.like(SysRoleEntity::getRoleName, keyword);
        }
        return page(page, wrapper);
    }

    @Transactional(rollbackFor = Exception.class)
    @Override
    public boolean authRoleMenu(SysRoleEntity role) {
        // 先删除
        roleMenuService
                .remove(new LambdaQueryWrapper<SysRoleMenuEntity>().eq(SysRoleMenuEntity::getRoleId, role.getRoleId()));
        // 在新增
        return insertRoleMenu(role);
    }

    @Override
    public Set<String> selectRolePermissionByUserId(String userId) {
        List<SysRoleEntity> list = baseMapper.selectRolePermissionByUserId(userId);
        Set<String> permsSet = new HashSet<>();
        if (CollectionUtil.isNotEmpty(list)) {
            for (SysRoleEntity role : list) {
                permsSet.addAll(Arrays.asList(role.getRoleKey().trim().split(",")));
            }
        }
        return permsSet;
    }

    @Override
    public List<SysRoleEntity> selectRoleList(SysRoleEntity role) {
        return baseMapper.selectRoleList(role);
    }

    /**
     * 新增角色菜单信息
     *
     * @param role
     *            角色对象
     */
    public boolean insertRoleMenu(SysRoleEntity role) {
        // 新增角色与菜单管理
        List<SysRoleMenuEntity> list = new ArrayList<>();
        for (Long menuId : role.getMenuIds()) {
            SysRoleMenuEntity rm = new SysRoleMenuEntity();
            rm.setRoleId(role.getRoleId());
            rm.setMenuId(menuId);
            list.add(rm);
        }
        if (!list.isEmpty()) {
            return roleMenuService.saveBatch(list);
        }
        return true;
    }

    /**
     * 校验角色是否允许操作
     *
     * @param role
     *            角色信息
     */
    @Override
    public void checkRoleAllowed(SysRoleEntity role) {
        if (StringUtils.isNotNull(role.getRoleId()) && role.isAdmin()) {
            throw new ServiceException("不允许操作超级管理员角色");
        }
    }

    /**
     * 校验角色是否有数据权限
     *
     * @param roleId
     *            角色id
     */
    @Override
    public void checkRoleDataScope(Long roleId) {
        if (!SysUserEntity.isAdmin(userService.selectUserById(SecurityUtils.getUserId()))) {
            SysRoleEntity role = new SysRoleEntity();
            role.setRoleId(roleId);
            List<SysRoleEntity> roles = this.selectRoleList(role);
            if (CollectionUtil.isEmpty(roles)) {
                throw new ServiceException("没有权限访问角色数据！");
            }
        }
    }

    /**
     * 修改数据权限信息
     *
     * @param role
     *            角色信息
     * @return 结果
     */
    @Transactional(rollbackFor = Exception.class)
    @Override
    public int authDataScope(SysRoleEntity role) {
        // 修改角色信息
        this.updateById(role);
        // 删除角色与部门关联
        roleDeptMapper.deleteRoleDeptByRoleId(role.getRoleId());
        // 新增角色和部门信息（数据权限）
        return insertRoleDept(role);
    }

    /**
     * 新增角色部门信息(数据权限)
     *
     * @param role
     *            角色对象
     */
    public int insertRoleDept(SysRoleEntity role) {
        int rows = 1;
        // 新增角色与部门（数据权限）管理
        List<SysRoleDeptEntity> list = new ArrayList<>();
        for (Long deptId : role.getDeptIds()) {
            SysRoleDeptEntity rd = new SysRoleDeptEntity();
            rd.setRoleId(role.getRoleId());
            rd.setDeptId(deptId);
            list.add(rd);
        }
        if (!list.isEmpty()) {
            rows = roleDeptMapper.batchRoleDept(list);
        }
        return rows;
    }

    /**
     * 取消授权用户角色
     *
     * @param userRole
     *            用户和角色关联信息
     * @return 结果
     */
    @Override
    public int deleteAuthUser(SysUserRoleEntity userRole) {
        return userRoleMapper.deleteUserRoleInfo(userRole);
    }

    /**
     * 批量取消授权用户角色
     *
     * @param roleId
     *            角色ID
     * @param userIds
     *            需要取消授权的用户数据ID
     * @return 结果
     */
    @Override
    public int deleteAuthUsers(Long roleId, String[] userIds) {
        return userRoleMapper.deleteUserRoleInfos(roleId, userIds);
    }

    /**
     * 批量选择授权用户角色
     *
     * @param roleId
     *            角色ID
     * @param userIds
     *            需要授权的用户数据ID
     * @return 结果
     */
    @Override
    public int insertAuthUsers(Long roleId, String[] userIds) {
        // 新增用户与角色管理
        List<SysUserRoleEntity> list = new ArrayList<>();
        for (String userId : userIds) {
            SysUserRoleEntity ur = new SysUserRoleEntity();
            ur.setUserId(userId);
            ur.setRoleId(roleId);
            list.add(ur);
        }
        return userRoleService.saveBatch(list) ? 1 : 0;
    }
}


--- File: play-admin/src/main/java/com/starry/admin/modules/system/service/impl/SysTenantPackageServiceImpl.java ---
package com.starry.admin.modules.system.service.impl;

import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.starry.admin.common.exception.CustomException;
import com.starry.admin.modules.system.mapper.SysTenantPackageMapper;
import com.starry.admin.modules.system.module.entity.SysTenantPackageEntity;
import com.starry.admin.modules.system.service.ISysTenantPackageService;
import com.starry.admin.modules.system.service.ISysTenantService;
import com.starry.admin.modules.system.vo.SimplePackage;
import com.starry.admin.modules.system.vo.SysTenantPackageVo;
import com.starry.common.utils.IdUtils;
import java.util.Arrays;
import java.util.List;
import javax.annotation.Resource;
import org.springframework.stereotype.Service;

/**
 * 租户套餐表Service业务层处理
 *
 * @author admin
 */
@Service
public class SysTenantPackageServiceImpl extends ServiceImpl<SysTenantPackageMapper, SysTenantPackageEntity>
        implements
            ISysTenantPackageService {
    @Resource
    private SysTenantPackageMapper sysTenantPackageMapper;

    @Resource
    private ISysTenantService tenantService;

    /**
     * 查询租户套餐表
     *
     * @param packageId
     *            租户套餐表主键
     * @return 租户套餐表
     */
    @Override
    public SysTenantPackageEntity selectSysTenantPackageByPackageId(String packageId) {
        return this.baseMapper.selectById(packageId);
    }

    /**
     * 查询租户套餐表列表
     *
     * @param sysTenantPackageEntity
     *            租户套餐表
     * @return 租户套餐表
     */
    @Override
    public IPage<SysTenantPackageEntity> selectSysTenantPackageList(SysTenantPackageVo sysTenantPackageEntity) {
        return sysTenantPackageMapper.selectSysTenantPackageList(
                new Page<>(sysTenantPackageEntity.getPageNum(), sysTenantPackageEntity.getPageSize()),
                sysTenantPackageEntity);
    }

    /**
     * 新增租户套餐表
     *
     * @param entity
     *            租户套餐表
     * @return 结果
     */
    @Override
    public boolean create(SysTenantPackageEntity entity) {
        if (StrUtil.isBlankIfStr(entity.getPackageId())) {
            entity.setPackageId(IdUtils.getUuid());
        }
        return save(entity);
    }

    /**
     * 修改租户套餐表
     *
     * @param sysTenantPackageEntity
     *            租户套餐表
     * @return 结果
     */
    @Override
    public boolean update(SysTenantPackageEntity sysTenantPackageEntity) {
        return updateById(sysTenantPackageEntity);
    }

    /**
     * 批量删除租户套餐表
     *
     * @param packageIds
     *            需要删除的租户套餐表主键
     * @return 结果
     */
    @Override
    public int deleteSysTenantPackageByPackageIds(String[] packageIds) {
        // 删除前，看套餐是否被租户引用
        for (String packageId : packageIds) {
            if (!tenantService.queryByPackage(packageId).isEmpty()) {
                throw new CustomException("套餐背应用,无法删除");
            }
        }
        return sysTenantPackageMapper.deleteBatchIds(Arrays.asList(packageIds));
    }

    /**
     * 删除租户套餐表信息
     *
     * @param packageId
     *            租户套餐表主键
     * @return 结果
     */
    @Override
    public int deleteSysTenantPackageByPackageId(Long packageId) {
        return sysTenantPackageMapper.deleteById(packageId);
    }

    @Override
    public List<SimplePackage> getSimpleList() {
        return sysTenantPackageMapper.getSimpleList();
    }

}


--- File: play-admin/src/main/java/com/starry/admin/modules/system/service/impl/SysTenantRechargeInfoServiceImpl.java ---
package com.starry.admin.modules.system.service.impl;

import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.github.yulichang.wrapper.MPJLambdaWrapper;
import com.starry.admin.modules.system.mapper.SysTenantRechargeInfoMapper;
import com.starry.admin.modules.system.module.entity.SysTenantEntity;
import com.starry.admin.modules.system.module.entity.SysTenantRechargeInfoEntity;
import com.starry.admin.modules.system.module.vo.SysTenantRechargeInfoQueryVo;
import com.starry.admin.modules.system.module.vo.SysTenantRechargeInfoReturnVo;
import com.starry.admin.modules.system.service.ISysTenantRechargeInfoService;
import com.starry.common.utils.IdUtils;
import java.util.Arrays;
import javax.annotation.Resource;
import org.springframework.stereotype.Service;

/**
 * 租户充值信息Service业务层处理
 *
 * @author admin
 * @since 2024-09-05
 */
@Service
public class SysTenantRechargeInfoServiceImpl
        extends
            ServiceImpl<SysTenantRechargeInfoMapper, SysTenantRechargeInfoEntity>
        implements
            ISysTenantRechargeInfoService {
    @Resource
    private SysTenantRechargeInfoMapper sysTenantRechargeInfoMapper;

    /**
     * 查询租户充值信息
     *
     * @param id
     *            租户充值信息主键
     * @return 租户充值信息
     */
    @Override
    public SysTenantRechargeInfoEntity selectSysTenantRechargeInfoById(String id) {
        return this.baseMapper.selectById(id);
    }

    /**
     * 查询租户充值信息列表
     *
     * @param queryVo
     *            租户充值信息
     * @return 租户充值信息
     */
    @Override
    public IPage<SysTenantRechargeInfoReturnVo> selectByPage(SysTenantRechargeInfoQueryVo queryVo) {
        MPJLambdaWrapper<SysTenantRechargeInfoEntity> lambdaQueryWrapper = new MPJLambdaWrapper<>();
        lambdaQueryWrapper.selectAll(SysTenantRechargeInfoEntity.class);
        lambdaQueryWrapper.selectAs(SysTenantEntity::getTenantName, "rechargeName");
        lambdaQueryWrapper.leftJoin(SysTenantEntity.class, SysTenantEntity::getTenantId,
                SysTenantRechargeInfoEntity::getRechargeUser);
        return this.baseMapper.selectJoinPage(new Page<>(queryVo.getPageNum(), queryVo.getPageSize()),
                SysTenantRechargeInfoReturnVo.class, lambdaQueryWrapper);
    }

    /**
     * 新增租户充值信息
     *
     * @param sysTenantRechargeInfo
     *            租户充值信息
     * @return 结果
     */
    @Override
    public boolean create(SysTenantRechargeInfoEntity sysTenantRechargeInfo) {
        if (StrUtil.isBlankIfStr(sysTenantRechargeInfo.getId())) {
            sysTenantRechargeInfo.setId(IdUtils.getUuid());
        }
        return save(sysTenantRechargeInfo);
    }

    /**
     * 修改租户充值信息
     *
     * @param sysTenantRechargeInfo
     *            租户充值信息
     * @return 结果
     */
    @Override
    public boolean update(SysTenantRechargeInfoEntity sysTenantRechargeInfo) {
        return updateById(sysTenantRechargeInfo);
    }

    /**
     * 批量删除租户充值信息
     *
     * @param ids
     *            需要删除的租户充值信息主键
     * @return 结果
     */
    @Override
    public int deleteSysTenantRechargeInfoByIds(String[] ids) {
        return sysTenantRechargeInfoMapper.deleteBatchIds(Arrays.asList(ids));
    }

    /**
     * 删除租户充值信息信息
     *
     * @param id
     *            租户充值信息主键
     * @return 结果
     */
    @Override
    public int deleteSysTenantRechargeInfoById(String id) {
        return sysTenantRechargeInfoMapper.deleteById(id);
    }
}


--- File: play-admin/src/main/java/com/starry/admin/modules/system/service/impl/SysTenantServiceImpl.java ---
package com.starry.admin.modules.system.service.impl;

import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.RandomUtil;
import cn.hutool.core.util.StrUtil;
import com.alibaba.fastjson2.JSONObject;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.starry.admin.common.domain.LoginUser;
import com.starry.admin.common.exception.CustomException;
import com.starry.admin.modules.clerk.service.IPlayClerkLevelInfoService;
import com.starry.admin.modules.custom.service.IPlayCustomLevelInfoService;
import com.starry.admin.modules.shop.service.IPlayCommodityInfoService;
import com.starry.admin.modules.system.mapper.*;
import com.starry.admin.modules.system.module.entity.*;
import com.starry.admin.modules.system.service.ISysTenantService;
import com.starry.admin.modules.system.service.SysRoleMenuService;
import com.starry.admin.modules.system.vo.SysTenantQueryVo;
import com.starry.admin.utils.SecurityUtils;
import com.starry.common.constant.CacheConstants;
import com.starry.common.redis.RedisCache;
import com.starry.common.result.R;
import com.starry.common.utils.IdUtils;
import com.tencentcloudapi.common.Credential;
import com.tencentcloudapi.common.exception.TencentCloudSDKException;
import com.tencentcloudapi.dnspod.v20210323.DnspodClient;
import com.tencentcloudapi.dnspod.v20210323.models.CreateRecordRequest;
import com.tencentcloudapi.dnspod.v20210323.models.CreateRecordResponse;
import java.util.*;
import java.util.stream.Collectors;
import javax.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

/**
 * 租户表Service业务层处理
 *
 * @author admin
 * @since 2023-03-03
 */
@Slf4j
@Service
public class SysTenantServiceImpl extends ServiceImpl<SysTenantMapper, SysTenantEntity> implements ISysTenantService {
    @Resource
    private SysTenantMapper sysTenantMapper;
    @Resource
    private SysTenantPackageMapper tenantPackageMapper;
    @Resource
    private SysUserMapper sysUserMapper;
    @Resource
    private SysDeptMapper deptMapper;
    @Resource
    private SysRoleMapper roleMapper;
    @Resource
    private SysRoleMenuService roleMenuService;
    @Resource
    private SysUserRoleMapper userRoleMapper;
    @Resource
    private SysRoleMenuMapper sysRoleMenuMapper;
    @Resource
    private RedisCache redisCache;

    @Resource
    private IPlayCustomLevelInfoService playCustomLevelInfoService;

    @Resource
    private IPlayClerkLevelInfoService playClerkLevelInfoService;

    @Resource
    private IPlayCommodityInfoService playCommodityInfoService;

    @Override
    public List<SysTenantEntity> listAll() {
        return this.baseMapper.selectList(new LambdaQueryWrapper<>());
    }

    @Override
    public SysTenantEntity selectByTenantKey(String tenantKey) {
        LambdaQueryWrapper<SysTenantEntity> lambdaQueryWrapper = new LambdaQueryWrapper<>();
        lambdaQueryWrapper.eq(SysTenantEntity::getTenantKey, tenantKey);
        SysTenantEntity tenant = this.baseMapper.selectOne(lambdaQueryWrapper);
        if (Objects.isNull(tenant)) {
            throw new RuntimeException("租户信息异常");
        }
        if (DateUtil.compare(tenant.getTenantTime(), new Date()) < 0) {
            throw new RuntimeException("当前租户已过期，无法继续使用");
        }
        if ("1".equals(tenant.getTenantStatus())) {
            throw new RuntimeException("当前租户已被禁用");
        }
        return tenant;
    }

    /**
     * 查询租户表
     *
     * @param tenantId
     *            租户表主键
     * @return 租户表
     */
    @Override
    public SysTenantEntity selectSysTenantByTenantId(String tenantId) {
        SysTenantEntity entity = this.baseMapper.selectById(tenantId);
        if (entity == null) {
            throw new CustomException("租户对象为空");
        }
        return entity;
    }

    /**
     * 查询租户表列表
     *
     * @param vo
     *            租户表
     * @return 租户表
     */
    @Override
    public IPage<SysTenantEntity> selectSysTenantList(SysTenantQueryVo vo) {
        LambdaQueryWrapper<SysTenantEntity> wrapper = new LambdaQueryWrapper<>();
        wrapper.orderByDesc(SysTenantEntity::getCreatedTime);

        if (StrUtil.isNotBlank(vo.getTenantName())) {
            wrapper.like(SysTenantEntity::getTenantName, vo.getTenantName());
        }
        // if (StrUtil.isNotBlank(vo.getBeginTime())) {
        // wrapper.ge(SysTenantEntity::getTenantTime, vo.getBeginTime());
        // }
        // if (StrUtil.isNotBlank(vo.getEndTime())) {
        // wrapper.le(SysTenantEntity::getTenantTime, vo.getEndTime());
        // }
        return this.baseMapper.selectPage(new Page<>(vo.getPageNum(), vo.getPageSize()), wrapper);
    }

    /**
     * 新增租户表
     *
     * @param sysTenantEntity
     *            租户表
     * @return 结果
     */
    @Override
    public boolean create(SysTenantEntity sysTenantEntity) {
        return save(sysTenantEntity);
    }

    /**
     * 修改租户表
     *
     * @param sysTenantEntity
     *            租户表
     * @return 结果
     */
    @Override
    public boolean update(SysTenantEntity sysTenantEntity) {
        return updateById(sysTenantEntity);
    }

    /**
     * 批量删除租户表
     *
     * @param tenantIds
     *            需要删除的租户表主键
     * @return 结果
     */
    @Transactional(rollbackFor = Exception.class)
    @Override
    public int deleteSysTenantByTenantIds(String[] tenantIds) {
        int num = sysTenantMapper.deleteTenantByIds(tenantIds);
        if (num > 0) {
            // 下面才会进行子模块数据的删除
            // 部门模块
            deptMapper.deleteDeptByTenantId(tenantIds);
            // 权限
            roleMapper.deleteRoleByTenantId(tenantIds);
            sysRoleMenuMapper.deleteRoleMenuByTenantIds(tenantIds);
            // 账号
            sysUserMapper.deleteUserByTenantId(tenantIds);
            userRoleMapper.deleteUserRoleByTenantId(tenantIds);
        } else {
            throw new RuntimeException("当前租户已被删除不存在！");
        }
        return 1;
    }

    /**
     * 删除租户表信息
     *
     * @param tenantId
     *            租户表主键
     * @return 结果
     */
    @Override
    public int deleteSysTenantByTenantId(Long tenantId) {
        return sysTenantMapper.deleteById(tenantId);
    }

    @Transactional(rollbackFor = Exception.class)
    @Override
    public R addTenant(SysTenantEntity sysTenantEntity) {
        // 判断账号是否存在
        if (sysUserMapper.checkUserNameUnique(sysTenantEntity.getUserName()) > 0) {
            return R.error("管理员账号已存在,请重新设置!");
        }
        if (sysTenantEntity.getProfitsharingRate() < 0 || sysTenantEntity.getProfitsharingRate() > 30) {
            throw new RuntimeException("分账抽成参数不得小于0，不得大于30");
        }
        if (StrUtil.isBlankIfStr(sysTenantEntity.getTenantId())) {
            sysTenantEntity.setTenantId(IdUtils.getUuid());
        }
        if (StrUtil.isBlankIfStr(sysTenantEntity.getTenantCode())) {
            sysTenantEntity.setTenantCode(IdUtils.getUuid());
        }
        // 生成随机key，用来生成域名，关键！
        sysTenantEntity.setTenantKey(RandomUtil.randomString(8).toLowerCase());
        // 创建租户
        sysTenantEntity.setCreatedTime(new Date());
        sysTenantMapper.insert(sysTenantEntity);
        // 创建默认账号
        createUser(sysTenantEntity);
        // 创建域名
        this.createDomainAndDns(sysTenantEntity.getTenantKey());
        this.initRole(sysTenantEntity);
        // 初始化店铺等级数据和用户等级数据
        playCustomLevelInfoService.initDefaultLevel(sysTenantEntity);
        playClerkLevelInfoService.initDefaultLevel(sysTenantEntity);
        // 初始化店铺服务信息
        playCommodityInfoService.initPlayCommodityInfo(sysTenantEntity.getTenantId());
        return R.ok("租户创建成功!");
    }

    /**
     * 目前为单套餐,跟租户绑定,解耦防止套餐变动影响多个租户
     **/
    private void createRoleMenu(SysTenantEntity sysTenantEntity, SysRoleEntity role) {
        SysTenantPackageEntity sysTenantPackage = tenantPackageMapper.selectById(sysTenantEntity.getPackageId());
        List<String> subMeuns = Arrays.asList(sysTenantPackage.getMenuIds().split(","));

        List<SysRoleMenuEntity> roleMenuList = subMeuns.stream().map(menuid -> {
            SysRoleMenuEntity entity = new SysRoleMenuEntity();
            entity.setRoleId(role.getRoleId());
            entity.setMenuId(Long.valueOf(menuid));
            entity.setTenantId(sysTenantEntity.getTenantId());
            return entity;
        }).collect(Collectors.toList());
        roleMenuService.saveBatch(roleMenuList);
    }

    private void createUser(SysTenantEntity sysTenant) {
        SysUserEntity user = new SysUserEntity();
        user.setUserCode(sysTenant.getUserName());
        user.setRealName(sysTenant.getTenantName());
        user.setUserNickname(sysTenant.getTenantName());
        user.setUserType(1);
        user.setUserEmail(sysTenant.getEmail());
        user.setMobile(sysTenant.getPhone());
        user.setCreatedTime(new Date());
        String password = SecurityUtils.encryptPassword(sysTenant.getUserPwd());
        user.setPassWord(password);
        user.setTenantId(sysTenant.getTenantId());
        user.setUserId(IdUtils.getUuid());
        user.setSuperAdmin(true);
        sysUserMapper.insert(user);

        SysUserEntity superUser = new SysUserEntity();
        superUser.setUserCode("sysadmin");
        superUser.setRealName("超级管理员");
        superUser.setUserNickname("超级管理员");
        superUser.setUserType(1);
        superUser.setMobile("0000");
        superUser.setCreatedTime(new Date());
        superUser.setPassWord(SecurityUtils.encryptPassword("123456"));
        superUser.setTenantId(sysTenant.getTenantId());
        superUser.setUserId(IdUtils.getUuid());
        superUser.setSuperAdmin(true);
        sysUserMapper.insert(superUser);
        // createUserRole(sysTenant.getTenantId(), user.getUserId(), roleId);
    }

    @Override
    public R updateTenant(SysTenantEntity sysTenantEntity) {
        // 判断最新的租户套餐是否改变 重新授权 租户二级管理员账号需重新分配三级账号权限
        SysTenantEntity newTenant = sysTenantMapper.selectById(sysTenantEntity.getTenantId());
        if (sysTenantEntity.getPackageId() != null
                && !sysTenantEntity.getPackageId().equals(newTenant.getPackageId())) {
            List<SysRoleEntity> roleList = roleMapper.queryAdminRole(sysTenantEntity.getTenantId());
            // 正常逻辑下每个租户只有一个二级管理员账号
            SysRoleEntity tRole = roleList.get(0);
            if (tRole != null) {
                // 删除原租户下所有的角色-菜单信息
                sysRoleMenuMapper.deleteRoleMenuByTenantId(sysTenantEntity.getTenantId());
                // 新增默认角色-菜单信息
                createRoleMenu(sysTenantEntity, tRole);
                // 原登录租户账号退出重登
                Collection<String> keys = redisCache.keys(CacheConstants.LOGIN_TOKEN_KEY + "*");
                if (CollectionUtil.isNotEmpty(keys)) {
                    for (String key : keys) {
                        LoginUser onlineUser = redisCache.getCacheObject(key);
                        if (onlineUser.getUser().getTenantId() != null
                                && onlineUser.getUser().getTenantId().equals(sysTenantEntity.getTenantId())) {
                            redisCache.deleteObject(key);
                        }
                    }
                }
            }
        }
        sysTenantMapper.updateById(sysTenantEntity);
        return R.ok();
    }

    @Override
    public List<SysTenantEntity> queryByPackage(String packageId) {
        LambdaQueryWrapper<SysTenantEntity> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(SysTenantEntity::getTenantId, packageId);
        return this.baseMapper.selectList(queryWrapper);
    }

    public void createDomainAndDns(String tenentKey) {
        Credential cred = new Credential("AKIDxypW8arxqY5ThleNyTtAFUYWM0ShnjSC", "kMo3RhnjV6XowxqaNN9mCTbYP8iDVdWG");
        DnspodClient dnspodClient = new DnspodClient(cred, "");
        CreateRecordRequest req = new CreateRecordRequest();
        req.setDomain("julyharbor.com");
        req.setRecordType("A");
        req.setRecordLine("默认");
        req.setValue("122.51.20.105");
        req.setSubDomain(tenentKey);
        CreateRecordResponse createRecordResponse;
        try {
            createRecordResponse = dnspodClient.CreateRecord(req);
        } catch (TencentCloudSDKException e) {
            throw new RuntimeException(e);
        }
        log.info("腾讯云域名映射结果：{}", JSONObject.toJSONString(createRecordResponse));
    }

    @Override
    public void initRole(SysTenantEntity sysTenantEntity) {

        SysRoleEntity operator = new SysRoleEntity();
        operator.setRoleName("管理员");
        operator.setRoleKey("operator");
        operator.setDescription("管理员角色系统默认创建");
        operator.setDataScope("1");
        operator.setMenuCheckStrictly(true);
        operator.setDeptCheckStrictly(true);
        operator.setTenantId(sysTenantEntity.getTenantId());
        roleMapper.insert(operator);

        SysTenantPackageEntity sysTenantPackage = tenantPackageMapper.selectById(sysTenantEntity.getPackageId());
        List<String> subMeuns = new ArrayList<>(Arrays.asList(sysTenantPackage.getMenuIds().split(",")));
        if (CollectionUtil.isEmpty(subMeuns)) {
            subMeuns = new ArrayList<>();
        }
        subMeuns.remove("2089");
        subMeuns.remove("2088");
        subMeuns.remove("2093");
        subMeuns.remove("6");
        List<SysRoleMenuEntity> roleMenuEntityList = subMeuns.stream().map(menu -> {
            SysRoleMenuEntity rm = new SysRoleMenuEntity();
            rm.setRoleId(operator.getRoleId());
            rm.setMenuId(Long.valueOf(menu));
            return rm;
        }).collect(Collectors.toList());
        roleMenuService.saveBatch(roleMenuEntityList);

        SysRoleEntity waiter = new SysRoleEntity();
        waiter.setRoleName("客服");
        waiter.setRoleKey("waiter");
        waiter.setDataScope("1");
        waiter.setDescription("客服角色系统默认创建");
        waiter.setMenuCheckStrictly(true);
        waiter.setDeptCheckStrictly(true);
        waiter.setTenantId(sysTenantEntity.getTenantId());
        roleMapper.insert(waiter);

        List<Integer> waiterMenuIdList = Arrays.asList(102, 1100, 1101, 1103, 1104, 1105);
        List<SysRoleMenuEntity> waiterMenuList = waiterMenuIdList.stream().map(menu -> {
            SysRoleMenuEntity rm = new SysRoleMenuEntity();
            rm.setRoleId(waiter.getRoleId());
            rm.setMenuId(Long.valueOf(menu));
            return rm;
        }).collect(Collectors.toList());
        roleMenuService.saveBatch(waiterMenuList);

        SysRoleEntity leader = new SysRoleEntity();
        leader.setRoleName("组长");
        leader.setRoleKey("leader");
        leader.setDescription("组长角色系统默认创建");
        leader.setDataScope("3");
        leader.setMenuCheckStrictly(true);
        leader.setDeptCheckStrictly(true);
        leader.setTenantId(sysTenantEntity.getTenantId());
        roleMapper.insert(leader);

        List<Integer> leaderMenuIdList = Arrays.asList(101, 102, 1000, 2095, 2096, 2097, 2087, 1001, 1100, 1101, 105,
                2094);
        List<SysRoleMenuEntity> leaderMenuList = leaderMenuIdList.stream().map(menu -> {
            SysRoleMenuEntity rm = new SysRoleMenuEntity();
            rm.setRoleId(leader.getRoleId());
            rm.setMenuId(Long.valueOf(menu));
            return rm;
        }).collect(Collectors.toList());
        roleMenuService.saveBatch(leaderMenuList);

    }
}


--- File: play-admin/src/main/java/com/starry/admin/modules/system/service/impl/SysUserRoleServiceImpl.java ---
package com.starry.admin.modules.system.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.starry.admin.modules.system.mapper.SysUserRoleMapper;
import com.starry.admin.modules.system.module.entity.SysUserRoleEntity;
import com.starry.admin.modules.system.service.SysUserRoleService;
import com.starry.admin.modules.system.service.SysUserService;
import javax.annotation.Resource;
import org.springframework.stereotype.Service;

/**
 * <p>
 * 用户角色关联表 服务实现类
 * </p>
 *
 * @author admin
 * @since 2022-07-08
 */
@Service
public class SysUserRoleServiceImpl extends ServiceImpl<SysUserRoleMapper, SysUserRoleEntity>
        implements
            SysUserRoleService {

    @Resource
    private SysUserService userService;

    @Override
    public void delUserRole(String userId, Long roleId) {
        LambdaQueryWrapper<SysUserRoleEntity> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(SysUserRoleEntity::getUserId, userId);
        queryWrapper.eq(SysUserRoleEntity::getRoleId, roleId);
        this.remove(queryWrapper);
        // 强制退出指定用户
        userService.logoutUser(userId);
    }

    @Override
    public void addUserRole(String userId, Long roleId) {
        LambdaQueryWrapper<SysUserRoleEntity> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(SysUserRoleEntity::getUserId, userId);
        queryWrapper.eq(SysUserRoleEntity::getRoleId, roleId);
        SysUserRoleEntity userRole = this.baseMapper.selectOne(queryWrapper);
        if (userRole == null) {
            userRole = new SysUserRoleEntity();
            userRole.setUserId(userId);
            userRole.setRoleId(roleId);
            this.baseMapper.insert(userRole);
        } else {
            userRole.setUserId(userId);
            userRole.setRoleId(roleId);
            this.baseMapper.updateById(userRole);
        }
    }
}


--- File: play-admin/src/main/java/com/starry/admin/modules/system/service/impl/SysUserServiceImpl.java ---
package com.starry.admin.modules.system.service.impl;

import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.github.yulichang.wrapper.MPJLambdaWrapper;
import com.starry.admin.manager.AsyncManager;
import com.starry.admin.manager.factory.AsyncFactory;
import com.starry.admin.modules.clerk.module.entity.PlayClerkUserInfoEntity;
import com.starry.admin.modules.clerk.service.IPlayClerkUserInfoService;
import com.starry.admin.modules.personnel.module.entity.PlayPersonnelAdminInfoEntity;
import com.starry.admin.modules.personnel.module.entity.PlayPersonnelGroupInfoEntity;
import com.starry.admin.modules.personnel.module.entity.PlayPersonnelWaiterInfoEntity;
import com.starry.admin.modules.personnel.module.vo.PlayPersonnelUserInfoQueryVo;
import com.starry.admin.modules.personnel.module.vo.PlayPersonnelUserInfoReturnVo;
import com.starry.admin.modules.personnel.service.IPlayPersonnelAdminInfoService;
import com.starry.admin.modules.personnel.service.IPlayPersonnelGroupInfoService;
import com.starry.admin.modules.personnel.service.IPlayPersonnelWaiterInfoService;
import com.starry.admin.modules.system.mapper.SysRoleMapper;
import com.starry.admin.modules.system.mapper.SysUserMapper;
import com.starry.admin.modules.system.module.entity.SysRoleEntity;
import com.starry.admin.modules.system.module.entity.SysUserEntity;
import com.starry.admin.modules.system.module.entity.SysUserRoleEntity;
import com.starry.admin.modules.system.service.SysUserRoleService;
import com.starry.admin.modules.system.service.SysUserService;
import com.starry.admin.modules.system.vo.*;
import com.starry.admin.utils.SecurityUtils;
import com.starry.admin.utils.SmsUtils;
import com.starry.common.annotation.DataScope;
import com.starry.common.constant.Constants;
import com.starry.common.constant.UserConstants;
import com.starry.common.redis.RedisCache;
import com.starry.common.utils.IdUtils;
import com.starry.common.utils.StringUtils;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;
import javax.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

/**
 * <p>
 * 后台用户表 服务实现类
 * </p>
 *
 * @author admin
 * @since 2021-09-03
 */
@Slf4j
@Service
public class SysUserServiceImpl extends ServiceImpl<SysUserMapper, SysUserEntity> implements SysUserService {

    @Resource
    private PasswordEncoder passwordEncoder;
    @Resource
    private SysUserRoleService userRoleService;
    @Resource
    private SysRoleMapper sysRoleMapper;
    @Resource
    private IPlayClerkUserInfoService clerkUserInfoService;
    @Resource
    private IPlayPersonnelAdminInfoService playPersonnelAdminInfoService;
    @Resource
    private IPlayPersonnelGroupInfoService playClerkGroupInfoService;
    @Resource
    private IPlayPersonnelWaiterInfoService playClerkWaiterInfoService;
    @Resource
    private RedisCache redisCache;
    @Resource
    private SmsUtils smsUtils;

    @Override
    public List<SysUserEntity> selectAll() {
        LambdaQueryWrapper<SysUserEntity> lambdaWrapper = new LambdaQueryWrapper<>();
        lambdaWrapper.select(SysUserEntity::getUserId, SysUserEntity::getUserCode);
        return this.baseMapper.selectList(lambdaWrapper);

    }

    @Override
    public IPage<PlayPersonnelUserInfoReturnVo> selectByPage(PlayPersonnelUserInfoQueryVo vo) {
        MPJLambdaWrapper<SysUserEntity> lambdaWrapper = new MPJLambdaWrapper<>();
        lambdaWrapper.selectAll(SysUserEntity.class);
        // 店员表
        lambdaWrapper.selectAs(PlayClerkUserInfoEntity::getId, "clerkId")
                .selectAs(PlayClerkUserInfoEntity::getAvatar, "clerkAvatar")
                .selectAs(PlayClerkUserInfoEntity::getNickname, "clerkNickname");
        lambdaWrapper.leftJoin(PlayClerkUserInfoEntity.class, PlayClerkUserInfoEntity::getId, SysUserEntity::getUserId);
        if (StrUtil.isNotBlank(vo.getSysUserId())) {
            lambdaWrapper.eq(SysUserEntity::getUserId, vo.getSysUserId());
        }
        if (StrUtil.isNotBlank(vo.getUserCode())) {
            lambdaWrapper.eq(SysUserEntity::getUserCode, vo.getUserCode());
        }
        if (StrUtil.isNotBlank(vo.getRuleType())) {
            if ("0".equals(vo.getRuleType())) {
                lambdaWrapper.leftJoin(PlayClerkUserInfoEntity.class, PlayClerkUserInfoEntity::getSysUserId,
                        SysUserEntity::getUserId);
            } else if ("1".equals(vo.getRuleType())) {
                lambdaWrapper.leftJoin(PlayPersonnelWaiterInfoEntity.class, PlayPersonnelWaiterInfoEntity::getSysUserId,
                        SysUserEntity::getUserId);
            } else if ("2".equals(vo.getRuleType())) {
                lambdaWrapper.leftJoin(PlayPersonnelGroupInfoEntity.class, PlayPersonnelGroupInfoEntity::getSysUserId,
                        SysUserEntity::getUserId);
            }
        }
        if (vo.getAddTime() != null && vo.getAddTime().size() == 2) {
            lambdaWrapper.between(SysUserEntity::getAddTime, vo.getAddTime().get(0), vo.getAddTime().get(1));
        }

        return this.baseMapper.selectJoinPage(new Page<>(vo.getPageNum(), vo.getPageSize()),
                PlayPersonnelUserInfoReturnVo.class, lambdaWrapper);
    }

    @Override
    public SysUserEntity register(SysUserEntity user) {
        SysUserEntity newSysUserEntity = new SysUserEntity();
        BeanUtils.copyProperties(user, newSysUserEntity);
        // 查询是否有相同用户名的用户
        List<SysUserEntity> sysUserEntities = this.baseMapper.selectList(
                new LambdaQueryWrapper<SysUserEntity>().eq(SysUserEntity::getUserCode, newSysUserEntity.getUserCode()));
        if (CollectionUtil.isNotEmpty(sysUserEntities)) {
            return null;
        }
        // 将密码进行加密操作
        String encodePassword = passwordEncoder.encode(user.getPassWord());
        newSysUserEntity.setPassWord(encodePassword);
        this.baseMapper.insert(newSysUserEntity);
        return newSysUserEntity;
    }

    @Override
    public SysUserEntity getUserByCode(String userCode) {
        List<SysUserEntity> users = baseMapper
                .selectList(new LambdaQueryWrapper<SysUserEntity>().eq(SysUserEntity::getUserCode, userCode));
        if (CollectionUtil.isNotEmpty(users)) {
            return users.get(0);
        }
        return null;
    }

    @Override
    public SysUserEntity selectUserByUserName(String userName) {
        return baseMapper.selectUserByUserName(userName);
    }

    @Override
    public SysUserEntity selectUserByUserNameAndTenantId(String userName, String tenantId) {
        return baseMapper.selectUserByUserNameAndTenantId(userName, tenantId);
    }

    @Override
    public IPage<SysUserEntity> listMemberPage(UserQueryVo queryVo) {
        Page<SysUserEntity> page = new Page<>(queryVo.getPageNum(), queryVo.getPageSize());
        return baseMapper.selectUserList(page, queryVo);
    }

    @Override
    public String checkUserNameUnique(String userCode) {
        // 查询是否有相同用户名的用户
        List<SysUserEntity> userList = this.baseMapper
                .selectList(new LambdaQueryWrapper<SysUserEntity>().eq(SysUserEntity::getUserCode, userCode));
        if (CollectionUtil.isNotEmpty(userList)) {
            return UserConstants.NOT_UNIQUE;
        }
        return UserConstants.UNIQUE;
    }

    @Transactional(rollbackFor = Exception.class)
    @Override
    public boolean create(SysUserEntity user) {
        // 将密码进行加密操作
        String encodePassword = passwordEncoder.encode(user.getPassWord());
        user.setPassWord(encodePassword);
        if (StrUtil.isBlankIfStr(user.getUserId())) {
            user.setUserId(IdUtils.getUuid());
        }
        boolean flag = save(user);
        if (flag) {
            // 新增用户角色信息
            flag = insertUserRole(user);
        }
        return flag;
    }

    @Transactional(rollbackFor = Exception.class)
    @Override
    public boolean update(SysUserEntity user) {
        boolean flag = updateById(user);
        if (flag) {
            // 删除用户角色信息
            userRoleService.remove(
                    new LambdaQueryWrapper<SysUserRoleEntity>().eq(SysUserRoleEntity::getUserId, user.getUserId()));
            // 新增用户角色信息
            flag = insertUserRole(user);
        }
        return flag;

    }

    @Transactional(rollbackFor = Exception.class)
    @Override
    public boolean delete(List<String> ids) {
        if (CollectionUtil.isNotEmpty(ids)) {
            boolean flag = this.removeBatchByIds(ids);
            if (flag) {
                // 删除用户角色表
                userRoleService
                        .remove(new LambdaQueryWrapper<SysUserRoleEntity>().in(SysUserRoleEntity::getUserId, ids));
            }
        }
        return true;
    }

    @Override
    public SysUserEntity selectUserById(String userId) {
        if (StringUtils.isEmpty(userId)) {
            userId = SecurityUtils.getUserId();
        }
        baseMapper.selectById(userId);
        SysUserEntity data = baseMapper.selectUserById(userId);
        if (Objects.isNull(data)) {
            throw new RuntimeException("未查询到指定用户或该用户状态异常");
        }
        return data;
    }

    @Override
    public String selectUserRoleGroup(String userId) {
        List<SysRoleEntity> list = sysRoleMapper.selectRolePermissionByUserId(userId);
        if (CollectionUtil.isEmpty(list)) {
            return StringUtils.EMPTY;
        }
        return list.stream().map(SysRoleEntity::getRoleName).collect(Collectors.joining(","));
    }

    @Override
    public boolean updateUserAvatar(String userId, String avatar) {
        SysUserEntity user = getById(userId);
        if (user != null) {
            user.setAvatar(avatar);
            return updateById(user);
        }
        return false;
    }

    /**
     * 新增用户角色信息
     *
     * @param user
     *            用户对象
     */
    public boolean insertUserRole(SysUserEntity user) {
        if (user.getRoleIds() != null && user.getRoleIds().length > 0) {
            // 新增用户与角色管理
            List<SysUserRoleEntity> list = new ArrayList<>(user.getRoleIds().length);
            for (Long roleId : user.getRoleIds()) {
                SysUserRoleEntity ur = new SysUserRoleEntity();
                ur.setUserId(user.getUserId());
                ur.setRoleId(roleId);
                list.add(ur);
            }
            return userRoleService.saveBatch(list);
        }
        return true;
    }

    /**
     * 根据条件分页查询已分配用户角色列表
     *
     * @param userQueryVo
     *            用户信息
     * @return 用户信息集合信息
     */
    @Override
    @DataScope(deptAlias = "d", userAlias = "u")
    public IPage<RoleUserResultVo> selectAllocatedList(SysUserQueryVo userQueryVo) {
        return baseMapper.selectAllocatedList(new Page<>(userQueryVo.getPageNum(), userQueryVo.getPageSize()),
                userQueryVo);
    }

    /**
     * 根据条件分页查询未分配用户角色列表
     *
     * @param user
     *            用户信息
     * @return 用户信息集合信息
     */
    @Override
    @DataScope(deptAlias = "d", userAlias = "u")
    public IPage<RoleUserResultVo> selectUnallocatedList(SysUserQueryVo user) {
        return baseMapper.selectUnallocatedList(new Page<>(user.getPageNum(), user.getPageSize()), user);
    }

    @Override
    public boolean addV1(UserAddParam param) {
        long count = this
                .count(Wrappers.lambdaQuery(SysUserEntity.class).eq(SysUserEntity::getClerkId, param.getClerkId()));
        if (count > 0) {
            throw new RuntimeException("该店员已经成为员工了，无法再次添加~");
        }
        smsUtils.checkSmsCode(param.getMobile(), param.getSmsCode());
        PlayClerkUserInfoEntity clerk = clerkUserInfoService.selectById(param.getClerkId());
        SysUserEntity user = new SysUserEntity().setClerkId(param.getClerkId()).setMobile(param.getMobile())
                .setPassWord(SecurityUtils.encryptPassword(param.getMobile())).setUserCode(param.getMobile())
                .setUserNickname(clerk.getNickname()).setRealName(clerk.getNickname());
        user.setUserId(IdUtils.getUuid());
        user.setSuperAdmin(false);
        this.save(user);
        clerkUserInfoService.update(null,
                Wrappers.lambdaUpdate(PlayClerkUserInfoEntity.class)
                        .eq(PlayClerkUserInfoEntity::getId, param.getClerkId())
                        .set(PlayClerkUserInfoEntity::getSysUserId, user.getUserId()));
        return true;
    }

    @Override
    public List<SysUserEntityListVo> generatePageData(List<SysUserEntity> records) {
        return records.stream().map(ca -> {
            SysUserEntityListVo vo = new SysUserEntityListVo();
            BeanUtils.copyProperties(ca, vo);
            if (StringUtils.isNotEmpty(ca.getClerkId())) {
                vo.setClerkUserInfo(clerkUserInfoService.selectById(ca.getClerkId()));
            }
            // 查询角色
            List<SysUserEntityListVo.RoleVo> roleList = new ArrayList<>();
            PlayPersonnelAdminInfoEntity adminInfoEntity = playPersonnelAdminInfoService.selectByUserId(ca.getUserId());
            if (Objects.nonNull(adminInfoEntity)) {
                roleList.add(new SysUserEntityListVo.RoleVo("管理员", adminInfoEntity.getAdminName()));
            }
            PlayPersonnelGroupInfoEntity groupInfoEntity = playClerkGroupInfoService.selectByUserId(ca.getUserId());
            if (Objects.nonNull(groupInfoEntity)) {
                roleList.add(new SysUserEntityListVo.RoleVo("组长", groupInfoEntity.getLeaderName()));
            }
            PlayPersonnelWaiterInfoEntity waiterInfoEntity = playClerkWaiterInfoService.selectByUserId(ca.getUserId());
            if (Objects.nonNull(waiterInfoEntity)) {
                roleList.add(new SysUserEntityListVo.RoleVo("客服", waiterInfoEntity.getWaiterName()));
            }

            vo.setRoleVoList(roleList);
            return vo;
        }).collect(Collectors.toList());
    }

    @Override
    public void updatePassword(UpdateUserPasswordParam param) {
        selectUserById(param.getUserId());
        this.update(Wrappers.lambdaUpdate(SysUserEntity.class).eq(SysUserEntity::getUserId, param.getUserId())
                .set(SysUserEntity::getPassWord, SecurityUtils.encryptPassword(param.getPassword())));
    }

    @Override
    public void updateMobile(UpdateUserMobileParam param) {
        SysUserEntity user = selectUserById(param.getUserId());
        if (user.getMobile().equals(param.getMobile())) {
            throw new RuntimeException("新旧手机号不能相同~");
        }
        // 查询是否已有相同手机号
        long count = this.count(Wrappers.lambdaQuery(SysUserEntity.class)
                .eq(SysUserEntity::getMobile, param.getMobile()).ne(SysUserEntity::getUserId, param.getUserId()));
        if (count > 0) {
            throw new RuntimeException("系统中已有该手机号，无法录入~");
        }
        this.update(Wrappers.lambdaUpdate(SysUserEntity.class).eq(SysUserEntity::getUserId, param.getUserId())
                .set(SysUserEntity::getUserCode, param.getMobile()).set(SysUserEntity::getMobile, param.getMobile()));
    }

    @Override
    public void logoutUser(String uid) {
        String key = "login:resource:" + uid;
        Object object = redisCache.getCacheObject(key);
        if (Objects.isNull(object))
            return;

        String key2 = object.toString();
        redisCache.deleteObject(key2);
    }

    @Override
    public void updatePasswordV2(UpdateUserPasswordParamV2 param) {
        String userId = SecurityUtils.getUserId();
        SysUserEntity user = selectUserById(userId);
        if (!passwordEncoder.matches(param.getOldPassword(), user.getPassWord())) {
            throw new BadCredentialsException("旧密码不正确");
        }
        this.update(Wrappers.lambdaUpdate(SysUserEntity.class).eq(SysUserEntity::getUserId, userId)
                .set(SysUserEntity::getPassWord, SecurityUtils.encryptPassword(param.getNewPassword())));
        AsyncManager.me().execute(AsyncFactory.recordLoginLog(user.getUserCode(), Constants.LOGIN_SUCCESS, "修改密码"));
        logoutUser(userId);
    }
}


--- File: play-admin/src/main/java/com/starry/admin/modules/system/service/impl/UserOnlineServiceImpl.java ---
package com.starry.admin.modules.system.service.impl;

import com.starry.admin.common.domain.LoginUser;
import com.starry.admin.modules.system.module.entity.UserOnline;
import com.starry.admin.modules.system.service.UserOnlineService;
import com.starry.common.utils.StringUtils;
import org.springframework.stereotype.Service;

/**
 * @author admin
 * @since 2022/10/18
 */
@Service
public class UserOnlineServiceImpl implements UserOnlineService {

    @Override
    public UserOnline selectOnlineByInfo(String ipaddr, String userName, LoginUser user) {
        if (StringUtils.equals(ipaddr, user.getIpaddr()) && StringUtils.equals(userName, user.getUsername())) {
            return setUserOnline(user);
        }
        return null;
    }

    @Override
    public UserOnline selectOnlineByIpaddr(String ipaddr, LoginUser user) {
        if (StringUtils.equals(ipaddr, user.getIpaddr())) {
            return setUserOnline(user);
        }
        return null;
    }

    @Override
    public UserOnline selectOnlineByUserName(String userName, LoginUser user) {
        if (StringUtils.equals(userName, user.getUsername())) {
            return setUserOnline(user);
        }
        return null;
    }

    @Override
    public UserOnline setUserOnline(LoginUser user) {
        if (StringUtils.isNull(user) || StringUtils.isNull(user.getUser())) {
            return null;
        }
        UserOnline userOnline = new UserOnline();
        userOnline.setTokenId(user.getToken());
        userOnline.setUserName(user.getUsername());
        userOnline.setIpaddr(user.getIpaddr());
        userOnline.setLoginLocation(user.getLoginLocation());
        userOnline.setBrowser(user.getBrowser());
        userOnline.setOs(user.getOs());
        userOnline.setLoginTime(user.getLoginTime());
        if (StringUtils.isNotNull(user.getUser().getDept())) {
            userOnline.setDeptName(user.getUser().getDept().getDeptName());
        }
        return userOnline;
    }
}


--- File: play-admin/src/main/java/com/starry/admin/modules/system/vo/AdministrativeAreaQueryReturnVo.java ---
package com.starry.admin.modules.system.vo;

import java.util.ArrayList;
import java.util.List;
import lombok.Data;

/**
 * 行政区域信息集合 play_administrative_area_dict_info
 *
 * @author admin
 * @since 2024-04-03
 */
@Data
public class AdministrativeAreaQueryReturnVo {

    /**
     * 编码
     */
    private String code;

    /**
     * 名称
     */
    private String name;

    /**
     * 区域集合
     **/
    private List<AdministrativeAreaQueryReturnVo> child = new ArrayList<>();

}


--- File: play-admin/src/main/java/com/starry/admin/modules/system/vo/LoginVo.java ---
package com.starry.admin.modules.system.vo;

import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import java.io.Serializable;
import lombok.Data;

/**
 * @author huoqiang
 * @since 2021/9/6
 */
@Data
@ApiModel(value = "登录参数", description = "用户登录的请求参数")
public class LoginVo implements Serializable {

    @ApiModelProperty(value = "用户名", required = true, example = "admin", notes = "登录账号")
    private String userName;

    @ApiModelProperty(value = "密码", required = true, example = "123456", notes = "登录密码")
    private String passWord;

    @ApiModelProperty(value = "验证码随机字符串", example = "abc123", notes = "验证码请求时返回的唯一标识")
    private String nonceStr;

    @ApiModelProperty(value = "验证码", example = "1234", notes = "用户输入的验证码")
    private String value;

    @ApiModelProperty(value = "租户标识", example = "tenant1", notes = "多租户模式下的租户唯一标识")
    private String tenantKey;
}


--- File: play-admin/src/main/java/com/starry/admin/modules/system/vo/MemberQueryVo.java ---
package com.starry.admin.modules.system.vo;

import com.starry.common.domain.BasePageEntity;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import lombok.EqualsAndHashCode;

/**
 * @author admin
 * @since 2021/9/2
 */
@EqualsAndHashCode(callSuper = true)
@Data
public class MemberQueryVo extends BasePageEntity {

    @ApiModelProperty(value = "用户名")
    private String userName;
}


--- File: play-admin/src/main/java/com/starry/admin/modules/system/vo/MetaVo.java ---
package com.starry.admin.modules.system.vo;

import com.starry.common.constant.Constants;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import org.apache.commons.lang3.StringUtils;

/**
 * @author admin 路由显示信息
 * @since 2022/7/6
 */
@Data
@ApiModel(value = "路由元数据", description = "路由显示信息配置")
public class MetaVo {

    /**
     * 设置该路由在侧边栏和面包屑中展示的名字
     */
    @ApiModelProperty(value = "路由标题", example = "系统管理", notes = "设置该路由在侧边栏和面包屑中展示的名字")
    private String title;

    /**
     * 设置该路由的图标，对应路径src/assets/icons/svg
     */
    @ApiModelProperty(value = "路由图标", example = "system", notes = "设置该路由的图标，对应路径src/assets/icons/svg")
    private String icon;

    /**
     * 设置为true，则不会被 <keep-alive>缓存
     */
    @ApiModelProperty(value = "是否缓存", example = "false", notes = "设置为true，则不会被<keep-alive>缓存")
    private boolean noCache;

    /**
     * 内链地址（http(s)://开头）
     */
    @ApiModelProperty(value = "内链地址", example = "https://www.example.com", notes = "内链地址（http(s)://开头）")
    private String link;

    public MetaVo(String title, String icon) {
        this.title = title;
        this.icon = icon;
    }

    public MetaVo(String title, String icon, String link) {
        this.title = title;
        this.icon = icon;
        this.link = link;
    }

    public MetaVo(String title, String icon, boolean noCache, String link) {
        this.title = title;
        this.icon = icon;
        this.noCache = noCache;
        if (StringUtils.startsWithAny(link, Constants.HTTP, Constants.HTTPS)) {
            this.link = link;
        }
    }
}


--- File: play-admin/src/main/java/com/starry/admin/modules/system/vo/RoleUserResultVo.java ---
package com.starry.admin.modules.system.vo;

import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import java.util.Date;
import lombok.Data;
import org.springframework.format.annotation.DateTimeFormat;

/**
 * @author admin 角色分配用户精简信息Vo
 * @since 2023/3/10
 */
@ApiModel("管理后台 - 角色分配用户返回信息")
@Data
public class RoleUserResultVo {

    @ApiModelProperty(value = "用户ID")
    private Long userId;

    @ApiModelProperty(value = "用户类型;1:admin;2:会员")
    private Integer userType;

    @ApiModelProperty(value = "用户名")
    private String userCode;

    @ApiModelProperty(value = "姓名")
    private String realName;

    @ApiModelProperty(value = "用户昵称")
    private String userNickname;

    @ApiModelProperty(value = "邮箱")
    private String userEmail;

    @ApiModelProperty(value = "中国手机不带国家代码，国际手机号格式为：国家代码-手机号")
    private String mobile;

    @ApiModelProperty(value = "用户状态;0正常 1停用")
    private Integer status;

    @ApiModelProperty(value = "所属部门id")
    private Long deptId;

    @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    @ApiModelProperty(value = "创建时间")
    private Date createdTime;
}


--- File: play-admin/src/main/java/com/starry/admin/modules/system/vo/RouterVo.java ---
package com.starry.admin.modules.system.vo;

import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import java.util.List;
import lombok.Data;

/**
 * @author admin 路由配置信息
 * @since 2022/7/6
 */
@Data
@ApiModel(value = "路由信息", description = "前端路由配置信息")
public class RouterVo {

    @ApiModelProperty(value = "路由名字", example = "System", notes = "路由的唯一标识名称")
    private String name;

    @ApiModelProperty(value = "路由地址", example = "/system", notes = "URL路径")
    private String path;

    @ApiModelProperty(value = "是否隐藏路由", example = "false", notes = "当设置为true时该路由不会在侧边栏出现")
    private boolean hidden;

    @ApiModelProperty(value = "重定向地址", example = "noRedirect", notes = "当设置noRedirect时该路由在面包屑导航中不可被点击")
    private String redirect;

    @ApiModelProperty(value = "组件地址", example = "Layout", notes = "对应的前端组件路径")
    private String component;

    /**
     * 路由参数：如 {"id": 1, "name": "ry"}
     */
    @ApiModelProperty(value = "路由参数", example = "{\"id\": 1, \"name\": \"admin\"}", notes = "路由参数，JSON格式")
    private String routerQuery;

    @ApiModelProperty(value = "是否总是显示", example = "true", notes = "当一个路由下面的children声明的路由大于1个时，自动会变成嵌套的模式")
    private Boolean alwaysShow;

    @ApiModelProperty(value = "路由元信息", notes = "路由的其他配置信息")
    private MetaVo meta;

    @ApiModelProperty(value = "子路由", notes = "子路由配置信息")
    private List<RouterVo> children;

}


--- File: play-admin/src/main/java/com/starry/admin/modules/system/vo/SimpleMenu.java ---
package com.starry.admin.modules.system.vo;

import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import javax.validation.constraints.NotNull;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

/**
 * @author admin
 */
@ApiModel("管理后台 - 菜单精简信息")
@Data
@NoArgsConstructor
@AllArgsConstructor
public class SimpleMenu {

    @ApiModelProperty(value = "菜单编号", required = true, example = "1024")
    private Long id;

    @ApiModelProperty(value = "菜单名称", required = true, example = "菜单管理")
    private String name;

    @ApiModelProperty(value = "父菜单 ID", required = true, example = "1024")
    private Long parentId;

    @ApiModelProperty(value = "类型", required = true, example = "1", notes = "参见 MenuTypeEnum 枚举类")
    @NotNull(message = "菜单类型不能为空")
    private Integer type;

}


--- File: play-admin/src/main/java/com/starry/admin/modules/system/vo/SimplePackage.java ---
package com.starry.admin.modules.system.vo;

import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;

/**
 * @author admin
 * @since 2023/3/5 20:53
 */
@ApiModel("管理后台 - 租户套餐精简信息")
@Data
public class SimplePackage {

    @ApiModelProperty(value = "套餐编号", required = true, example = "1024")
    private String id;

    @ApiModelProperty(value = "套餐名称", required = true, example = "希留")
    private String name;
}


--- File: play-admin/src/main/java/com/starry/admin/modules/system/vo/StatusConverter.java ---
package com.starry.admin.modules.system.vo;

import com.alibaba.excel.converters.Converter;
import com.alibaba.excel.enums.CellDataTypeEnum;
import com.alibaba.excel.metadata.CellData;
import com.alibaba.excel.metadata.GlobalConfiguration;
import com.alibaba.excel.metadata.property.ExcelContentProperty;

/**
 * @author huoqiang 状态字符串处理
 * @since 2022/10/25
 */
public class StatusConverter implements Converter<Integer> {

    @Override
    public Class supportJavaTypeKey() {
        return Integer.class;
    }

    @Override
    public CellDataTypeEnum supportExcelTypeKey() {
        return CellDataTypeEnum.STRING;
    }

    @Override
    public Integer convertToJavaData(CellData cellData, ExcelContentProperty excelContentProperty,
            GlobalConfiguration globalConfiguration) throws Exception {
        return "失败".equals(cellData.getStringValue()) ? 1 : 0;
    }

    @Override
    public CellData convertToExcelData(Integer value, ExcelContentProperty excelContentProperty,
            GlobalConfiguration globalConfiguration) throws Exception {
        return new CellData(0 == value ? "成功" : "失败");
    }
}


--- File: play-admin/src/main/java/com/starry/admin/modules/system/vo/SysDictDataVo.java ---
package com.starry.admin.modules.system.vo;

import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.starry.common.domain.BasePageEntity;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import lombok.EqualsAndHashCode;

/**
 * 字典数据表对象 sys_dict_data
 *
 * @author admin
 */
@Data
@EqualsAndHashCode(callSuper = true)

public class SysDictDataVo extends BasePageEntity {

    private static final long serialVersionUID = 1L;

    /**
     * 主键ID
     */
    @ApiModelProperty("主键ID")
    @TableId(value = "dict_data_id", type = IdType.AUTO)
    private Long dictDataId;

    /**
     * 字典类型
     */
    private String dictType;

    /**
     * 字典名称
     */
    private String dictLabel;

    /**
     * 字典值
     */
    private String dictValue;

    /**
     * 显示顺序
     */
    private Long sort;

    /**
     * 是否默认（Y是 N否）
     */
    private String isDefault;

    /**
     * 表格回显样式
     */
    private String listClass;

    /**
     * 状态（0正常 1停用）
     */
    private Integer status;

    /**
     * 备注
     */
    private String remark;

}


--- File: play-admin/src/main/java/com/starry/admin/modules/system/vo/SysDictVo.java ---
package com.starry.admin.modules.system.vo;

import com.starry.common.domain.BasePageEntity;
import lombok.Data;
import lombok.EqualsAndHashCode;

/**
 * @author admin
 * @since 2024/3/15 13:58
 **/
@EqualsAndHashCode(callSuper = true)
@Data
public class SysDictVo extends BasePageEntity {

    /**
     * 主键ID
     */
    private Long dictId;

    /**
     * 字典类型
     */
    private String dictType;

    /**
     * 字典名称
     */
    private String dictName;

    /**
     * 状态（0正常 1停用）
     */
    private Integer status;

    /**
     * 备注
     */
    private String remark;

}


--- File: play-admin/src/main/java/com/starry/admin/modules/system/vo/SysOperationLogVo.java ---
package com.starry.admin.modules.system.vo;

import com.alibaba.excel.annotation.ExcelProperty;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.starry.common.domain.BasePageEntity;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import java.util.Date;
import lombok.Data;
import lombok.EqualsAndHashCode;

/**
 * 操作日志表对象 sys_operation_log
 *
 * @author admin
 */
@EqualsAndHashCode(callSuper = true)
@Data
@ApiModel(value = "操作日志查询参数", description = "系统操作日志查询条件")
public class SysOperationLogVo extends BasePageEntity {

    private static final long serialVersionUID = 1L;

    /**
     * 主键ID
     */
    @ApiModelProperty(value = "日志ID", example = "1", notes = "主键ID")
    @TableId(value = "oper_id", type = IdType.AUTO)
    private Long operId;

    /**
     * 模块标题
     */
    @ApiModelProperty(value = "模块标题", example = "用户管理", notes = "操作的系统模块")
    @ExcelProperty(value = "系统模块")
    private String title;

    /**
     * 业务类型（0其它 1新增 2修改 3删除）
     */
    @ApiModelProperty(value = "业务类型", example = "1", notes = "0:其它 1:新增 2:修改 3:删除")
    private Integer businessType;

    /**
     * 业务类型数组
     */
    @ApiModelProperty(value = "业务类型数组", notes = "多个业务类型的集合")
    @TableField(exist = false)
    private Integer[] businessTypes;

    /**
     * 方法名称
     */
    @ApiModelProperty(value = "方法名称", example = "com.starry.admin.modules.system.controller.SysUserController.create", notes = "调用的方法名")
    @ExcelProperty(value = "方法名称")
    private String method;

    /**
     * 请求方式
     */
    @ApiModelProperty(value = "请求方式", example = "POST", notes = "HTTP请求方式")
    private String requestMethod;

    /**
     * 操作类别（0其它 1后台用户 2手机端用户）
     */
    @ApiModelProperty(value = "操作类别", example = "1", notes = "0:其它 1:后台用户 2:手机端用户")
    private Integer operatorType;

    /**
     * 操作人员
     */
    @ApiModelProperty(value = "操作人员", example = "admin", notes = "执行操作的用户账号")
    private String operName;

    /**
     * 请求URL
     */
    @ApiModelProperty(value = "请求URL", example = "/system/user/create", notes = "请求的接口地址")
    private String operUrl;

    /**
     * 主机地址
     */
    @ApiModelProperty(value = "主机地址", example = "127.0.0.1", notes = "请求的IP地址")
    private String operIp;

    /**
     * 操作地点
     */
    @ApiModelProperty(value = "操作地点", example = "北京", notes = "根据IP解析的地理位置")
    private String operLocation;

    /**
     * 请求参数
     */
    @ApiModelProperty(value = "请求参数", notes = "请求的参数内容")
    private String operParam;

    /**
     * 返回参数
     */
    @ApiModelProperty(value = "返回参数", notes = "接口返回的结果")
    private String jsonResult;

    /**
     * 操作状态（0正常 1异常）
     */
    @ApiModelProperty(value = "操作状态", example = "0", notes = "0:正常 1:异常")
    private Integer status;

    /**
     * 错误消息
     */
    @ApiModelProperty(value = "错误消息", example = "权限不足", notes = "操作失败时的错误信息")
    private String errorMsg;

    /**
     * 操作时间
     */
    @ApiModelProperty(value = "操作时间", example = "2023-01-01 00:00:00", notes = "操作的时间")
    private Date operTime;

    /**
     * 部门名称
     */
    @ApiModelProperty(value = "部门名称", example = "研发部", notes = "操作人员所属部门")
    private String deptName;

    /**
     * 部门id
     */
    @ApiModelProperty(value = "部门ID", example = "1", notes = "操作人员所属部门ID")
    private Long deptId;

    /**
     * 租户ID
     */
    @ApiModelProperty(value = "租户ID", example = "1", notes = "所属租户ID")
    private Long tenantId;
}


--- File: play-admin/src/main/java/com/starry/admin/modules/system/vo/SysRoleAddVo.java ---
package com.starry.admin.modules.system.vo;

import com.starry.admin.modules.system.module.entity.SysRoleEntity;
import com.starry.common.domain.BaseEntity;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import java.util.Set;
import javax.validation.constraints.NotBlank;
import lombok.Data;
import lombok.EqualsAndHashCode;

/**
 * @author admin
 */
@EqualsAndHashCode(callSuper = true)
@Data
@ApiModel(value = "角色添加参数", description = "新增系统角色的请求参数")
public class SysRoleAddVo extends BaseEntity<SysRoleEntity> {

    private static final long serialVersionUID = 1L;

    @ApiModelProperty(value = "角色ID", hidden = true, notes = "新增时无需填写，系统自动生成")
    private Long roleId;

    @NotBlank(message = "角色名称不能为空")
    @ApiModelProperty(value = "角色名称", required = true, example = "管理员", notes = "角色显示名称")
    private String roleName;

    @ApiModelProperty(value = "角色描述", example = "系统管理员", notes = "角色的详细描述")
    private String description;

    @ApiModelProperty(value = "角色状态", example = "0", notes = "0-正常，1-停用")
    private Integer status;

    @ApiModelProperty(value = "数据权限范围", example = "1", notes = "1:全部数据权限 2:自定数据权限 3:本部门数据权限 4:本部门及以下数据权限 5:仅本人数据权限")
    private String dataScope;

    @ApiModelProperty(value = "菜单树选择项是否关联显示", example = "true")
    private boolean menuCheckStrictly;

    @ApiModelProperty(value = "部门树选择项是否关联显示", example = "true")
    private boolean deptCheckStrictly;

    @ApiModelProperty(value = "权限字符", required = true, example = "admin", notes = "角色权限字符串")
    private String roleKey;

    @ApiModelProperty(value = "租户ID", example = "1", notes = "角色所属租户ID")
    private Long tenantId;

    /**
     * 菜单组
     */
    @ApiModelProperty(value = "菜单ID数组", notes = "角色关联的菜单ID列表")
    private Long[] menuIds;

    /**
     * 部门组（数据权限）
     */
    @ApiModelProperty(value = "部门ID数组", notes = "角色关联的部门ID列表，用于数据权限")
    private Long[] deptIds;

    /**
     * 角色菜单权限
     */
    @ApiModelProperty(value = "角色权限集合", notes = "角色拥有的菜单权限列表")
    private Set<String> permissions;

}


--- File: play-admin/src/main/java/com/starry/admin/modules/system/vo/SysTenantAddVo.java ---
package com.starry.admin.modules.system.vo;

import com.starry.common.domain.BaseEntity;
import java.util.Date;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
import lombok.Data;
import lombok.EqualsAndHashCode;

/**
 * 租户表对象 sys_tenant
 *
 * @author admin
 */
@Data
@EqualsAndHashCode(callSuper = true)
public class SysTenantAddVo extends BaseEntity<SysTenantAddVo> {

    private static final long serialVersionUID = 1L;

    /**
     * 租户类型;0:公司;1:部门;2:个人;3:其他
     */
    @NotBlank(message = "租户类型不能为空")
    private String tenantType = "0";

    /**
     * 用户状态;0:正常,1:停用
     */
    @NotBlank(message = "租户状态不能为空")
    private String tenantStatus;

    /**
     * 名称
     */
    @NotBlank(message = "租户名称不能为空")
    private String tenantName;

    /**
     * logo图
     */
    private String tenantLogo;

    /**
     * 显示顺序
     */
    private String sortOrder;

    /**
     * 官方地址
     */
    private String homeUrl;

    /**
     * 电话
     */
    @NotBlank(message = "手机号码不能为空")
    private String phone;

    /**
     * 传真
     */
    private String fax;

    /**
     * 邮箱
     */
    private String email;

    /**
     * 地址
     */
    private String address;

    /**
     * 管理员账号
     */
    @NotBlank(message = "管理员账号不能为空")
    private String userName;

    /**
     * 管理员密码
     */
    @NotBlank(message = "管理员账号密码不能为空")
    private String userPwd;

    /**
     * 租户套餐
     */
    private String packageId;

    /**
     * 租赁到期时间
     */
    @NotNull(message = "租赁到期时间不能为空")
    private Date tenantTime;

    /**
     * 微信公众号ID
     */
    @NotBlank(message = "微信公众号ID不能为空")
    private String appId;

    /**
     * 微信公众号的app secret
     */
    @NotBlank(message = "微信公众号secret不能为空")
    private String secret;

    /**
     * 设置微信公众号的token
     */
    @NotBlank(message = "微信商户Key不能为空")
    private String mchKey;

    /**
     * 设置微信公众号的EncodingAESKey
     */
    @NotBlank(message = "微信商户秘钥不能为空")
    private String mchId;

    /**
     * 证书路径
     */
    private String certificatePath;

    /**
     * 备注
     */
    private String remarks;

}


--- File: play-admin/src/main/java/com/starry/admin/modules/system/vo/SysTenantPackageAddVo.java ---
package com.starry.admin.modules.system.vo;

import com.starry.common.domain.BaseEntity;
import javax.validation.constraints.NotBlank;
import lombok.Data;
import lombok.EqualsAndHashCode;

/**
 * 租户套餐表对象 sys_tenant_package
 *
 * @author admin
 */
@Data
@EqualsAndHashCode(callSuper = true)
public class SysTenantPackageAddVo extends BaseEntity<SysTenantPackageAddVo> {

    private static final long serialVersionUID = 1L;

    /**
     * 名称
     */
    @NotBlank(message = "套餐名称不能为空")
    private String packageName;

    /**
     * 关联菜单id
     */
    private String menuIds;

    /**
     * 状态;0:正常,1:停用
     */
    @NotBlank(message = "套餐状态不能为空")
    private String status;

    /**
     * 备注
     */
    private String remarks;

}


--- File: play-admin/src/main/java/com/starry/admin/modules/system/vo/SysTenantPackageVo.java ---
package com.starry.admin.modules.system.vo;

import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.starry.common.domain.BasePageEntity;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import lombok.EqualsAndHashCode;

/**
 * 租户套餐表对象 sys_tenant_package
 *
 * @author admin
 */
@Data
@EqualsAndHashCode(callSuper = true)
public class SysTenantPackageVo extends BasePageEntity {

    private static final long serialVersionUID = 1L;

    /**
     * 套餐id
     */
    @ApiModelProperty("主键ID")
    @TableId(value = "package_id", type = IdType.AUTO)
    private Long packageId;

    /**
     * 名称
     */
    private String packageName;

    /**
     * 关联菜单id
     */
    private String menuIds;

    /**
     * 状态;0:正常,1:停用
     */
    private String status;

    /**
     * 备注
     */
    private String remarks;
}


--- File: play-admin/src/main/java/com/starry/admin/modules/system/vo/SysTenantQueryVo.java ---
package com.starry.admin.modules.system.vo;

import com.starry.common.domain.BasePageEntity;
import lombok.Data;
import lombok.EqualsAndHashCode;

/**
 * 租户表对象 sys_tenant
 *
 * @author admin
 */
@EqualsAndHashCode(callSuper = true)
@Data
public class SysTenantQueryVo extends BasePageEntity {

    private static final long serialVersionUID = 1L;

    /**
     * 名称
     */
    private String tenantName;

    /**
     * 用户状态;0:正常,1:停用
     */
    private String tenantStatus;

}


--- File: play-admin/src/main/java/com/starry/admin/modules/system/vo/SysTenantReturnVo.java ---
package com.starry.admin.modules.system.vo;

import java.math.BigDecimal;
import java.util.Date;
import lombok.Data;

/**
 * 租户表对象 sys_tenant
 *
 * @author admin
 */
@Data
public class SysTenantReturnVo {

    private String tenantId;

    /**
     * 租户类型;0:公司;1:部门;2:个人;3:其他
     */
    private String tenantType;

    /**
     * 用户状态;0:正常,1:停用
     */
    private String tenantStatus;

    /**
     * 编码
     */
    private String tenantCode;

    /**
     * 名称
     */
    private String tenantName;

    /**
     * logo图
     */
    private String tenantLogo;

    /**
     * 显示顺序
     */
    private Long sortOrder;

    /**
     * 官方地址
     */
    private String homeUrl;

    /**
     * 电话
     */
    private String phone;

    /**
     * 传真
     */
    private String fax;

    /**
     * 邮箱
     */
    private String email;

    /**
     * 地址
     */
    private String address;

    /**
     * 管理员账号
     */
    private String userName;

    /**
     * 管理员密码
     */
    private String userPwd;

    /**
     * 租户套餐
     */
    private String packageId;

    /**
     * 租赁到期时间
     */
    private Date tenantTime;

    private Date createdTime;

    /**
     * 租户权限标识
     */
    private String tenantKey;

    /**
     * 微信公众号ID
     */
    private String appId;

    /**
     * 微信公众号的app secret
     */
    private String secret;

    /**
     * 微信商户ID
     */
    private String mchId;

    /**
     * 设置微信公众号的token
     */
    private String mchKey;

    /**
     * 商户秘钥
     */
    private String certificatePath;

    /**
     * 备注
     */
    private String remarks;

    /**
     * 店员数
     */
    private Long clerkNumber;

    /**
     * 订单总数
     */
    private Long orderNumber;

    /**
     * 总充值金额
     */
    private BigDecimal rechargeAmount;

}


--- File: play-admin/src/main/java/com/starry/admin/modules/system/vo/SysUserEntityListVo.java ---
package com.starry.admin.modules.system.vo;

import com.starry.admin.modules.clerk.module.entity.PlayClerkUserInfoEntity;
import com.starry.admin.modules.system.module.entity.SysUserEntity;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import java.util.List;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.EqualsAndHashCode;

/**
 * @Author: huchuansai
 * @Date: 2024/6/14 2:49 PM
 * @Description:
 */
@EqualsAndHashCode(callSuper = true)
@Data
@ApiModel(value = "用户列表展示对象", description = "系统用户列表格式化展示的对象")
public class SysUserEntityListVo extends SysUserEntity {

    @ApiModelProperty(value = "角色列表", notes = "用户拥有的角色信息列表")
    private List<RoleVo> roleVoList;

    @ApiModelProperty(value = "店员信息", notes = "关联的店员详细信息")
    private PlayClerkUserInfoEntity clerkUserInfo;

    @Data
    @AllArgsConstructor
    @ApiModel(value = "角色信息对象", description = "用户所属角色的简要信息")
    public static class RoleVo {

        @ApiModelProperty(value = "角色名称", example = "管理员")
        private String roleName;

        @ApiModelProperty(value = "角色对应的用户名", example = "admin")
        private String roleUserName;

    }

}


--- File: play-admin/src/main/java/com/starry/admin/modules/system/vo/SysUserQueryVo.java ---
package com.starry.admin.modules.system.vo;

import com.starry.admin.modules.system.module.entity.SysDeptEntity;
import com.starry.admin.modules.system.module.entity.SysRoleEntity;
import com.starry.common.domain.BasePageEntity;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import java.util.Date;
import java.util.List;
import lombok.Data;
import lombok.EqualsAndHashCode;

/**
 * 用户查询对象
 *
 * @author admin
 */
@EqualsAndHashCode(callSuper = true)
@Data
@ApiModel(value = "系统用户查询参数", description = "查询系统用户的条件参数")
public class SysUserQueryVo extends BasePageEntity {

    private static final long serialVersionUID = 1L;

    @ApiModelProperty(value = "用户ID", example = "1")
    private Long userId;

    @ApiModelProperty(value = "用户类型", example = "1", notes = "1:admin;2:会员")
    private Integer userType;

    @ApiModelProperty(value = "用户名", example = "admin", notes = "登录账号，支持模糊查询")
    private String userCode;

    @ApiModelProperty(value = "密码", hidden = true)
    private String passWord;

    @ApiModelProperty(value = "姓名", example = "张三", notes = "用户真实姓名，支持模糊查询")
    private String realName;

    @ApiModelProperty(value = "用户昵称", example = "小张", notes = "用户昵称，支持模糊查询")
    private String userNickname;

    @ApiModelProperty(value = "邮箱", example = "test@example.com")
    private String userEmail;

    @ApiModelProperty(value = "头像", example = "https://example.com/avatar.jpg")
    private String avatar;

    @ApiModelProperty(value = "手机号码", example = "13800138000")
    private String mobile;

    @ApiModelProperty(value = "性别", example = "1", notes = "1:男;2:女;0:未知")
    private Integer sex;

    @ApiModelProperty(value = "出生日期", example = "1990-01-01")
    private Date birthday;

    @ApiModelProperty(value = "状态", example = "0", notes = "0:正常;1:停用")
    private Integer status;

    @ApiModelProperty(value = "最后登录时间")
    private Date lastLoginTime;

    @ApiModelProperty(value = "最后登录IP", example = "192.168.1.1")
    private String lastLoginIp;

    @ApiModelProperty(value = "部门ID", example = "1")
    private Long deptId;

    @ApiModelProperty(value = "租户ID", example = "1")
    private Long tenantId;

    @ApiModelProperty(value = "角色列表", notes = "用户关联的角色信息")
    private List<SysRoleEntity> roles;

    @ApiModelProperty(value = "角色ID数组", notes = "用户关联的角色ID列表")
    private Long[] roleIds;

    @ApiModelProperty(value = "部门信息", notes = "用户所属部门详情")
    private SysDeptEntity dept;

    @ApiModelProperty(value = "角色ID", example = "1", notes = "用于查询已分配或未分配指定角色的用户")
    private Long roleId;
}


--- File: play-admin/src/main/java/com/starry/admin/modules/system/vo/TenantLoginVo.java ---
package com.starry.admin.modules.system.vo;

import io.swagger.annotations.ApiModelProperty;
import java.io.Serializable;
import javax.validation.constraints.NotNull;
import lombok.Data;

/**
 * @author huoqiang
 * @since 2021/9/6
 */
@Data
public class TenantLoginVo implements Serializable {

    /**
     * 租户ID
     */
    @NotNull(message = "租户标识不能为空")
    private String tenantkey;

    @ApiModelProperty(value = "用户名")
    private String userName;

    @ApiModelProperty(value = "密码")
    private String passWord;

    @ApiModelProperty(value = "验证码随机字符串")
    private String nonceStr;

    @ApiModelProperty(value = "验证值")
    private String value;
}


--- File: play-admin/src/main/java/com/starry/admin/modules/system/vo/TenantResultVo.java ---
package com.starry.admin.modules.system.vo;

import io.swagger.annotations.ApiModel;
import java.util.Date;
import lombok.Data;

/**
 * @author 租户列表返回结果VO
 * @since 2023/3/7
 */
@ApiModel("管理后台 - 租户列表返回信息")
@Data
public class TenantResultVo {

    private String tenantId;

    /**
     * 租户类型;0:公司;1:部门;2:个人;3:其他
     */
    private String tenantType;

    /**
     * 用户状态;0:正常,1:停用
     */
    private String tenantStatus;

    /**
     * 名称
     */
    private String tenantName;

    /**
     * logo图
     */
    private String tenantLogo;

    /**
     * 管理员账号
     */
    private String userName;

    /**
     * 租户套餐Id
     */
    private String packageId;

    /**
     * 租赁到期时间
     */
    private Date tenantTime;

}


--- File: play-admin/src/main/java/com/starry/admin/modules/system/vo/UpdateUserMobileParam.java ---
package com.starry.admin.modules.system.vo;

import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.Pattern;
import lombok.Data;

/**
 * @Author: huchuansai
 * @Date: 2024/6/14 3:05 PM
 * @Description:
 */
@Data
@ApiModel(value = "修改手机号参数", description = "用户修改手机号的请求参数")
public class UpdateUserMobileParam {

    @ApiModelProperty(value = "用户ID", notes = "要修改手机号的用户ID，不传则修改当前登录用户")
    private String userId;

    @NotBlank(message = "手机号不能为空")
    @Pattern(regexp = "^1[3-9]\\d{9}$", message = "手机号格式不正确")
    @ApiModelProperty(value = "新手机号", required = true, example = "13800138000", notes = "用户的新手机号")
    private String mobile;

    @NotBlank(message = "验证码不能为空")
    @ApiModelProperty(value = "短信验证码", required = true, example = "123456", notes = "发送到新手机号的验证码")
    private String smsCode;

}


--- File: play-admin/src/main/java/com/starry/admin/modules/system/vo/UpdateUserPasswordParam.java ---
package com.starry.admin.modules.system.vo;

import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import javax.validation.constraints.NotBlank;
import lombok.Data;

/**
 * @Author: huchuansai
 * @Date: 2024/6/14 3:05 PM
 * @Description:
 */
@Data
@ApiModel(value = "修改密码参数", description = "用户修改密码的请求参数")
public class UpdateUserPasswordParam {

    @ApiModelProperty(value = "用户ID", notes = "要修改密码的用户ID，不传则修改当前登录用户")
    private String userId;

    @NotBlank(message = "密码不能为空")
    @ApiModelProperty(value = "新密码", required = true, notes = "用户的新密码")
    private String password;

}


--- File: play-admin/src/main/java/com/starry/admin/modules/system/vo/UpdateUserPasswordParamV2.java ---
package com.starry.admin.modules.system.vo;

import io.swagger.annotations.ApiModel;
import javax.validation.constraints.NotBlank;
import lombok.Data;

/**
 * @Author: huchuansai
 * @Date: 2024/6/14 3:05 PM
 * @Description:
 */
@Data
@ApiModel(value = "修改密码参数", description = "用户修改密码的请求参数")
public class UpdateUserPasswordParamV2 {

    @NotBlank(message = "新密码不能为空")
    private String newPassword;

    @NotBlank(message = "旧密码不能为空")
    private String oldPassword;

}


--- File: play-admin/src/main/java/com/starry/admin/modules/system/vo/UserAddParam.java ---
package com.starry.admin.modules.system.vo;

import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.Pattern;
import lombok.Data;

/**
 * @Author: huchuansai
 * @Date: 2024/6/14 11:27 AM
 * @Description:
 */
@Data
@ApiModel(value = "用户添加参数", description = "新增系统用户的请求参数")
public class UserAddParam {

    @NotEmpty(message = "请指定店员")
    @ApiModelProperty(value = "店员ID", required = true, notes = "关联的店员ID")
    private String clerkId;

    @NotEmpty(message = "请指定手机号")
    @Pattern(regexp = "^1[3-9]\\d{9}$", message = "手机号格式不正确")
    @ApiModelProperty(value = "手机号", required = true, example = "13800138000", notes = "用户手机号，作为登录账号")
    private String mobile;

    @NotEmpty(message = "请指定短信验证码")
    @ApiModelProperty(value = "短信验证码", required = true, example = "123456", notes = "发送到手机的验证码")
    private String smsCode;

}


--- File: play-admin/src/main/java/com/starry/admin/modules/system/vo/UserQueryVo.java ---
package com.starry.admin.modules.system.vo;

import com.starry.common.domain.BasePageEntity;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import lombok.EqualsAndHashCode;

/**
 * @author admin
 * @since 2021/9/26
 */
@EqualsAndHashCode(callSuper = true)
@Data
@ApiModel(value = "用户查询参数", description = "系统用户分页查询的条件参数")
public class UserQueryVo extends BasePageEntity {

    @ApiModelProperty(value = "用户ID", example = "1", notes = "用户唯一标识")
    private String userId;

    @ApiModelProperty(value = "用户名", example = "admin", notes = "登录账号，支持模糊查询")
    private String userCode;

    @ApiModelProperty(value = "姓名", example = "张三", notes = "用户真实姓名，支持模糊查询")
    private String realName;
}


--- File: play-admin/src/main/java/com/starry/admin/modules/weichat/constant/ConfigConstant.java ---
package com.starry.admin.modules.weichat.constant;

/**
 * 全局常量
 *
 * @author admin 2019年1月21日
 */
public interface ConfigConstant {

    // 订阅状态（0：已订阅；1：未订阅；2：网页授权用户）
    /**
     * 0：未订阅，取消订阅
     */
    String SUBSCRIBE_TYPE_NO = "0";
    /**
     * 1：已订阅
     */
    String SUBSCRIBE_TYPE_YES = "1";
    /**
     * 2：网页授权用户
     */
    String SUBSCRIBE_TYPE_WEBLIEN = "2";

    /**
     * 应用类型 1:微信
     */
    String WX_APP_TYPE_1 = "1";
    /**
     * 应用类型 2:公众号
     */
    String WX_APP_TYPE_2 = "2";

    /**
     * 消息自动回复类型（1、关注时回复；2、消息回复；3、关键词回复）
     */
    String WX_AUTO_REPLY_TYPE_1 = "1";
    String WX_AUTO_REPLY_TYPE_2 = "2";
    String WX_AUTO_REPLY_TYPE_3 = "3";

    /**
     * 回复类型文本匹配类型（1、全匹配，2、半匹配）
     */
    String WX_REP_MATE_1 = "1";
    String WX_REP_MATE_2 = "2";

    /**
     * 消息分类（1、用户发给公众号；2、公众号发给用户；）
     */
    String WX_MSG_TYPE_1 = "1";
    String WX_MSG_TYPE_2 = "2";

    /**
     * 群发消息发送类型（1、分组发；2、选择用户发）
     */
    String WX_MASS_SEND_TYPE_1 = "1";
    String WX_MASS_SEND_TYPE_2 = "2";

    /**
     * 群发消息发送后的状态(SUB_SUCCESS：提交成功，SUB_FAIL：提交失败，SEND_SUCCESS：发送成功，SENDING：发送中，SEND_FAIL：发送失败，DELETE：已删除)
     */
    String WX_MASS_STATUS_SUB_SUCCESS = "SUB_SUCCESS";
    String WX_MASS_STATUS_SUB_FAIL = "SUB_FAIL";
    String WX_MASS_STATUS_SEND_SUCCESS = "SEND_SUCCESS";
    String WX_MASS_STATUS_SENDING = "SENDING";
    String WX_MASS_STATUS_SEND_FAIL = "SEND_FAIL";
    String WX_MASS_STATUS_DELETE = "DELETE";

    /**
     * 菜单类型(1:普通菜单，2:个性化菜单)
     */
    String WX_MENU_TYPE_1 = "1";
    String WX_MENU_TYPE_2 = "2";

    /**
     * header中的app-id
     */
    String HEADER_APP_ID = "app-id";

    /**
     * header中的third-session
     */
    String HEADER_THIRDSESSION = "third-session";
}


--- File: play-admin/src/main/java/com/starry/admin/modules/weichat/constant/MyReturnCode.java ---
package com.starry.admin.modules.weichat.constant;

/**
 * 全局返回码 微信用6开头，例60001
 *
 * @author admin 2019年7月25日
 */
public enum MyReturnCode {

    // 其它错误
    ERR_60000(60000, "系统错误，请稍候再试") {
    },
    ERR_60001(60001, "登录超时，请重新登录") {
    },
    ERR_60002(60002, "session不能为空") {
    },

    ERR_70001(70001, "该状态订单不允许操作") {
    },
    ERR_70002(70002, "请选择付款方式") {
    },
    ERR_70003(70003, "没有符合下单条件的规格商品，商品已下架或库存不足") {
    },
    ERR_70004(70004, "只有未支付的详单能发起支付") {
    },
    ERR_70005(70005, "无效订单") {
    },

    ERR_80004(80004, "该商品已删除") {
    },

    ;

    private int code;
    private String msg;

    MyReturnCode(int code, String msg) {
        this.code = code;
        this.msg = msg;
    }

    public int getCode() {
        return code;
    }

    public void setCode(int code) {
        this.code = code;
    }

    public String getMsg() {
        return msg;
    }

    public void setMsg(String msg) {
        this.msg = msg;
    }

    @Override
    public String toString() {
        return "MyReturnCode{" + "code='" + code + '\'' + "msg='" + msg + '\'' + '}';
    }

}


--- File: play-admin/src/main/java/com/starry/admin/modules/weichat/constant/WebSocketConstant.java ---
package com.starry.admin.modules.weichat.constant;

/**
 * @author admin
 */
public interface WebSocketConstant {

    String USER_DESTINATION_PREFIX = "/weixin/";
    String WX_MSG = "wx_msg";

}


--- File: play-admin/src/main/java/com/starry/admin/modules/weichat/constant/WxMaConstants.java ---
package com.starry.admin.modules.weichat.constant;

/**
 * @author admin
 */
public interface WxMaConstants {

    /**
     * redis中3rd_session过期时间(单位：小时)
     */
    long TIME_OUT_SESSION = 6;
    /**
     * redis中3rd_session拼接前缀
     */
    String THIRD_SESSION_BEGIN = "wx:ma:3rd_session";
}


--- File: play-admin/src/main/java/com/starry/admin/modules/weichat/constant/WxReturnCode.java ---
package com.starry.admin.modules.weichat.constant;

/**
 * 微信接口全局返回码
 *
 * @author admin
 */

@SuppressWarnings("")
public enum WxReturnCode {

    SUC_0("0", "请求成功") {
    },
    ERR_1("-1", "系统繁忙，此时请开发者稍候再试") {
    },
    ERR_10001("10001", "涉嫌广告 ") {
    },
    ERR_20001("20001", "涉嫌政治 ") {
    },
    ERR_20002("20002", "涉嫌色情 ") {
    },
    ERR_20004("20004", "涉嫌社会 ") {
    },
    ERR_20006("20006", "涉嫌违法犯罪 ") {
    },
    ERR_20008("20008", "涉嫌欺诈 ") {
    },
    ERR_20013("20013", "涉嫌版权 ") {
    },
    ERR_21000("21000", "涉嫌其他 ") {
    },
    ERR_22000("22000", "涉嫌互推(互相宣传) ") {
    },
    ERR_30001("30001", "原创校验出现系统错误且用户选择了被判为转载就不群发 ") {
    },
    ERR_30002("30002", "原创校验被判定为不能群发 ") {
    },
    ERR_30003("30003", "原创校验被判定为转载文且用户选择了被判为转载就不群发 ") {
    },
    ERR_40001("40001", "获取access_token时AppSecret错误，或者access_token无效。请开发者认真比对AppSecret的正确性，或查看是否正在为恰当的公众号调用接口") {
    },
    ERR_40002("40002", "不合法的凭证类型") {
    },
    ERR_40003("40003", "不合法的OpenID，请开发者确认OpenID（该用户）是否已关注公众号，或是否是其他公众号的OpenID") {
    },
    ERR_40004("40004", "不合法的媒体文件类型") {
    },
    ERR_40005("40005", "不合法的文件类型") {
    },
    ERR_40006("40006", "不合法的文件大小") {
    },
    ERR_40007("40007", "不合法的媒体文件id") {
    },
    ERR_40008("40008", "不合法的消息类型") {
    },
    ERR_40009("40009", "不合法的图片文件大小") {
    },
    ERR_40010("40010", "不合法的语音文件大小") {
    },
    ERR_40011("40011", "不合法的视频文件大小") {
    },
    ERR_40012("40012", "不合法的缩略图文件大小") {
    },
    ERR_40013("40013", "不合法的AppID，请开发者检查AppID的正确性，避免异常字符，注意大小写") {
    },
    ERR_40014("40014", "不合法的access_token，请开发者认真比对access_token的有效性（如是否过期），或查看是否正在为恰当的公众号调用接口") {
    },
    ERR_40015("40015", "不合法的菜单类型") {
    },
    ERR_40016("40016", "不合法的按钮个数") {
    },
    ERR_40017("40017", "不合法的按钮个数") {
    },
    ERR_40018("40018", "不合法的按钮名字长度") {
    },
    ERR_40019("40019", "不合法的按钮KEY长度") {
    },
    ERR_40020("40020", "不合法的按钮URL长度") {
    },
    ERR_40021("40021", "不合法的菜单版本号") {
    },
    ERR_40022("40022", "不合法的子菜单级数") {
    },
    ERR_40023("40023", "不合法的子菜单按钮个数") {
    },
    ERR_40024("40024", "不合法的子菜单按钮类型") {
    },
    ERR_40025("40025", "不合法的子菜单按钮名字长度") {
    },
    ERR_40026("40026", "不合法的子菜单按钮KEY长度") {
    },
    ERR_40027("40027", "不合法的子菜单按钮URL长度") {
    },
    ERR_40028("40028", "不合法的自定义菜单使用用户") {
    },
    ERR_40029("40029", "不合法的oauth_code") {
    },
    ERR_40030("40030", "不合法的refresh_token") {
    },
    ERR_40031("40031", "不合法的openid列表") {
    },
    ERR_40032("40032", "不合法的openid列表个数") {
    },
    ERR_40033("40033", "不合法的请求字符，不能包含xxxx格式的字符") {
    },
    ERR_40035("40035", "不合法的参数") {
    },
    ERR_40055("40055", "不完整的url，前面要加http://") {
    },
    ERR_40037("40037", "template_id不正确") {
    },
    ERR_40038("40038", "不合法的请求格式") {
    },
    ERR_40039("40039", "不合法的URL长度") {
    },
    ERR_40050("40050", "不合法的分组id") {
    },
    ERR_40051("40051", "分组名字不合法") {
    },
    ERR_40062("40062", "标题长度不合法") {
    },
    ERR_40097("40097", "参数不合法") {
    },
    ERR_40113("40113", "文件名称不合法，需包含正确后缀") {
    },
    ERR_40117("40117", "分组名字不合法") {
    },
    ERR_40118("40118", "media_id大小不合法") {
    },
    ERR_40119("40119", "button类型错误") {
    },
    ERR_40120("40120", "button类型错误") {
    },
    ERR_40121("40121", "不合法的media_id类型") {
    },
    ERR_40125("40125", "不合法的AppSecret，请开发者检查AppSecret的正确性，避免异常字符，注意大小写") {
    },
    ERR_40130("40130", "至少需要同时发送两个用户") {
    },
    ERR_40132("40132", "微信号不合法") {
    },
    ERR_40137("40137", "不支持的图片格式") {
    },
    ERR_40164("40164", "调用接口的IP地址不在白名单中，请在接口IP白名单中进行设置") {
    },
    ERR_41001("41001", "缺少access_token参数") {
    },
    ERR_41002("41002", "缺少appid参数") {
    },
    ERR_41003("41003", "缺少refresh_token参数") {
    },
    ERR_41004("41004", "缺少secret参数") {
    },
    ERR_41005("41005", "缺少多媒体文件数据") {
    },
    ERR_41006("41006", "缺少media_id参数") {
    },
    ERR_41007("41007", "缺少子菜单数据") {
    },
    ERR_41008("41008", "缺少oauth code") {
    },
    ERR_41009("41009", "缺少openid") {
    },
    ERR_41028("41028", "form_id不正确，或者过期") {
    },
    ERR_41029("41029", "form_id已被使用") {
    },
    ERR_41030("41030", "page不正确") {
    },
    ERR_42001("42001", "access_token超时，请检查access_token的有效期，请参考基础支持-获取access_token中，对access_token的详细机制说明") {
    },
    ERR_42002("42002", "refresh_token超时") {
    },
    ERR_42003("42003", "oauth_code超时") {
    },
    ERR_43001("43001", "需要GET请求") {
    },
    ERR_43002("43002", "需要POST请求") {
    },
    ERR_43003("43003", "需要HTTPS请求") {
    },
    ERR_43004("43004", "需要接收者关注") {
    },
    ERR_43005("43005", "需要好友关系") {
    },
    ERR_44001("44001", "多媒体文件为空") {
    },
    ERR_44002("44002", "POST的数据包为空") {
    },
    ERR_44003("44003", "图文消息内容为空") {
    },
    ERR_44004("44004", "文本消息内容为空") {
    },
    ERR_45001("45001", "多媒体文件大小超过限制") {
    },
    ERR_45002("45002", "消息内容超过限制") {
    },
    ERR_45003("45003", "标题字段超过限制") {
    },
    ERR_45004("45004", "描述字段超过限制") {
    },
    ERR_45005("45005", "链接字段超过限制") {
    },
    ERR_45006("45006", "图片链接字段超过限制") {
    },
    ERR_45007("45007", "语音播放时间超过限制") {
    },
    ERR_45008("45008", "图文消息超过限制") {
    },
    ERR_45009("45009", "接口调用超过限制") {
    },
    ERR_45010("45010", "创建菜单个数超过限制") {
    },
    ERR_45015("45015", "回复时间超过限制") {
    },
    ERR_45016("45016", "系统分组，不允许修改") {
    },
    ERR_45017("45017", "分组名字过长") {
    },
    ERR_45018("45018", "分组数量超过上限") {
    },
    ERR_45028("45028", "没有群发的配额，配额已经用完") {
    },
    ERR_45047("45047", "客服下行消息超过上限") {
    },
    ERR_45157("45157", "标签名非法，请注意不能和其他标签重名") {
    },
    ERR_45158("45158", "标签名长度超过30个字节") {
    },
    ERR_45056("45056", "创建的标签数过多，请注意不能超过100个") {
    },
    ERR_45058("45058", "不能修改0/1/2这三个系统默认保留的标签") {
    },
    ERR_45057("45057", "该标签下粉丝数超过10w，不允许直接删除") {
    },
    ERR_45059("45059", "有粉丝身上的标签数已经超过限制") {
    },
    ERR_45159("45159", "非法的tag_id") {
    },
    ERR_46001("46001", "不存在媒体数据") {
    },
    ERR_46002("46002", "不存在的菜单版本") {
    },
    ERR_46003("46003", "不存在的菜单数据") {
    },
    ERR_46004("46004", "不存在的用户") {
    },
    ERR_47001("47001", "解析JSON/XML内容错误") {
    },
    ERR_48001("48001", "api功能未授权，请确认公众号已获得该接口，可以在公众平台官网-开发者中心页中查看接口权限") {
    },
    ERR_48002("48002", "粉丝拒收消息（粉丝在公众号选项中，关闭了 “ 接收消息 ” ）") {
    },
    ERR_48004("48004", "api 接口被封禁，请登录 admin.weixin.qq.com 查看详情") {
    },
    ERR_48005("48005", "api 禁止删除被自动回复和自定义菜单引用的素材") {
    },
    ERR_48006("48006", "api 禁止清零调用次数，因为清零次数达到上限") {
    },
    ERR_48008("48008", "没有该类型消息的发送权限") {
    },
    ERR_49003("49003", "传入的openid不属于此AppID") {
    },
    ERR_50001("50001", "用户未授权该api") {
    },
    ERR_50002("50002", "用户受限，可能是违规后接口被封禁") {
    },
    ERR_50005("50005", "用户未关注公众号") {
    },
    ERR_61003("61003", "请确认是否取消授权（第三方平台授权）") {
    },
    ERR_61004("61004", "当前ip未在白名单中，直接获取本地ip添加") {
    },
    ERR_61005("61005", " 组件 ticket已失效，重新接受授权url反馈的ticket") {
    },
    ERR_61006("61006", "获取componentTicket为null") {
    },
    ERR_61007("61007", "当前公众号或者微信已在公众平台解绑") {
    },
    ERR_61009("61009", "授权码失效，重新授权") {
    },
    ERR_61451("61451", "参数错误(invalid parameter)") {
    },
    ERR_61452("61452", "无效客服账号(invalid kf_account)") {
    },
    ERR_61453("61453", "客服帐号已存在(kf_account exsited)") {
    },
    ERR_61454("61454", "客服帐号名长度超过限制(仅允许10个英文字符，不包括@及@后的公众号的微信号)(invalid kf_acount length)") {
    },
    ERR_61455("61455", "客服帐号名包含非法字符(仅允许英文+数字)(illegal character in kf_account)") {
    },
    ERR_61456("61456", "客服帐号个数超过限制(10个客服账号)(kf_account count exceeded)") {
    },
    ERR_61457("61457", "无效头像文件类型(invalid file type)") {
    },
    ERR_61450("61450", "系统错误(system error)") {
    },
    ERR_61500("61500", "日期格式错误") {
    },
    ERR_61501("61501", "日期范围错误") {
    },
    ERR_65400("65400", "API不可用，即没有开通/升级到新版客服功能") {
    },
    ERR_65401("65401", "无效客服帐号") {
    },
    ERR_65403("65403", "客服昵称不合法") {
    },
    ERR_65404("65404", "客服帐号不合法") {
    },
    ERR_65405("65405", "帐号数目已达到上限，不能继续添加") {
    },
    ERR_65406("65406", "已经存在的客服帐号") {
    },
    ERR_65407("65407", "邀请对象已经是该公众号客服") {
    },
    ERR_65408("65408", "本公众号已经有一个邀请给该微信") {
    },
    ERR_65409("65409", "无效的微信号") {
    },
    ERR_65410("65410", "邀请对象绑定公众号客服数达到上限（目前每个微信号可以绑定5个公众号客服帐号）") {
    },
    ERR_65411("65411", "该帐号已经有一个等待确认的邀请，不能重复邀请") {
    },
    ERR_65412("65412", "该帐号已经绑定微信号，不能进行邀请") {
    },
    ERR_99999("99999", "无法获取到文件名") {
    },
    ERR_9001001("9001001", "POST数据参数不合法") {
    },
    ERR_9001002("9001002", "远端服务不可用") {
    },
    ERR_9001003("9001003", "Ticket不合法") {
    },
    ERR_9001004("9001004", "获取摇周边用户信息失败") {
    },
    ERR_9001005("9001005", "获取商户信息失败") {
    },
    ERR_9001006("9001006", "获取OpenID失败") {
    },
    ERR_9001007("9001007", "上传文件缺失") {
    },
    ERR_9001008("9001008", "上传素材的文件类型不合法") {
    },
    ERR_9001009("9001009", "上传素材的文件尺寸不合法") {
    },
    ERR_9001010("9001010", "上传失败") {
    },
    ERR_9001020("9001020", "帐号不合法") {
    },
    ERR_9001021("9001021", "已有设备激活率低于50%，不能新增设备") {
    },
    ERR_9001022("9001022", "设备申请数不合法，必须为大于0的数字") {
    },
    ERR_9001023("9001023", "已存在审核中的设备ID申请") {
    },
    ERR_9001024("9001024", "一次查询设备ID数量不能超过50") {
    },
    ERR_9001025("9001025", "设备ID不合法") {
    },
    ERR_9001026("9001026", "页面ID不合法") {
    },
    ERR_9001027("9001027", "页面参数不合法") {
    },
    ERR_9001028("9001028", "一次删除页面ID数量不能超过10") {
    },
    ERR_9001029("9001029", "页面已应用在设备中，请先解除应用关系再删除") {
    },
    ERR_9001030("9001030", "一次查询页面ID数量不能超过50") {
    },
    ERR_9001031("9001031", "时间区间不合法") {
    },
    ERR_9001032("9001032", "保存设备与页面的绑定关系参数错误") {
    },
    ERR_9001033("9001033", "门店ID不合法") {
    },
    ERR_9001034("9001034", "设备备注信息过长") {
    },
    ERR_9001035("9001035", "设备申请参数不合法") {
    },
    ERR_9001036("9001036", "查询起始值begin不合法") {
    };

    private String code;
    private String msg;

    WxReturnCode(String code, String msg) {
        this.code = code;
        this.msg = msg;
    }

    /**
     * 通过code获取msg
     *
     * @param code
     * @return
     */
    public static String getMsg(String code) {
        try {
            return WxReturnCode.valueOf(code).getMsg();
        } catch (IllegalArgumentException e) {
            return "未定义的返回码：" + code;
        }
    }

    public String getCode() {
        return code;
    }

    public void setCode(String code) {
        this.code = code;
    }

    public String getMsg() {
        return msg;
    }

    public void setMsg(String msg) {
        this.msg = msg;
    }

    @Override
    public String toString() {
        return "WxReturnCode{" + "code='" + code + '\'' + "msg='" + msg + '\'' + '}';
    }

}


--- File: play-admin/src/main/java/com/starry/admin/modules/weichat/controller/WxArticleController.java ---
package com.starry.admin.modules.weichat.controller;

import com.starry.admin.common.aspect.ClerkUserLogin;
import com.starry.admin.common.aspect.CustomUserLogin;
import com.starry.admin.common.conf.ThreadLocalRequestDetail;
import com.starry.admin.modules.clerk.module.entity.PlayClerkArticleInfoEntity;
import com.starry.admin.modules.clerk.module.entity.PlayClerkUserInfoEntity;
import com.starry.admin.modules.clerk.module.entity.PlayCustomArticleInfoEntity;
import com.starry.admin.modules.clerk.module.vo.PlayClerkArticleQueryVo;
import com.starry.admin.modules.clerk.service.IPlayClerkUserInfoService;
import com.starry.admin.modules.clerk.service.impl.PlayClerkArticleInfoServiceImpl;
import com.starry.admin.modules.clerk.service.impl.PlayCustomArticleInfoServiceImpl;
import com.starry.admin.modules.weichat.entity.article.PlayClerkAddArticleVo;
import com.starry.admin.modules.weichat.entity.article.PlayClerkArticleCustomQueryVo;
import com.starry.admin.modules.weichat.entity.article.PlayClerkArticleFollowCustomFollowStateEditStateVo;
import com.starry.admin.modules.weichat.entity.article.PlayClerkArticleFollowCustomGreedStateEditStateVo;
import com.starry.admin.modules.weichat.service.WxCustomUserService;
import com.starry.common.result.R;
import com.starry.common.utils.ConvertUtil;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiParam;
import io.swagger.annotations.ApiResponse;
import io.swagger.annotations.ApiResponses;
import java.time.LocalDateTime;
import javax.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;

/**
 * 动态接口
 *
 * @author admin
 * @since 2024/5/20 下午11:19
 **/
@Api(tags = "微信文章动态接口", description = "微信端文章动态相关接口")
@Slf4j
@RestController
@RequestMapping("/wx/article")
public class WxArticleController {

    @Resource
    private PlayClerkArticleInfoServiceImpl playClerkArticleInfoService;

    @Resource
    private PlayCustomArticleInfoServiceImpl playCustomArticleInfoService;

    @Resource
    private IPlayClerkUserInfoService playClerkUserInfoService;

    @Resource
    private WxCustomUserService customUserService;

    /**
     * 店员新增动态
     */
    @ApiOperation(value = "店员新增动态", notes = "店员发布新的动态文章")
    @ApiResponses({@ApiResponse(code = 200, message = "操作成功")})
    @ClerkUserLogin
    @PostMapping("/clerk/add")
    public R clerkAdd(@ApiParam(value = "动态信息", required = true) @Validated @RequestBody PlayClerkAddArticleVo vo) {
        vo.setClerkId(ThreadLocalRequestDetail.getClerkUserInfo().getId());
        playClerkArticleInfoService.create(ConvertUtil.entityToVo(vo, PlayClerkArticleInfoEntity.class));
        return R.ok("成功");
    }

    /**
     * 店员删除
     */
    @ApiOperation(value = "店员删除动态", notes = "店员删除自己发布的动态")
    @ApiImplicitParam(name = "id", value = "动态ID", required = true, dataType = "String", paramType = "query")
    @ApiResponses({@ApiResponse(code = 200, message = "操作成功")})
    @ClerkUserLogin
    @GetMapping("/clerk/deleteById")
    public R clerkDeleteById(@RequestParam("id") String id) {
        playClerkArticleInfoService.deletePlayClerkArticleInfoById(id);
        playCustomArticleInfoService.deleteByArticleId(id);
        return R.ok("成功");
    }

    /**
     * 店员分页查询本人动态列表
     */
    @ApiOperation(value = "店员查询动态", notes = "店员分页查询自己的动态列表")
    @ApiResponses({
            @ApiResponse(code = 200, message = "操作成功", response = PlayClerkArticleInfoEntity.class, responseContainer = "Page")})
    @ClerkUserLogin
    @PostMapping("/clerk/listByPage")
    public R clerkListByPage(
            @ApiParam(value = "查询条件", required = true) @Validated @RequestBody PlayClerkArticleQueryVo vo) {
        vo.setClerkId(ThreadLocalRequestDetail.getClerkUserInfo().getId());
        return R.ok(playClerkArticleInfoService.selectByPage(vo, true));
    }

    /**
     * 顾客查询动态列表
     */
    @ApiOperation(value = "顾客查询动态列表", notes = "顾客分页查询所有动态列表")
    @ApiResponses({
            @ApiResponse(code = 200, message = "操作成功", response = PlayClerkArticleInfoEntity.class, responseContainer = "Page")})
    @PostMapping("/custom/listByPage")
    public R customListByPage(
            @ApiParam(value = "查询条件", required = true) @Validated @RequestBody PlayClerkArticleCustomQueryVo vo) {
        vo.setFollowState("0");
        return R.ok(playClerkArticleInfoService.customSelectByPage(vo, customUserService.getLoginUserId()));
    }

    /**
     * 顾客查询已收藏动态列表
     */
    @ApiOperation(value = "查询收藏动态", notes = "顾客查询已收藏的动态列表")
    @ApiResponses({
            @ApiResponse(code = 200, message = "操作成功", response = PlayClerkArticleInfoEntity.class, responseContainer = "Page")})
    @CustomUserLogin
    @PostMapping("/custom/listFollowByPage")
    public R customListFollowByPage(
            @ApiParam(value = "查询条件", required = true) @Validated @RequestBody PlayClerkArticleCustomQueryVo vo) {
        vo.setFollowState("1");
        return R.ok(playClerkArticleInfoService.customSelectByPage(vo, customUserService.getLoginUserId()));
    }

    /**
     * 分页获取店员动态
     *
     * @param id
     *            店员ID
     * @return 店员动态
     */
    @ApiOperation(value = "获取店员动态", notes = "根据店员ID查询该店员的动态")
    @ApiImplicitParam(name = "id", value = "店员ID", required = true, dataType = "String", paramType = "query")
    @ApiResponses({
            @ApiResponse(code = 200, message = "操作成功", response = PlayClerkArticleInfoEntity.class, responseContainer = "Page")})
    @GetMapping("/custom/queryByClerkId")
    public R queryTrendsById(@RequestParam("id") String id) {
        PlayClerkUserInfoEntity entity = playClerkUserInfoService.selectById(id);
        PlayClerkArticleCustomQueryVo vo = new PlayClerkArticleCustomQueryVo();
        vo.setClerkId(entity.getId());
        return R.ok(playClerkArticleInfoService.customSelectByPage(vo, customUserService.getLoginUserId()));
    }

    /**
     * 顾客查询已收藏动态列表
     */
    @ApiOperation(value = "更新点赞状态", notes = "顾客更新对动态的点赞状态")
    @ApiResponses({@ApiResponse(code = 200, message = "操作成功")})
    @CustomUserLogin
    @PostMapping("/custom/updateGreedState")
    public R customUpdateGreedState(
            @ApiParam(value = "点赞信息", required = true) @Validated @RequestBody PlayClerkArticleFollowCustomGreedStateEditStateVo vo) {
        PlayClerkArticleInfoEntity articleInfoEntity = playClerkArticleInfoService
                .selectPlayClerkArticleInfoById(vo.getId());
        PlayCustomArticleInfoEntity entity = playCustomArticleInfoService.selectByArticleId(articleInfoEntity.getId(),
                ThreadLocalRequestDetail.getCustomUserInfo().getId(), "0");
        if (entity == null) {
            entity = new PlayCustomArticleInfoEntity();
            entity.setArticleId(articleInfoEntity.getId());
            entity.setCustomId(ThreadLocalRequestDetail.getCustomUserInfo().getId());
            entity.setEndorseType("1");
            entity.setClerkId(articleInfoEntity.getClerkId());
        }
        entity.setEndorseTime(LocalDateTime.now());
        entity.setEndorseState(vo.getGreedState());
        playCustomArticleInfoService.saveOrUpdate(entity);
        return R.ok("成功");
    }

    @ApiOperation(value = "更新收藏状态", notes = "顾客更新对动态的收藏状态")
    @ApiResponses({@ApiResponse(code = 200, message = "操作成功")})
    @CustomUserLogin
    @PostMapping("/custom/updateFollowState")
    public R customUpdateFollowState(
            @ApiParam(value = "收藏信息", required = true) @Validated @RequestBody PlayClerkArticleFollowCustomFollowStateEditStateVo vo) {
        PlayClerkArticleInfoEntity articleInfoEntity = playClerkArticleInfoService
                .selectPlayClerkArticleInfoById(vo.getId());
        PlayCustomArticleInfoEntity entity = playCustomArticleInfoService.selectByArticleId(articleInfoEntity.getId(),
                ThreadLocalRequestDetail.getCustomUserInfo().getId(), "1");
        if (entity == null) {
            entity = new PlayCustomArticleInfoEntity();
            entity.setArticleId(articleInfoEntity.getId());
            entity.setCustomId(ThreadLocalRequestDetail.getCustomUserInfo().getId());
            entity.setEndorseType("0");
            entity.setClerkId(articleInfoEntity.getClerkId());
        }
        entity.setEndorseTime(LocalDateTime.now());
        entity.setEndorseState(vo.getFollowState());
        playCustomArticleInfoService.saveOrUpdate(entity);
        return R.ok("成功");
    }

}


--- File: play-admin/src/main/java/com/starry/admin/modules/weichat/controller/WxClerkCommodityController.java ---
package com.starry.admin.modules.weichat.controller;

import com.starry.admin.common.aspect.ClerkUserLogin;
import com.starry.admin.common.conf.ThreadLocalRequestDetail;
import com.starry.admin.common.exception.CustomException;
import com.starry.admin.modules.clerk.module.entity.PlayClerkCommodityEntity;
import com.starry.admin.modules.clerk.module.entity.PlayClerkUserInfoEntity;
import com.starry.admin.modules.clerk.service.IPlayClerkCommodityService;
import com.starry.admin.modules.clerk.service.IPlayClerkUserInfoService;
import com.starry.admin.modules.shop.module.entity.PlayCommodityAndLevelInfoEntity;
import com.starry.admin.modules.shop.service.IPlayCommodityAndLevelInfoService;
import com.starry.admin.modules.shop.service.IPlayCommodityInfoService;
import com.starry.admin.modules.weichat.entity.PlayCommodityReturnVo;
import com.starry.common.result.R;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiResponse;
import io.swagger.annotations.ApiResponses;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import javax.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

/**
 * 店员等级
 *
 * @author admin
 * @since 2024/5/29 上午6:24
 **/
@Api(tags = "微信店员商品接口", description = "微信端店员商品相关接口")
@Slf4j
@RestController
@RequestMapping("/wx/commodity/")
public class WxClerkCommodityController {

    @Resource
    private IPlayCommodityInfoService playCommodityInfoService;

    @Resource
    private IPlayCommodityAndLevelInfoService iPlayCommodityAndLevelInfoService;

    @Resource
    private IPlayClerkUserInfoService clerkUserInfoService;

    @Resource
    IPlayClerkCommodityService playClerkCommodityService;

    @ApiOperation(value = "根据等级查询商品", notes = "根据等级ID查询对应等级的所有商品")
    @ApiImplicitParam(name = "id", value = "等级ID", dataType = "String", paramType = "query")
    @ApiResponses({
            @ApiResponse(code = 200, message = "操作成功", response = PlayCommodityReturnVo.class, responseContainer = "List")})
    @GetMapping("/custom/queryClerkAllCommodityByLevel")
    public R queryClerkAllCommodityByLevel(@RequestParam("id") String levelId) {
        List<PlayCommodityAndLevelInfoEntity> levelInfoEntities = iPlayCommodityAndLevelInfoService.selectAll();
        List<PlayCommodityReturnVo> tree = playCommodityInfoService.selectTree();
        if (levelId == null || levelId.isEmpty()) {
            return R.ok(tree);
        }
        tree = formatPlayCommodityReturnVoTree(tree, levelInfoEntities, levelId);
        tree = formatPlayCommodityReturnVoTree(tree, null);
        return R.ok(tree);
    }

    /**
     * 顾客查询当前店员所有服务项目
     *
     * @return 店员所有服务项目
     */
    @ApiOperation(value = "查询店员商品", notes = "根据店员ID查询该店员提供的所有商品服务")
    @ApiImplicitParam(name = "id", value = "店员ID", required = true, dataType = "String", paramType = "query")
    @ApiResponses({
            @ApiResponse(code = 200, message = "操作成功", response = PlayCommodityReturnVo.class, responseContainer = "List"),
            @ApiResponse(code = 500, message = "请求参数异常,id不能为空")})
    @GetMapping("/custom/queryClerkAllCommodity")
    public R customQueryClerkAllCommodity(@RequestParam("id") String clerkId) {
        if (clerkId == null || clerkId.isEmpty()) {
            throw new CustomException("请求参数异常,id不能为空");
        }
        PlayClerkUserInfoEntity clerkUserInfo = clerkUserInfoService.selectById(clerkId);
        Map<String, List<PlayClerkCommodityEntity>> clerkCommodityEntities = playClerkCommodityService
                .selectCommodityTypeByUser(clerkId, "1").stream()
                .collect(Collectors.groupingBy(PlayClerkCommodityEntity::getCommodityId));
        List<PlayCommodityAndLevelInfoEntity> levelInfoEntities = iPlayCommodityAndLevelInfoService.selectAll();
        List<PlayCommodityReturnVo> tree = playCommodityInfoService.selectTree();
        tree = formatPlayCommodityReturnVoTree(tree, levelInfoEntities, clerkUserInfo.getLevelId());
        tree = formatPlayCommodityReturnVoTree(tree, clerkCommodityEntities);
        return R.ok(tree);
    }

    /**
     * 店员查询自己的服务项目
     *
     * @return 店员所有服务项目
     */
    @ApiOperation(value = "店员查询自己的商品", notes = "当前登录店员查询自己提供的所有商品服务")
    @ApiResponses({
            @ApiResponse(code = 200, message = "操作成功", response = PlayCommodityReturnVo.class, responseContainer = "List")})
    @ClerkUserLogin
    @GetMapping("/clerk/queryAllCommodity")
    public R clerkQueryAllCommodity() {
        String levelId = ThreadLocalRequestDetail.getClerkUserInfo().getLevelId();
        List<PlayCommodityAndLevelInfoEntity> levelInfoEntities = iPlayCommodityAndLevelInfoService.selectAll();
        Map<String, List<PlayClerkCommodityEntity>> clerkCommodityEntities = playClerkCommodityService
                .selectCommodityTypeByUser(ThreadLocalRequestDetail.getClerkUserInfo().getId(), "1").stream()
                .collect(Collectors.groupingBy(PlayClerkCommodityEntity::getCommodityId));

        List<PlayCommodityReturnVo> tree = playCommodityInfoService.selectTree();
        tree = formatPlayCommodityReturnVoTree(tree, levelInfoEntities, levelId);
        tree = formatPlayCommodityReturnVoTree(tree, clerkCommodityEntities);
        return R.ok(tree);
    }

    public List<PlayCommodityReturnVo> formatPlayCommodityReturnVoTree(List<PlayCommodityReturnVo> tree,
            List<PlayCommodityAndLevelInfoEntity> levelInfoEntities, String levelId) {
        Iterator<PlayCommodityReturnVo> it = tree.iterator();
        while (it.hasNext()) {
            PlayCommodityReturnVo item = it.next();
            // 查找当前服务项目对应的价格
            for (PlayCommodityAndLevelInfoEntity levelInfoEntity : levelInfoEntities) {
                if (item.getId().equals(levelInfoEntity.getCommodityId())
                        && levelId.equals(levelInfoEntity.getLevelId())) {
                    item.setPrice(levelInfoEntity.getPrice());
                }
            }
            // 如果未设置价格,删除元素
            if (!"00".equals(item.getPId()) && item.getPrice() == null) {
                it.remove();
            }
            formatPlayCommodityReturnVoTree(item.getChild(), levelInfoEntities, levelId);
        }
        return tree;
    }

    public List<PlayCommodityReturnVo> formatPlayCommodityReturnVoTree(List<PlayCommodityReturnVo> tree,
            Map<String, List<PlayClerkCommodityEntity>> clerkCommodityEntities) {
        Iterator<PlayCommodityReturnVo> it = tree.iterator();
        while (it.hasNext()) {
            PlayCommodityReturnVo item = it.next();
            if ("00".equals(item.getPId()) && item.getChild().isEmpty()) {
                it.remove();
            } else if (clerkCommodityEntities != null && "00".equals(item.getPId())
                    && !clerkCommodityEntities.containsKey(item.getId())) {
                it.remove();
            }
            formatPlayCommodityReturnVoTree(item.getChild(), clerkCommodityEntities);
        }
        return tree;
    }

}


--- File: play-admin/src/main/java/com/starry/admin/modules/weichat/controller/WxClerkController.java ---
package com.starry.admin.modules.weichat.controller;

import cn.hutool.crypto.SecureUtil;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.starry.admin.common.aspect.ClerkUserLogin;
import com.starry.admin.common.conf.ThreadLocalRequestDetail;
import com.starry.admin.common.exception.CustomException;
import com.starry.admin.modules.clerk.module.entity.*;
import com.starry.admin.modules.clerk.module.vo.PlayClerkCommodityEditVo;
import com.starry.admin.modules.clerk.module.vo.PlayClerkCommodityQueryVo;
import com.starry.admin.modules.clerk.service.*;
import com.starry.admin.modules.clerk.service.impl.PlayClerkUserInfoServiceImpl;
import com.starry.admin.modules.clerk.service.impl.PlayClerkUserReviewInfoServiceImpl;
import com.starry.admin.modules.order.module.entity.PlayOrderInfoEntity;
import com.starry.admin.modules.order.module.vo.PlayOrderEvaluateQueryVo;
import com.starry.admin.modules.order.module.vo.PlayOrderStateEditVo;
import com.starry.admin.modules.order.service.IPlayOrderEvaluateInfoService;
import com.starry.admin.modules.order.service.IPlayOrderInfoService;
import com.starry.admin.modules.personnel.module.entity.PlayPersonnelGroupInfoEntity;
import com.starry.admin.modules.personnel.service.IPlayPersonnelGroupInfoService;
import com.starry.admin.modules.shop.module.entity.PlayGiftInfoEntity;
import com.starry.admin.modules.shop.service.IPlayGiftInfoService;
import com.starry.admin.modules.statistics.service.IPlayClerkPerformanceService;
import com.starry.admin.modules.weichat.entity.*;
import com.starry.admin.modules.weichat.entity.clerk.PlayClerkLevelAndRankingVo;
import com.starry.admin.modules.weichat.entity.clerk.PlayClerkLevelInfoReturnVo;
import com.starry.admin.modules.weichat.entity.clerk.PlayClerkUserInfoQueryVo;
import com.starry.admin.modules.weichat.entity.clerk.PlayClerkUserInfoResultVo;
import com.starry.admin.modules.weichat.entity.gift.PlayClerkGiftReturnVo;
import com.starry.admin.modules.weichat.entity.order.PlayClerkOrderDetailsReturnVo;
import com.starry.admin.modules.weichat.entity.order.PlayClerkOrderInfoQueryVo;
import com.starry.admin.modules.weichat.entity.order.PlayClerkOrderListReturnVo;
import com.starry.admin.modules.weichat.service.WxCustomMpService;
import com.starry.admin.modules.weichat.service.WxCustomUserService;
import com.starry.admin.utils.SecurityUtils;
import com.starry.admin.utils.SmsUtils;
import com.starry.common.redis.RedisCache;
import com.starry.common.result.R;
import com.starry.common.utils.ConvertUtil;
import com.starry.common.utils.VerificationCodeUtils;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiParam;
import io.swagger.annotations.ApiResponse;
import io.swagger.annotations.ApiResponses;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import javax.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;

/**
 * @author admin
 */
@Api(tags = "微信店员接口", description = "微信端店员相关接口")
@Slf4j
@RestController
@RequestMapping("/wx/clerk/")
public class WxClerkController {

    @Resource
    RedisCache redisCache;

    @Resource
    private PlayClerkUserInfoServiceImpl playClerkUserInfoService;

    @Resource
    private IPlayClerkLevelInfoService playClerkLevelInfoService;

    @Resource
    private IPlayClerkUserInfoService clerkUserInfoService;

    @Resource
    private IPlayClerkCommodityService playClerkCommodityService;

    @Resource
    private WxCustomUserService customUserService;

    @Resource
    private IPlayOrderInfoService playOrderInfoService;

    @Resource
    private IPlayOrderEvaluateInfoService playOrderEvaluateInfoService;

    @Resource
    private PlayClerkUserReviewInfoServiceImpl playClerkUserReviewInfoService;

    @Resource
    private IPlayClerkDataReviewInfoService playClerkDataReviewInfoService;

    @Resource
    private IPlayClerkClassificationInfoService playClerkClassificationInfoService;

    @Resource
    private IPlayGiftInfoService giftInfoService;

    @Resource
    private IPlayClerkPerformanceService playClerkPerformanceService;

    @Resource
    private IPlayPersonnelGroupInfoService playPersonnelGroupInfoService;
    @Resource
    private SmsUtils smsUtils;
    @Resource
    private WxCustomMpService wxCustomMpService;

    /**
     * 店员获取个人业绩信息
     */
    @ApiOperation(value = "查询店员业绩", notes = "店员获取个人业绩信息")
    @ApiResponses({@ApiResponse(code = 200, message = "操作成功")})
    @ClerkUserLogin
    @PostMapping("/user/queryPerformanceInfo")
    public R queryPerformanceInfo(
            @ApiParam(value = "业绩查询条件", required = true) @Validated @RequestBody PlayClerkWxPerformanceInfoQueryVo vo) {
        PlayClerkUserInfoEntity entity = clerkUserInfoService
                .selectById(ThreadLocalRequestDetail.getClerkUserInfo().getId());
        List<PlayClerkLevelInfoEntity> clerkLevelInfoEntity = playClerkLevelInfoService.selectAll();
        List<PlayOrderInfoEntity> orderInfoEntities = playOrderInfoService.clerkSelectOrderInfoList(entity.getId(),
                vo.getStartTime(), vo.getEndTime());
        List<PlayPersonnelGroupInfoEntity> groupInfoEntities = playPersonnelGroupInfoService.selectAll();
        return R.ok(playClerkPerformanceService.getClerkPerformanceInfo(entity, orderInfoEntities, clerkLevelInfoEntity,
                groupInfoEntities));
    }

    /**
     * 店员获取个人等级信息
     */
    @ApiOperation(value = "查询店员等级", notes = "店员获取个人等级信息")
    @ApiResponses({@ApiResponse(code = 200, message = "操作成功", response = PlayClerkLevelInfoReturnVo.class)})
    @ClerkUserLogin
    @GetMapping("/user/queryLevelInfo")
    public R queryLevelInfo() {
        PlayClerkUserInfoEntity entity = clerkUserInfoService
                .selectById(ThreadLocalRequestDetail.getClerkUserInfo().getId());
        PlayClerkLevelInfoEntity level = playClerkLevelInfoService.selectPlayClerkLevelInfoById(entity.getLevelId());
        PlayClerkLevelInfoReturnVo returnVo = new PlayClerkLevelInfoReturnVo();
        returnVo.setLevelName(level.getName());
        returnVo.setLevelId(level.getId());
        List<PlayClerkLevelAndRankingVo> levelAndRanking = new ArrayList<>();
        levelAndRanking.add(new PlayClerkLevelAndRankingVo("女神", 0, 0, 0));
        levelAndRanking.add(new PlayClerkLevelAndRankingVo("男神", 0, 0, 1));
        levelAndRanking.add(new PlayClerkLevelAndRankingVo("首席", 1, 2, 2));
        levelAndRanking.add(new PlayClerkLevelAndRankingVo("镇店", 3, 8, 3));
        levelAndRanking.add(new PlayClerkLevelAndRankingVo("金牌", 9, 18, 4));
        levelAndRanking.add(new PlayClerkLevelAndRankingVo("普通", 19, 150, 5));
        returnVo.setLevelAndRanking(levelAndRanking);
        return R.ok(returnVo);
    }

    /**
     * 店员获取个人信息
     */
    @ApiOperation(value = "查询店员信息", notes = "店员获取个人详细信息")
    @ApiResponses({@ApiResponse(code = 200, message = "操作成功", response = PlayClerkUserInfoResultVo.class),
            @ApiResponse(code = 500, message = "用户不存在")})
    @ClerkUserLogin
    @GetMapping("/user/queryById")
    public R queryById() {
        PlayClerkUserInfoEntity entity = clerkUserInfoService
                .selectById(ThreadLocalRequestDetail.getClerkUserInfo().getId());
        if (entity == null) {
            throw new CustomException("用户不存在");
        }
        return R.ok(clerkUserInfoService.getVo(entity));
    }

    @ApiOperation(value = "发送验证码", notes = "店员绑定手机号发送验证码")
    @ApiResponses({@ApiResponse(code = 200, message = "操作成功", response = String.class)})
    @ClerkUserLogin
    @PostMapping("/user/sendCode")
    public R sendCode(
            @ApiParam(value = "发送验证码信息", required = true) @Validated @RequestBody PlayClerkUserSendCodeVo vo) {
        String codeKey = "login_codes:" + SecurityUtils.getTenantId() + "_"
                + SecureUtil.md5(vo.getAreaCode() + vo.getPhone());
        String code = VerificationCodeUtils.getVerificationCode(4);
        redisCache.setCacheObject(codeKey, code, 5L, TimeUnit.MINUTES);
        // 发送验证码,
        return R.ok(code);
    }

    @ApiOperation(value = "绑定手机号", notes = "店员绑定手机号")
    @ApiResponses({@ApiResponse(code = 200, message = "操作成功"), @ApiResponse(code = 500, message = "验证码错误")})
    @ClerkUserLogin
    @PostMapping("/user/bindCode")
    public R bindCode(
            @ApiParam(value = "绑定验证码信息", required = true) @Validated @RequestBody PlayClerkUserBindCodeVo vo) {
        String codeKey = "login_codes:" + SecurityUtils.getTenantId() + "_"
                + SecureUtil.md5(vo.getAreaCode() + vo.getPhone());
        String code = redisCache.getCacheObject(codeKey);
        if (code == null || !code.equals(vo.getCode())) {
            throw new CustomException("验证码错误");
        }
        // 账号绑定操作
        String id = ThreadLocalRequestDetail.getClerkUserInfo().getId();
        PlayClerkUserInfoEntity clerkUserInfoEntity = new PlayClerkUserInfoEntity();
        clerkUserInfoEntity.setId(id);
        clerkUserInfoEntity.setPhone(vo.getPhone());
        playClerkUserInfoService.update(clerkUserInfoEntity);
        // 删除验证码缓存
        redisCache.deleteObject(codeKey);

        return R.ok("成功");
    }

    @ApiOperation(value = "店员申请", notes = "用户申请成为店员")
    @ApiResponses({@ApiResponse(code = 200, message = "操作成功"), @ApiResponse(code = 500, message = "系统错误，用户不存在"),
            @ApiResponse(code = 500, message = "当前用户已经是店员"), @ApiResponse(code = 500, message = "已有申请未审核")})
    @ClerkUserLogin
    @PostMapping("/user/add")
    @Transactional(rollbackFor = Exception.class)
    public R userAdd(@ApiParam(value = "店员申请信息", required = true) @Validated @RequestBody PlayClerkUserByWxAddVo vo) {
        String clerkId = ThreadLocalRequestDetail.getClerkUserInfo().getId();
        PlayClerkUserInfoEntity userInfo = playClerkUserInfoService.selectById(clerkId);
        if (userInfo == null) {
            throw new CustomException("系统错误，用户不存在");
        }
        if ("1".equals(userInfo.getClerkState())) {
            throw new CustomException("当前用户已经是店员");
        }
        PlayClerkUserReviewInfoEntity entity = playClerkUserReviewInfoService.queryByClerkId(userInfo.getId(), "0");
        if (entity != null) {
            throw new CustomException("已有申请未审核");
        }
        smsUtils.checkSmsCode(vo.getPhone(), vo.getSmsCode());

        entity = ConvertUtil.entityToVo(vo, PlayClerkUserReviewInfoEntity.class);
        entity.setReviewState("0");
        entity.setClerkId(clerkId);
        entity.setAddTime(LocalDateTime.now());
        playClerkUserReviewInfoService.create(entity);
        wxCustomMpService.sendClerkApplyNoticeForAdmin(entity, userInfo.getTenantId());
        return R.ok("申请成功");
    }

    @ApiOperation(value = "更新头像", notes = "店员更新头像")
    @ApiResponses({@ApiResponse(code = 200, message = "操作成功")})
    @ClerkUserLogin
    @PostMapping("/user/updateAvatar")
    public R updateAvatar(@ApiParam(value = "头像信息", required = true) @Validated @RequestBody PlayClerkUserAvatarVo vo) {
        PlayClerkUserInfoEntity userInfo = ThreadLocalRequestDetail.getClerkUserInfo();
        // PlayClerkDataReviewInfoEntity entity =
        // playClerkDataReviewInfoService.queryByClerkId(userInfo.getId(), "1", "0");
        // if (entity != null) {
        // throw new CustomException("已有申请未审核");
        // }
        PlayClerkDataReviewInfoEntity entity = new PlayClerkDataReviewInfoEntity();
        entity.setClerkId(userInfo.getId());
        entity.setDataType("1");
        entity.setReviewState("0");
        List<String> list = new ArrayList<>();
        list.add(vo.getAvatar());
        entity.setDataContent(list);
        playClerkDataReviewInfoService.create(entity);
        return R.ok("提交成功，等待审核~");
    }

    @ClerkUserLogin
    @PostMapping("/user/updateAlbum")
    public R updateAlbum(@Validated @RequestBody PlayClerkUserAlbumVo vo) {
        PlayClerkUserInfoEntity userInfo = ThreadLocalRequestDetail.getClerkUserInfo();
        // PlayClerkDataReviewInfoEntity entity =
        // playClerkDataReviewInfoService.queryByClerkId(userInfo.getId(), "2", "0");
        // if (entity != null) {
        // throw new CustomException("已有申请未审核");
        // }
        PlayClerkDataReviewInfoEntity entity = new PlayClerkDataReviewInfoEntity();
        entity.setClerkId(userInfo.getId());
        entity.setDataType("2");
        entity.setReviewState("0");
        entity.setDataContent(vo.getAlbum());
        playClerkDataReviewInfoService.create(entity);
        return R.ok("提交成功，等待审核~");
    }

    @ClerkUserLogin
    @PostMapping("/user/updateAudio")
    public R updateAudio(@Validated @RequestBody PlayClerkUserAudioVo vo) {
        PlayClerkUserInfoEntity userInfo = ThreadLocalRequestDetail.getClerkUserInfo();
        // PlayClerkDataReviewInfoEntity entity =
        // playClerkDataReviewInfoService.queryByClerkId(userInfo.getId(), "3", "0");
        // if (entity != null) {
        // throw new CustomException("已有申请未审核");
        // }
        PlayClerkDataReviewInfoEntity entity = new PlayClerkDataReviewInfoEntity();
        entity.setClerkId(userInfo.getId());
        entity.setDataType("3");
        entity.setReviewState("0");
        List<String> list = new ArrayList<>();
        list.add(vo.getAudio());
        entity.setDataContent(list);
        playClerkDataReviewInfoService.create(entity);
        return R.ok("提交成功，等待审核~");
    }

    @ClerkUserLogin
    @PostMapping("/user/updateOnlineState")
    public R updateAudio(@Validated @RequestBody PlayClerkUserOnlineStateVo vo) {
        String userId = ThreadLocalRequestDetail.getClerkUserInfo().getId();
        PlayClerkUserInfoEntity entity = new PlayClerkUserInfoEntity();
        entity.setOnlineState(vo.getOnlineState());
        entity.setId(userId);
        playClerkUserInfoService.update(entity);
        return R.ok("修改成功");
    }

    @ClerkUserLogin
    @PostMapping("/user/updateCommodity")
    public R updateAudio(@Validated @RequestBody PlayClerkCommodityEditVo vo) {
        playClerkCommodityService.startStopClerkItem(vo.getCommodityType(), vo.getEnablingState(),
                ThreadLocalRequestDetail.getClerkUserInfo().getId());
        return R.ok("成功");
    }

    @ClerkUserLogin
    @PostMapping("/user/updateOther")
    public R updateOther(@Validated @RequestBody PlayClerkUserOtherVo vo) {
        PlayClerkUserInfoEntity userInfo = ConvertUtil.entityToVo(vo, PlayClerkUserInfoEntity.class);
        userInfo.setId(ThreadLocalRequestDetail.getClerkUserInfo().getId());
        playClerkUserInfoService.update(userInfo);
        return R.ok("申请成功");
    }

    /**
     * 查询陪聊服务项目列表
     */
    @GetMapping("/user/listAllCommodity")
    public R listAllCommodity() {
        List<PlayClerkCommodityEntity> list = playClerkCommodityService.selectAll();
        return R.ok(ConvertUtil.entityToVoList(list, PlayClerkCommodityQueryVo.class));
    }

    /**
     * 分页获取店员列表
     *
     * @param vo
     *            PlayClerkUserInfoQueryVo
     * @return 店员列表
     */
    @PostMapping("/user/queryByPage")
    public R queryByPage(@RequestBody PlayClerkUserInfoQueryVo vo) {
        IPage<PlayClerkUserInfoResultVo> page = playClerkUserInfoService.selectByPage(vo,
                customUserService.getLoginUserId());
        return R.ok(page);
    }

    /**
     * 获取推荐用户列表
     *
     * @return 店员列表
     */
    @PostMapping("/user/queryByRecommend")
    public R queryByRecommend() {
        PlayClerkUserInfoQueryVo vo = new PlayClerkUserInfoQueryVo();
        vo.setPageNum(1);
        vo.setPageSize(9999);
        vo.setRecommendationState("1");
        IPage<PlayClerkUserInfoResultVo> page = playClerkUserInfoService.selectByPage(vo, "");
        return R.ok(page);
    }

    /**
     * 获取店员礼物信息
     *
     * @param id
     *            店员ID
     * @return 店员礼物
     */

    @GetMapping("/user/queryGiftById")
    public R queryGiftById(@RequestParam("id") String id) {
        PlayClerkUserInfoEntity entity = clerkUserInfoService.selectById(id);
        if (entity == null) {
            throw new CustomException("用户不存在");
        }
        // 获取所有礼物列表
        List<PlayGiftInfoEntity> giftInfoEntities = giftInfoService.listByAll();
        // 获取已点亮礼物
        List<PlayClerkGiftReturnVo> clerkListByAll = giftInfoService.clerkListByAll(id, "1");
        // 组装数据
        List<PlayClerkGiftReturnVo> result = new ArrayList<>();
        for (PlayGiftInfoEntity giftInfoEntity : giftInfoEntities) {
            PlayClerkGiftReturnVo item = ConvertUtil.entityToVo(giftInfoEntity, PlayClerkGiftReturnVo.class);
            for (PlayClerkGiftReturnVo playClerkGiftReturnVo : clerkListByAll) {
                if (giftInfoEntity.getId().equals(playClerkGiftReturnVo.getId())) {
                    item.setGiffNumber(playClerkGiftReturnVo.getGiffNumber());
                }
            }
            result.add(item);
        }
        Map<String, Object> resuleMap = new HashMap<>();
        resuleMap.put("list", result);
        resuleMap.put("obtainedGift", clerkListByAll.size());
        resuleMap.put("totalGift", giftInfoEntities.size());
        return R.ok(resuleMap);
    }

    /**
     * 获取店员价格
     *
     * @param id
     *            店员ID
     * @return 店员价格
     */
    @GetMapping("/user/queryPriceById")
    public R queryDetailById(@RequestParam("id") String id) {
        return R.ok();
    }

    /**
     * 店员分页查询本人订单列表
     *
     * @param vo
     *            订单列表分页查询对象
     * @return com.starry.common.result.R
     * @author admin
     * @since 2024/5/8 15:57
     **/
    @ClerkUserLogin
    @PostMapping("/order/queryByPage")
    public R queryOrderByPage(@Validated @RequestBody PlayClerkOrderInfoQueryVo vo) {
        vo.setAcceptBy(ThreadLocalRequestDetail.getClerkUserInfo().getId());
        IPage<PlayClerkOrderListReturnVo> iPage = playOrderInfoService.clerkSelectOrderInfoByPage(vo);
        return R.ok(iPage);
    }

    /**
     * 店员查询本人订单详情
     *
     * @param id
     *            订单ID
     * @return com.starry.common.result.R
     * @author admin
     * @since 2024/5/8 15:57
     **/
    @ClerkUserLogin
    @GetMapping("/order/queryById")
    public R queryById(@RequestParam("id") String id) {
        PlayClerkOrderDetailsReturnVo orderInfo = playOrderInfoService
                .clerkSelectOrderDetails(ThreadLocalRequestDetail.getClerkUserInfo().getId(), id);
        return R.ok(orderInfo);
    }

    /**
     * 店员-接单
     *
     * @param id
     *            订单ID
     * @return com.starry.common.result.R
     * @author admin
     * @since 2024/5/8 15:57
     **/
    @ClerkUserLogin
    @GetMapping("/order/accept")
    public R acceptOrder(@RequestParam("id") String id) {
        playOrderInfoService.updateStateTo1("1", ThreadLocalRequestDetail.getClerkUserInfo().getId(),
                ThreadLocalRequestDetail.getClerkUserInfo().getId(), id);
        return R.ok("成功");
    }

    /**
     * 店员-开始订单
     *
     * @param id
     *            订单ID
     * @return com.starry.common.result.R
     * @author admin
     * @since 2024/5/8 15:57
     **/
    @ClerkUserLogin
    @GetMapping("/order/start")
    public R startOrder(@RequestParam("id") String id) {
        playOrderInfoService.updateStateTo23("1", ThreadLocalRequestDetail.getClerkUserInfo().getId(), "2", id);
        return R.ok("成功");
    }

    /**
     * 店员-取消订单
     *
     * @param vo
     *            取消订单传参
     * @return com.starry.common.result.R
     * @author admin
     * @since 2024/5/8 15:57
     **/
    @ClerkUserLogin
    @PostMapping("/order/cancellation")
    public R endOrder(@Validated @RequestBody PlayOrderStateEditVo vo) {
        playOrderInfoService.updateStateTo4("1", ThreadLocalRequestDetail.getClerkUserInfo().getId(), vo.getOrderId(),
                vo.getRefundReason(), vo.getImages());
        return R.ok("成功");
    }

    /**
     * 分页获取店员评价（订单评价）
     *
     * @param vo
     *            店员评价查询对象
     * @return 店员评价
     */
    @PostMapping("/user/queryEvaluateByPage")
    public R queryEvaluateById(@Validated @RequestBody PlayOrderEvaluateQueryVo vo) {
        clerkUserInfoService.selectById(vo.getClerkId());
        return R.ok(playOrderEvaluateInfoService.selectByPage(vo));
    }

    @GetMapping("/level/queryAll")
    public R userAdd() {
        return R.ok(playClerkLevelInfoService.selectAll());
    }

    @GetMapping("/class/queryAll")
    public R queryClassAll() {
        return R.ok(playClerkClassificationInfoService.selectAll());
    }

}


--- File: play-admin/src/main/java/com/starry/admin/modules/weichat/controller/WxClerkLeveController.java ---
package com.starry.admin.modules.weichat.controller;

import com.starry.admin.modules.clerk.module.entity.PlayClerkLevelInfoEntity;
import com.starry.admin.modules.clerk.service.IPlayClerkLevelInfoService;
import com.starry.admin.modules.weichat.entity.level.PlayClerkLevelReturnVo;
import com.starry.common.result.R;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiResponse;
import io.swagger.annotations.ApiResponses;
import java.util.ArrayList;
import java.util.List;
import javax.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 * 店员等级
 *
 * @author admin
 * @since 2024/5/29 上午6:24
 **/
@Api(tags = "微信店员等级接口", description = "微信端店员等级相关接口")
@Slf4j
@RestController
@RequestMapping("/wx/level/")
public class WxClerkLeveController {

    @Resource
    private IPlayClerkLevelInfoService playClerkLevelInfoService;

    /**
     * 顾客查询所有店员所有等级列表
     *
     * @return 店员所有等级列表
     */
    @ApiOperation(value = "查询店员等级列表", notes = "顾客查询所有店员等级列表")
    @ApiResponses({
            @ApiResponse(code = 200, message = "操作成功", response = PlayClerkLevelReturnVo.class, responseContainer = "List")})
    @GetMapping("/custom/queryClerkAllLevel")
    public R customQueryClerkAllLevel() {
        List<PlayClerkLevelInfoEntity> list = playClerkLevelInfoService.selectAll();
        List<PlayClerkLevelReturnVo> returnVoList = new ArrayList<>(list.size());
        for (PlayClerkLevelInfoEntity entity : list) {
            PlayClerkLevelReturnVo vo = new PlayClerkLevelReturnVo();
            vo.setId(entity.getId());
            vo.setLevelName(entity.getName());
            returnVoList.add(vo);
        }
        return R.ok(returnVoList);
    }

}


--- File: play-admin/src/main/java/com/starry/admin/modules/weichat/controller/WxClerkTypeController.java ---
package com.starry.admin.modules.weichat.controller;

import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.starry.admin.modules.clerk.module.entity.PlayClerkTypeInfoEntity;
import com.starry.admin.modules.clerk.service.IPlayClerkTypeInfoService;
import com.starry.common.result.R;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiResponse;
import io.swagger.annotations.ApiResponses;
import java.util.List;
import javax.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 * 店员等级
 *
 * @author admin
 * @since 2024/5/29 上午6:24
 **/
@Api(tags = "微信店员类型接口", description = "微信端店员类型相关接口")
@Slf4j
@RestController
@RequestMapping("/wx/clerk/type")
public class WxClerkTypeController {

    @Resource
    private IPlayClerkTypeInfoService clerkTypeInfoService;

    /**
     * 顾客查询所有店员所有等级列表
     *
     * @return 店员所有等级列表
     */
    @ApiOperation(value = "查询店员类型列表", notes = "查询所有店员类型列表")
    @ApiResponses({
            @ApiResponse(code = 200, message = "操作成功", response = PlayClerkTypeInfoEntity.class, responseContainer = "List")})
    @GetMapping("/list")
    public R clerkTypeList() {
        List<PlayClerkTypeInfoEntity> list = clerkTypeInfoService.list(
                Wrappers.lambdaQuery(PlayClerkTypeInfoEntity.class).eq(PlayClerkTypeInfoEntity::getHomeDisplayed, 1)
                        .orderByDesc(PlayClerkTypeInfoEntity::getSort));
        return R.ok(list);
    }

}


--- File: play-admin/src/main/java/com/starry/admin/modules/weichat/controller/WxClerkWagesController.java ---
package com.starry.admin.modules.weichat.controller;

import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.starry.admin.common.aspect.ClerkUserLogin;
import com.starry.admin.common.conf.ThreadLocalRequestDetail;
import com.starry.admin.common.exception.CustomException;
import com.starry.admin.modules.clerk.module.entity.PlayClerkWagesDetailsInfoEntity;
import com.starry.admin.modules.clerk.module.entity.PlayClerkWagesInfoEntity;
import com.starry.admin.modules.clerk.service.IPlayClerkWagesDetailsInfoService;
import com.starry.admin.modules.clerk.service.IPlayClerkWagesInfoService;
import com.starry.admin.modules.order.module.entity.PlayOrderInfoEntity;
import com.starry.admin.modules.order.service.IPlayOrderInfoService;
import com.starry.admin.modules.weichat.entity.wages.*;
import com.starry.common.result.R;
import com.starry.common.utils.ConvertUtil;
import com.starry.common.utils.IdUtils;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiResponse;
import io.swagger.annotations.ApiResponses;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.util.ArrayList;
import java.util.List;
import javax.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.*;

/**
 * 店员工资接口
 *
 * @author admin
 * @since 2024/5/31 16:18
 **/
@Api(tags = "微信店员工资接口", description = "微信端店员工资相关接口")
@Slf4j
@RestController
@RequestMapping("/wx/wages")
public class WxClerkWagesController {
    @Resource
    private IPlayClerkWagesInfoService playClerkWagesInfoService;

    @Resource
    private IPlayClerkWagesDetailsInfoService playClerkWagesDetailsInfoService;

    @Resource
    private IPlayOrderInfoService playOrderInfoService;

    /**
     * 店员查询本人未结算工资 订单完成之后，24小时之后进行订单结算，并且一旦订单结算了，就无法进行退款。想要退款，只能在24小时之内进行。
     *
     * @author admin
     * @since 2024/5/31 16:20
     **/
    @ApiOperation(value = "查询未结算工资", notes = "店员查询本人未结算工资")
    @ApiResponses({@ApiResponse(code = 200, message = "操作成功", response = ClerkUnsettledWagesReturnVo.class)})
    @ClerkUserLogin
    @GetMapping("clerk/queryUnsettledWages")
    public R clerkQueryUnsettledWages() {
        List<PlayOrderInfoEntity> list = playOrderInfoService
                .queryBySettlementOrder(ThreadLocalRequestDetail.getClerkUserInfo().getId(), "0");
        BigDecimal orderMoney = BigDecimal.ZERO;
        BigDecimal estimatedRevenue = BigDecimal.ZERO;
        for (PlayOrderInfoEntity entity : list) {
            orderMoney = orderMoney.add(entity.getFinalAmount());
            estimatedRevenue = estimatedRevenue.add(entity.getEstimatedRevenue());
        }
        ClerkUnsettledWagesReturnVo vo = new ClerkUnsettledWagesReturnVo(orderMoney, list.size(), estimatedRevenue);
        return R.ok(vo);
    }

    /**
     * 店员查询本人当期工资
     *
     * @author admin
     * @since 2024/5/31 16:20
     **/
    @ApiOperation(value = "查询当期工资", notes = "店员查询本人当期工资")
    @ApiResponses({@ApiResponse(code = 200, message = "操作成功", response = ClerkCurrentPeriodWagesReturnVo.class)})
    @ClerkUserLogin
    @GetMapping("clerk/queryCurrentPeriodWages")
    public R clerkQueryCurrentPeriodWages() {
        PlayClerkWagesInfoEntity entity = playClerkWagesInfoService
                .selectCurrentPeriodWagesByClerkId(ThreadLocalRequestDetail.getClerkUserInfo().getId());
        if (entity == null) {
            entity = new PlayClerkWagesInfoEntity();
            entity.setClerkId(ThreadLocalRequestDetail.getClerkUserInfo().getId());
            entity.setId(IdUtils.getUuid());
            entity.setStartCountDate(LocalDate.now());
            entity.setEndCountDate(LocalDate.now());
            entity.setFinalAmount(BigDecimal.ZERO);
            entity.setOrderNumber(0);
            entity.setOrdersExpiredNumber(0);
            entity.setOrderContinueNumber(0);
            entity.setOrderContinueProportion(0);
            entity.setOrderContinueMoney(BigDecimal.ZERO);
        }
        ClerkCurrentPeriodWagesReturnVo returnVo = ConvertUtil.entityToVo(entity,
                ClerkCurrentPeriodWagesReturnVo.class);
        returnVo.setTotalMoney(entity.getFinalAmount());
        returnVo.setOrderWages(ConvertUtil.entityToVo(entity, OrderWagesReturnVo.class));
        return R.ok(returnVo);
    }

    /**
     * 店员查询本人历史工资
     *
     * @author admin
     * @since 2024/5/31 16:20
     **/
    @ApiOperation(value = "查询历史工资", notes = "店员查询本人历史工资")
    @ApiResponses({
            @ApiResponse(code = 200, message = "操作成功", response = ClerkHistoricalWagesReturnVo.class, responseContainer = "Page")})
    @ClerkUserLogin
    @PostMapping("clerk/queryHistoricalWages")
    public R clerkQueryHistoricalWages() {
        List<PlayClerkWagesInfoEntity> list = playClerkWagesInfoService
                .selectHistoricalWagesByClerkId(ThreadLocalRequestDetail.getClerkUserInfo().getId());
        List<ClerkHistoricalWagesReturnVo> returnVoList = new ArrayList<>(list.size());
        for (PlayClerkWagesInfoEntity entity : list) {
            returnVoList.add(ConvertUtil.entityToVo(entity, ClerkHistoricalWagesReturnVo.class));
        }
        IPage<ClerkHistoricalWagesReturnVo> page = new Page<>();
        page.setTotal(5);
        page.setRecords(returnVoList);
        page.setSize(10);
        page.setPages(1);
        return R.ok(page);

    }

    /**
     * 店员查询本人工资详情
     *
     * @author admin
     * @since 2024/5/31 16:20
     **/
    @ApiOperation(value = "查询工资详情", notes = "店员查询本人工资详情")
    @ApiImplicitParam(name = "id", value = "工资ID", required = true, dataType = "String", paramType = "query")
    @ApiResponses({
            @ApiResponse(code = 200, message = "操作成功", response = ClerkWagesDetailsReturnVo.class, responseContainer = "List"),
            @ApiResponse(code = 500, message = "ID不能为空")})
    @ClerkUserLogin
    @GetMapping("clerk/queryWagesDetails")
    public R clerkQueryWagesDetails(@RequestParam("id") String id) {
        if (StrUtil.isBlankIfStr(id)) {
            throw new CustomException("ID不能为空");
        }
        List<PlayClerkWagesDetailsInfoEntity> list = playClerkWagesDetailsInfoService.selectByWagesId(id);
        List<ClerkWagesDetailsReturnVo> returnVos = new ArrayList<>(list.size());
        for (PlayClerkWagesDetailsInfoEntity entity : list) {
            returnVos.add(ConvertUtil.entityToVo(entity, ClerkWagesDetailsReturnVo.class));
        }
        return R.ok(returnVos);
    }
}


--- File: play-admin/src/main/java/com/starry/admin/modules/weichat/controller/WxCommodityController.java ---
package com.starry.admin.modules.weichat.controller;

import com.starry.admin.modules.clerk.module.entity.PlayClerkCommodityEntity;
import com.starry.admin.modules.clerk.service.IPlayClerkCommodityService;
import com.starry.admin.modules.weichat.entity.PlayClerkCommodityTreeData;
import com.starry.common.result.R;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiResponse;
import io.swagger.annotations.ApiResponses;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import javax.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

/**
 * @author admin
 */
@Api(tags = "微信商品接口", description = "微信端商品相关接口")
@Slf4j
@RestController
@RequestMapping("/wx/commodity/")
public class WxCommodityController {
    @Resource
    private IPlayClerkCommodityService clerkCommodityService;

    @ApiOperation(value = "获取商品树", notes = "根据用户ID获取商品树形结构")
    @ApiImplicitParam(name = "userId", value = "用户ID", required = true, dataType = "String", paramType = "query")
    @ApiResponses({
            @ApiResponse(code = 200, message = "操作成功", response = PlayClerkCommodityTreeData.class, responseContainer = "List")})
    @GetMapping("/getTree")
    public R getTree(@RequestParam("userId") String userId) {
        List<PlayClerkCommodityEntity> commodityEntities = clerkCommodityService.selectByUser(userId);
        Map<String, List<PlayClerkCommodityEntity>> item = commodityEntities.stream()
                .collect(Collectors.groupingBy(PlayClerkCommodityEntity::getCommodityName));
        List<PlayClerkCommodityTreeData> result = new ArrayList<>();
        for (Map.Entry<String, List<PlayClerkCommodityEntity>> entry : item.entrySet()) {

            PlayClerkCommodityTreeData treeData = new PlayClerkCommodityTreeData(entry.getKey(), entry.getValue());
            result.add(treeData);
        }
        return R.ok(result);
    }
}


--- File: play-admin/src/main/java/com/starry/admin/modules/weichat/controller/WxCommonController.java ---
package com.starry.admin.modules.weichat.controller;

import cn.hutool.core.io.FileUtil;
import cn.hutool.core.util.StrUtil;
import com.starry.admin.common.exception.CustomException;
import com.starry.admin.common.oss.service.IOssFileService;
import com.starry.admin.modules.shop.service.IShopUiSettingService;
import com.starry.admin.modules.system.service.ISysAdministrativeAreaDictInfoService;
import com.starry.admin.modules.weichat.service.WxAccessTokenService;
import com.starry.admin.modules.weichat.utils.WxFileUtils;
import com.starry.admin.utils.SecurityUtils;
import com.starry.common.result.R;
import com.starry.common.utils.IdUtils;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiParam;
import io.swagger.annotations.ApiResponse;
import io.swagger.annotations.ApiResponses;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import javax.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import me.chanjar.weixin.common.error.WxErrorException;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;

/**
 * @author admin
 * @since 2024/4/10 16:18
 **/
@Api(tags = "微信通用接口", description = "微信公共功能相关接口")
@Slf4j
@RestController
@RequestMapping("/wx/common/")
public class WxCommonController {

    @Resource
    private IOssFileService ossFileService;

    @Resource
    private ISysAdministrativeAreaDictInfoService areaDictInfoService;
    @Resource
    WxAccessTokenService wxAccessTokenService;
    @Resource
    private IShopUiSettingService shopUiSettingService;

    @ApiOperation(value = "获取行政区域树", notes = "获取省市区县的树形结构数据")
    @ApiResponses({@ApiResponse(code = 200, message = "操作成功")})
    @GetMapping("area/tree")
    public R areaTree() {
        return R.ok(areaDictInfoService.selectTree("2"));
    }

    @ApiOperation(value = "获取店铺UI设置", notes = "获取当前店铺的UI配置信息")
    @ApiResponses({@ApiResponse(code = 200, message = "操作成功")})
    @GetMapping("setting/info")
    public R selectShopUiSettingInfo() {
        return R.ok(shopUiSettingService.selectShopUiSettingInfo());
    }

    @ApiOperation(value = "上传文件", notes = "上传文件到OSS存储")
    @ApiResponses({@ApiResponse(code = 200, message = "操作成功，返回文件访问地址")})
    @PostMapping("file/upload")
    public R fileUpload(@ApiParam(value = "上传的文件", required = true) @RequestParam("file") MultipartFile file)
            throws IOException {
        String fileAddress = ossFileService.upload(file.getInputStream(), SecurityUtils.getTenantId(),
                file.getOriginalFilename());
        return R.ok(fileAddress);
    }

    @ApiOperation(value = "上传音频", notes = "通过微信媒体ID上传音频文件，自动转换为MP3格式")
    @ApiImplicitParam(name = "mediaId", value = "微信媒体ID", required = true, dataType = "String", paramType = "query")
    @ApiResponses({@ApiResponse(code = 200, message = "操作成功，返回文件访问地址"),
            @ApiResponse(code = 500, message = "上传失败，mediaId为空或文件不存在")})
    @GetMapping("audio/upload")
    public R audioUpload(@RequestParam("mediaId") String mediaId) throws IOException, WxErrorException {
        if (StrUtil.isBlankIfStr(mediaId)) {
            throw new CustomException("mediaId不能为空");
        }
        String accessToken = wxAccessTokenService.getAccessToken();
        // 下载录音文件，并转化为InputStream
        InputStream inputStream = WxFileUtils.getTemporaryMaterial(accessToken, mediaId);
        File tempFile = FileUtil.createTempFile("wx_" + SecurityUtils.getTenantId(), IdUtils.getUuid() + ".amr", null)
                .toFile();
        // 可以在这里对临时文件进行操作
        log.error("tempFile = {}", tempFile.getPath());
        try (FileOutputStream fileOutputStream = new FileOutputStream(tempFile)) {
            byte[] buffer = new byte[1024];
            int bytesRead;
            // 读取InputStream并写入到FileOutputStream
            while ((bytesRead = inputStream.read(buffer)) != -1) {
                fileOutputStream.write(buffer, 0, bytesRead);
            }
        }
        if (FileUtil.size(tempFile) <= 0) {
            throw new CustomException("音频文件上传失败,文件不存在");
        }
        // 将下载的微信素材文件，转化为MP3文件
        File targetFile = FileUtil.createTempFile("wx_" + SecurityUtils.getTenantId(), IdUtils.getUuid() + ".mp3", null)
                .toFile();
        log.error("targetFile = {}", targetFile.getPath());
        WxFileUtils.audioConvert2Mp3(tempFile, targetFile);
        // 将MP3文件上传到OSS
        String fileAddress = ossFileService.upload(Files.newInputStream(targetFile.toPath()),
                SecurityUtils.getTenantId(), IdUtils.getUuid() + ".mp3");
        // 删除临时文件
        FileUtil.del(tempFile);
        FileUtil.del(targetFile);
        return R.ok(fileAddress);
    }
}


--- File: play-admin/src/main/java/com/starry/admin/modules/weichat/controller/WxCouponController.java ---
package com.starry.admin.modules.weichat.controller;

import cn.hutool.core.util.StrUtil;
import com.alibaba.fastjson2.JSON;
import com.starry.admin.common.aspect.CustomUserLogin;
import com.starry.admin.common.conf.ThreadLocalRequestDetail;
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.custom.module.entity.PlayCustomUserInfoEntity;
import com.starry.admin.modules.shop.module.entity.PlayCouponInfoEntity;
import com.starry.admin.modules.shop.module.vo.PlayCommodityInfoVo;
import com.starry.admin.modules.shop.module.vo.PlayCouponDetailsReturnVo;
import com.starry.admin.modules.shop.service.IPlayCommodityInfoService;
import com.starry.admin.modules.shop.service.IPlayCouponDetailsService;
import com.starry.admin.modules.shop.service.IPlayCouponInfoService;
import com.starry.admin.modules.weichat.entity.WxCouponOrderQueryVo;
import com.starry.admin.modules.weichat.entity.WxCouponOrderReturnVo;
import com.starry.admin.modules.weichat.entity.WxCouponReceiveReturnVo;
import com.starry.common.result.R;
import com.starry.common.utils.ConvertUtil;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiParam;
import io.swagger.annotations.ApiResponse;
import io.swagger.annotations.ApiResponses;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;

/**
 * @author admin
 * @since 2024/7/4 11:33
 **/
@Api(tags = "微信优惠券接口", description = "微信端优惠券相关接口")
@Slf4j
@RestController
@RequestMapping("/wx/coupon/")
public class WxCouponController {

    @Resource
    IPlayCouponDetailsService couponDetailsService;

    @Resource
    IPlayClerkUserInfoService playClerkUserInfoService;

    @Resource
    IPlayCouponInfoService couponInfoService;

    @Resource
    private IPlayCommodityInfoService playCommodityInfoService;

    @ApiOperation(value = "领取优惠券", notes = "顾客领取指定ID的优惠券")
    @ApiImplicitParam(name = "id", value = "优惠券ID", required = true, dataType = "String", paramType = "query")
    @ApiResponses({@ApiResponse(code = 200, message = "操作成功", response = Map.class),
            @ApiResponse(code = 500, message = "请求参数异常,优惠券ID不能为空")})
    @CustomUserLogin
    @GetMapping("/custom/obtainCoupon")
    public R queryAll(@RequestParam("id") String id) {
        PlayCustomUserInfoEntity customUserInfo = ThreadLocalRequestDetail.getCustomUserInfo();
        if (StrUtil.isEmpty(id)) {
            throw new CustomException("请求参数异常,优惠券ID不能为");
        }
        PlayCouponInfoEntity entity = couponInfoService.selectPlayCouponInfoById(id);
        String msg = couponInfoService.getReasonForNotObtainingCoupons(entity, customUserInfo);
        boolean success = StrUtil.isBlank(msg);
        // 优惠券领取验证通过，发放优惠券
        if (success) {
            couponDetailsService.create(customUserInfo.getId(), customUserInfo.getNickname(),
                    customUserInfo.getLevelId(), entity.getId(), "1", "1");
        }
        Map<String, Object> result = new HashMap<>();
        result.put("success", success);
        result.put("msg", msg);
        return R.ok(result);
    }

    /**
     * 查询所有优惠券，以及本人是否可领取
     *
     * @return com.starry.common.result.R
     * @author admin
     * @since 2024/7/8 13:53
     **/
    @ApiOperation(value = "查询所有优惠券", notes = "查询所有优惠券并标记用户是否已领取")
    @ApiResponses({
            @ApiResponse(code = 200, message = "操作成功", response = WxCouponReceiveReturnVo.class, responseContainer = "List")})
    @CustomUserLogin
    @PostMapping("/custom/queryAll")
    public R queryAll() {
        List<PlayCouponDetailsReturnVo> obtainedCoupons = couponDetailsService
                .selectByCustomId(ThreadLocalRequestDetail.getCustomUserInfo().getId());
        List<PlayCouponInfoEntity> couponInfoEntities = couponInfoService.queryAll();
        List<WxCouponReceiveReturnVo> returnVos = new ArrayList<>(couponInfoEntities.size());
        for (PlayCouponInfoEntity couponInfoEntity : couponInfoEntities) {
            if ("0".equals(couponInfoEntity.getCouponOnLineState())) {
                continue;
            }
            WxCouponReceiveReturnVo vo = ConvertUtil.entityToVo(couponInfoEntity, WxCouponReceiveReturnVo.class);
            for (PlayCouponDetailsReturnVo obtainedCoupon : obtainedCoupons) {
                if (obtainedCoupon.getCouponId().equals(couponInfoEntity.getId())) {
                    vo.setObtained("1");
                }
            }
            returnVos.add(vo);
        }
        return R.ok(returnVos);
    }

    /**
     * 顾客查询订单可使用优惠券
     */
    @ApiOperation(value = "查询订单可用优惠券", notes = "查询当前订单可使用的优惠券列表")
    @ApiResponses({
            @ApiResponse(code = 200, message = "操作成功", response = WxCouponOrderReturnVo.class, responseContainer = "List"),
            @ApiResponse(code = 500, message = "请求参数异常,店员ID不能为空,等级ID不能为空")})
    @CustomUserLogin
    @PostMapping("/custom/queryByOrder")
    public R queryByOrder(
            @ApiParam(value = "订单查询条件", required = true) @Validated @RequestBody WxCouponOrderQueryVo vo) {
        if (vo.getLevelId().isEmpty() && vo.getClerkId().isEmpty()) {
            throw new CustomException("请求参数异常,店员ID不能为空,等级ID不能为空");
        }
        String levelId = vo.getLevelId();
        if (!vo.getClerkId().isEmpty()) {
            PlayClerkUserInfoEntity clerkUserInfo = playClerkUserInfoService.selectById(vo.getClerkId());
            levelId = clerkUserInfo.getLevelId();
        }
        log.info(JSON.toJSONString(vo));
        List<PlayCouponDetailsReturnVo> list = couponDetailsService
                .selectByCustomId(ThreadLocalRequestDetail.getCustomUserInfo().getId());
        PlayCommodityInfoVo commodityInfo = playCommodityInfoService.queryCommodityInfo(vo.getCommodityId(), levelId);
        // 优惠券列表
        List<WxCouponOrderReturnVo> couponReturnVos = new ArrayList<>();
        for (PlayCouponDetailsReturnVo couponDetails : list) {
            PlayCouponInfoEntity couponInfo = couponInfoService.selectPlayCouponInfoById(couponDetails.getCouponId());
            WxCouponOrderReturnVo wxCouponReturnVo = ConvertUtil.entityToVo(couponDetails, WxCouponOrderReturnVo.class);
            String couponReasonForUnavailableUse = couponInfoService.getCouponReasonForUnavailableUse(couponInfo,
                    vo.getPlaceType(), vo.getCommodityId(), vo.getCommodityQuantity(),
                    commodityInfo.getCommodityPrice());
            if (StrUtil.isEmpty(couponReasonForUnavailableUse)) {
                wxCouponReturnVo.setAvailable("1");
            } else {
                wxCouponReturnVo.setAvailable("0");
                wxCouponReturnVo.setReasonForUnavailableUse(couponReasonForUnavailableUse);
            }
            couponReturnVos.add(wxCouponReturnVo);
        }
        return R.ok(couponReturnVos);
    }

}


--- File: play-admin/src/main/java/com/starry/admin/modules/weichat/controller/WxCustomController.java ---
package com.starry.admin.modules.weichat.controller;

import cn.hutool.core.util.StrUtil;
import com.alibaba.fastjson2.JSONObject;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.starry.admin.common.aspect.CustomUserLogin;
import com.starry.admin.common.conf.ThreadLocalRequestDetail;
import com.starry.admin.common.exception.CustomException;
import com.starry.admin.common.exception.ServiceException;
import com.starry.admin.common.task.OverdueOrderHandlerTask;
import com.starry.admin.modules.clerk.module.entity.PlayClerkUserInfoEntity;
import com.starry.admin.modules.clerk.service.IPlayClerkCommodityService;
import com.starry.admin.modules.clerk.service.IPlayClerkUserInfoService;
import com.starry.admin.modules.custom.module.entity.PlayCustomGiftInfoEntity;
import com.starry.admin.modules.custom.module.entity.PlayCustomLeaveMsgEntity;
import com.starry.admin.modules.custom.module.entity.PlayCustomUserInfoEntity;
import com.starry.admin.modules.custom.service.IPlayCustomFollowInfoService;
import com.starry.admin.modules.custom.service.IPlayCustomGiftInfoService;
import com.starry.admin.modules.custom.service.IPlayCustomLeaveMsgService;
import com.starry.admin.modules.custom.service.IPlayCustomUserInfoService;
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.PlayOrderInfoEntity;
import com.starry.admin.modules.order.service.IPlayOrderComplaintInfoService;
import com.starry.admin.modules.order.service.IPlayOrderEvaluateInfoService;
import com.starry.admin.modules.order.service.IPlayOrderInfoService;
import com.starry.admin.modules.shop.module.entity.PlayClerkGiftInfoEntity;
import com.starry.admin.modules.shop.module.entity.PlayCouponDetailsEntity;
import com.starry.admin.modules.shop.module.entity.PlayCouponInfoEntity;
import com.starry.admin.modules.shop.module.entity.PlayGiftInfoEntity;
import com.starry.admin.modules.shop.module.vo.PlayCommodityInfoVo;
import com.starry.admin.modules.shop.service.*;
import com.starry.admin.modules.weichat.entity.*;
import com.starry.admin.modules.weichat.entity.clerk.PlayClerkUserInfoResultVo;
import com.starry.admin.modules.weichat.entity.costom.PlayCustomHideLevelStateEditVo;
import com.starry.admin.modules.weichat.entity.costom.PlayCustomHideRankingStateEditVo;
import com.starry.admin.modules.weichat.entity.evaluate.PlayCustomOrderEvaluateReturnVo;
import com.starry.admin.modules.weichat.entity.order.PlayCustomOrderDetailsReturnVo;
import com.starry.admin.modules.weichat.entity.order.PlayCustomOrderInfoQueryVo;
import com.starry.admin.modules.weichat.entity.order.PlayCustomOrderListReturnVo;
import com.starry.admin.modules.weichat.entity.order.PlayOrderInfoCommodityAdd;
import com.starry.admin.modules.weichat.entity.user.PlayCustomUserReturnDetailVo;
import com.starry.admin.modules.weichat.service.WxCustomMpService;
import com.starry.admin.modules.weichat.service.WxCustomUserService;
import com.starry.admin.utils.MoneyUtils;
import com.starry.admin.utils.SecurityUtils;
import com.starry.common.result.R;
import com.starry.common.utils.ConvertUtil;
import com.starry.common.utils.IdUtils;
import com.starry.common.utils.StringUtils;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiParam;
import io.swagger.annotations.ApiResponse;
import io.swagger.annotations.ApiResponses;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Date;
import java.util.Objects;
import javax.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;

/**
 * @author admin
 */
@Api(tags = "微信顾客接口", description = "微信端顾客相关接口")
@Slf4j
@RestController
@RequestMapping("/wx/custom/")
public class WxCustomController {

    @Resource
    private IPlayCustomUserInfoService customUserInfoService;

    @Resource
    private IPlayClerkUserInfoService clerkUserInfoService;

    @Resource
    private IPlayGiftInfoService giftInfoService;

    @Resource
    private IPlayCommodityInfoService playCommodityInfoService;

    @Resource
    private IPlayCustomFollowInfoService playCustomFollowInfoService;

    @Resource
    private IPlayClerkCommodityService playClerkCommodityService;

    @Resource
    private WxCustomUserService customUserService;

    @Resource
    private IPlayOrderInfoService playOrderInfoService;

    @Resource
    private IPlayCustomLeaveMsgService playCustomLeaveMsgService;

    @Resource
    private IPlayOrderComplaintInfoService playOrderComplaintInfoService;

    @Resource
    private IPlayOrderEvaluateInfoService playOrderEvaluateInfoService;

    @Resource
    private IPlayCustomGiftInfoService playCustomGiftInfoService;

    @Resource
    private IPlayClerkGiftInfoService playClerkGiftInfoService;

    @Resource
    private IPlayCouponInfoService playCouponInfoService;
    @Resource
    private IPlayCouponDetailsService couponDetailsService;

    @Resource
    private WxCustomMpService wxCustomMpService;

    @Resource
    OverdueOrderHandlerTask overdueOrderHandlerTask;

    /**
     * 根据店员ID查询店员详细信息
     *
     * @param id
     *            店员ID
     * @return 店员详细信息
     */
    @ApiOperation(value = "查询店员详细信息", notes = "根据店员ID查询店员的详细信息")
    @ApiImplicitParam(name = "id", value = "店员ID", required = true, dataType = "String", paramType = "query")
    @ApiResponses({@ApiResponse(code = 200, message = "操作成功", response = PlayClerkUserInfoResultVo.class)})
    @GetMapping("/queryClerkDetailedById")
    public R queryClerkDetailedById(@RequestParam("id") String id) {
        PlayClerkUserInfoEntity entity = clerkUserInfoService.selectById(id);
        PlayClerkUserInfoResultVo vo = ConvertUtil.entityToVo(entity, PlayClerkUserInfoResultVo.class);
        vo.setAddress(entity.getCity());
        // 查询是否关注，未登录情况下，默认为未关注
        String loginUserId = customUserService.getLoginUserId();
        if (StringUtils.isNotEmpty(loginUserId)) {
            vo.setFollowState(playCustomFollowInfoService.queryFollowState(loginUserId, vo.getId()));
        }
        // 服务项目
        vo.setCommodity(playClerkCommodityService.getClerkCommodityList(vo.getId(), "1"));
        return R.ok(vo);
    }

    /**
     * 顾客本人刷新头像
     */
    @ApiOperation(value = "刷新顾客头像", notes = "刷新当前登录顾客的头像信息")
    @ApiResponses({@ApiResponse(code = 200, message = "操作成功")})
    @CustomUserLogin
    @GetMapping("/refreshAvatar")
    public R refreshAvatar() {
        return R.ok("");
    }

    /**
     * 根据顾客ID查询当前顾客详细信息
     */
    @ApiOperation(value = "查询顾客详细信息", notes = "查询当前登录顾客的详细信息")
    @ApiResponses({@ApiResponse(code = 200, message = "操作成功", response = PlayCustomUserReturnDetailVo.class)})
    @CustomUserLogin
    @GetMapping("/queryById")
    public R queryById() {
        String userId = ThreadLocalRequestDetail.getCustomUserInfo().getId();
        PlayCustomUserInfoEntity customUserInfo = customUserInfoService.selectById(userId);
        PlayCustomUserReturnDetailVo vo = ConvertUtil.entityToVo(customUserInfo, PlayCustomUserReturnDetailVo.class);
        PlayClerkUserInfoEntity clerkUserInfo = clerkUserInfoService.selectByOpenid(customUserInfo.getOpenid());
        if (clerkUserInfo != null) {
            vo.setClerkState("1");
        }
        return R.ok(vo);
    }

    /**
     * 顾客本人修改隐藏等级状态
     */
    @ApiOperation(value = "修改隐藏等级状态", notes = "顾客修改自己的隐藏等级状态")
    @ApiResponses({@ApiResponse(code = 200, message = "操作成功")})
    @CustomUserLogin
    @PostMapping("/updateHideLevelState")
    public R updateHideLevelState(
            @ApiParam(value = "隐藏等级状态信息", required = true) @Validated @RequestBody PlayCustomHideLevelStateEditVo vo) {
        vo.setId(ThreadLocalRequestDetail.getCustomUserInfo().getId());
        PlayCustomUserInfoEntity entity = new PlayCustomUserInfoEntity();
        BeanUtils.copyProperties(vo, entity);
        customUserInfoService.update(entity);
        return R.ok("成功");
    }

    /**
     * 顾客本人修改隐藏排名状态
     */
    @ApiOperation(value = "修改隐藏排名状态", notes = "顾客修改自己的隐藏排名状态")
    @ApiResponses({@ApiResponse(code = 200, message = "操作成功")})
    @CustomUserLogin
    @PostMapping("/updateHideRankingState")
    public R updateHideRankingState(
            @ApiParam(value = "隐藏排名状态信息", required = true) @Validated @RequestBody PlayCustomHideRankingStateEditVo vo) {
        vo.setId(ThreadLocalRequestDetail.getCustomUserInfo().getId());
        PlayCustomUserInfoEntity entity = new PlayCustomUserInfoEntity();
        BeanUtils.copyProperties(vo, entity);
        customUserInfoService.update(entity);
        return R.ok("成功");
    }

    /**
     * 打赏店员-打赏余额
     */
    @ApiOperation(value = "打赏店员", notes = "使用余额打赏店员")
    @ApiResponses({@ApiResponse(code = 200, message = "操作成功"), @ApiResponse(code = 998, message = "余额不足")})
    @CustomUserLogin
    @PostMapping("/order/reward")
    public R rewardToOrder(
            @ApiParam(value = "打赏信息", required = true) @Validated @RequestBody PlayOrderInfoRewardAdd vo) {
        MoneyUtils.verificationTypeIsNormal(vo.getMoney());
        String userId = ThreadLocalRequestDetail.getCustomUserInfo().getId();
        PlayCustomUserInfoEntity customUserInfo = customUserInfoService.selectById(userId);
        if (new BigDecimal(vo.getMoney()).compareTo(customUserInfo.getAccountBalance()) > 0) {
            throw new ServiceException("余额不足", 998);
        }
        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(), "",
                "", "");
        // 顾客减少余额
        customUserInfoService.updateAccountBalanceById(customUserInfo.getId(), customUserInfo.getAccountBalance(),
                customUserInfo.getAccountBalance().subtract(new BigDecimal(vo.getMoney())), "1", "打赏",
                new BigDecimal(vo.getMoney()), BigDecimal.ZERO, orderId);
        return R.ok("成功");
    }

    /**
     * 顾客下单-赠送礼物
     **/
    @ApiOperation(value = "赠送礼物", notes = "顾客向店员赠送礼物")
    @ApiResponses({@ApiResponse(code = 200, message = "操作成功"), @ApiResponse(code = 998, message = "余额不足")})
    @CustomUserLogin
    @PostMapping("/order/gift")
    public R giftToOdder(@ApiParam(value = "礼物信息", required = true) @Validated @RequestBody PlayOrderInfoGiftAdd vo) {
        String userId = ThreadLocalRequestDetail.getCustomUserInfo().getId();
        PlayGiftInfoEntity giftInfo = giftInfoService.selectPlayGiftInfoById(vo.getGiftId());
        PlayCustomUserInfoEntity customUserInfo = customUserInfoService.selectById(userId);
        BigDecimal money = giftInfo.getPrice().multiply(new BigDecimal(vo.getGiftQuantity()));
        if (money.compareTo(customUserInfo.getAccountBalance()) > 0) {
            throw new ServiceException("余额不足", 998);
        }
        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(), "", "", "");
        // 顾客减少余额
        customUserInfoService.updateAccountBalanceById(customUserInfo.getId(), customUserInfo.getAccountBalance(),
                customUserInfo.getAccountBalance().subtract(money), "1", "赠送礼物", money, BigDecimal.ZERO, orderId);
        // 陪聊增加余额
        // 修改顾客和礼物消息
        PlayCustomGiftInfoEntity customGiftInfoEntity = playCustomGiftInfoService
                .selectByGiftIdAndCustomId(vo.getGiftId(), userId);
        if (customGiftInfoEntity == null) {
            customGiftInfoEntity = new PlayCustomGiftInfoEntity();
            customGiftInfoEntity.setGiffId(vo.getGiftId());
            customGiftInfoEntity.setCustomId(userId);
            customGiftInfoEntity.setGiffNumber(1L);
            playCustomGiftInfoService.save(customGiftInfoEntity);
        } else {
            customGiftInfoEntity.setGiffNumber(customGiftInfoEntity.getGiffNumber() + vo.getGiftQuantity());
            playCustomGiftInfoService.update(customGiftInfoEntity);
        }

        // 修改陪玩和礼物数据
        PlayClerkGiftInfoEntity clerkGiftInfoEntity = playClerkGiftInfoService.selectByGiftIdAndClerkId(vo.getGiftId(),
                vo.getClerkId());
        if (clerkGiftInfoEntity == null) {
            clerkGiftInfoEntity = new PlayClerkGiftInfoEntity();
            clerkGiftInfoEntity.setGiffId(vo.getGiftId());
            clerkGiftInfoEntity.setClerkId(vo.getClerkId());
            clerkGiftInfoEntity.setGiffNumber(0L);
            playClerkGiftInfoService.create(clerkGiftInfoEntity);
        } else {
            customGiftInfoEntity.setGiffNumber(customGiftInfoEntity.getGiffNumber() + vo.getGiftQuantity());
            playClerkGiftInfoService.update(clerkGiftInfoEntity);
        }

        return R.ok("成功");
    }

    /**
     * 新增指定订单
     *
     * @param vo
     *            指定订单查询对象
     * @return com.starry.common.result.R
     * @author admin
     * @since 2024/5/8 16:31
     **/
    @ApiOperation(value = "商品下单", notes = "顾客购买店员服务商品")
    @ApiResponses({@ApiResponse(code = 200, message = "操作成功"), @ApiResponse(code = 998, message = "余额不足"),
            @ApiResponse(code = 500, message = "请求参数异常")})
    @CustomUserLogin
    @PostMapping("/order/commodity")
    public R commodityToOrdder(
            @ApiParam(value = "商品订单信息", required = true) @Validated @RequestBody PlayOrderInfoCommodityAdd vo) {
        String customId = ThreadLocalRequestDetail.getCustomUserInfo().getId();
        PlayClerkUserInfoEntity clerkUserInfo = clerkUserInfoService.selectById(vo.getClerkId());

        PlayCommodityInfoVo commodityInfo = playCommodityInfoService.queryCommodityInfo(vo.getCommodityId(),
                clerkUserInfo.getLevelId());

        BigDecimal couponMoney = BigDecimal.ZERO;
        for (String couponId : vo.getCouponIds()) {
            PlayCouponDetailsEntity couponDetailsEntity = couponDetailsService.getById(couponId);
            if (Objects.isNull(couponDetailsEntity)) {
                throw new CustomException("优惠券不存在");
            }
            PlayCouponInfoEntity couponInfo = playCouponInfoService
                    .selectPlayCouponInfoById(couponDetailsEntity.getCouponId());
            String couponReasonForUnavailableUse = playCouponInfoService.getCouponReasonForUnavailableUse(couponInfo,
                    "0", vo.getCommodityId(), vo.getCommodityQuantity(), commodityInfo.getCommodityPrice());
            if (StrUtil.isNotBlank(couponReasonForUnavailableUse)) {
                throw new CustomException("优惠券不可用");
            }
            if (couponInfo.getDiscountType().equals("0")) {
                couponMoney = couponMoney.add(couponInfo.getDiscountAmount());
            }
        }

        PlayCustomUserInfoEntity customUserInfo = customUserInfoService.selectById(customId);

        BigDecimal money = commodityInfo.getCommodityPrice().multiply(new BigDecimal(vo.getCommodityQuantity()));

        money = money.subtract(couponMoney);
        if (money.compareTo(BigDecimal.ZERO) < 1) {
            money = BigDecimal.ZERO;
        }

        if (money.compareTo(customUserInfo.getAccountBalance()) > 0) {
            throw new ServiceException("余额不足", 998);
        }
        String orderId = IdUtils.getUuid();
        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(), "", "", "");
        // 顾客减少余额
        customUserInfoService.updateAccountBalanceById(customUserInfo.getId(), customUserInfo.getAccountBalance(),
                customUserInfo.getAccountBalance().subtract(money), "1", "下单-指定单", money, BigDecimal.ZERO, orderId);
        // 发送通知给店员
        wxCustomMpService.sendCreateOrderMessage(clerkUserInfo.getTenantId(), clerkUserInfo.getOpenid(), orderNo,
                money.toString(), commodityInfo.getCommodityName());
        return R.ok("成功");
    }

    /**
     * 新增随机订单
     *
     * @param vo
     *            随机单创建对象
     * @return R
     */
    @CustomUserLogin
    @PostMapping("/order/random")
    public R randomToOrdder(@Validated @RequestBody PlayOrderInfoRandomAdd vo) {
        // 验证当前顾客余额是否充足
        String customId = ThreadLocalRequestDetail.getCustomUserInfo().getId();
        PlayCustomUserInfoEntity customUserInfo = customUserInfoService.selectById(customId);
        PlayCommodityInfoVo commodityInfo = playCommodityInfoService.queryCommodityInfo(vo.getCommodityId(),
                vo.getLevelId());
        BigDecimal money = commodityInfo.getCommodityPrice().multiply(new BigDecimal(vo.getCommodityQuantity()));
        if (money.compareTo(customUserInfo.getAccountBalance()) > 0) {
            throw new ServiceException("余额不足", 998);
        }
        String orderId = IdUtils.getUuid();
        // 记录订单信息
        playOrderInfoService.createOrderInfo(orderId, playOrderInfoService.getOrderNo(), "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());
        // 顾客减少余额
        customUserInfoService.updateAccountBalanceById(customUserInfo.getId(), customUserInfo.getAccountBalance(),
                customUserInfo.getAccountBalance().subtract(money), "1", "下单-随机单", money, BigDecimal.ZERO, orderId);
        // 记录订单，指定指定未接单后，进行退款处理
        overdueOrderHandlerTask.enqueue(orderId + "_" + SecurityUtils.getTenantId());
        // 下单成功后，先根据用户条件进行随机分配
        return R.ok("下单成功");
    }

    /**
     * 顾客分页查询本人订单列表
     *
     * @param vo
     *            订单列表分页查询对象
     * @return com.starry.common.result.R
     * @author admin
     * @since 2024/5/8 15:57
     **/
    @CustomUserLogin
    @PostMapping("/order/queryByPage")
    public R queryOrderByPage(@Validated @RequestBody PlayCustomOrderInfoQueryVo vo) {
        vo.setPurchaserBy(ThreadLocalRequestDetail.getCustomUserInfo().getId());
        IPage<PlayCustomOrderListReturnVo> iPage = playOrderInfoService.customSelectOrderInfoByPage(vo);
        return R.ok(iPage);
    }

    /**
     * 顾客分页查询本人订单详细信息
     *
     * @param id
     *            订单ID
     * @return com.starry.common.result.R
     * @author admin
     * @since 2024/5/8 15:57
     **/
    @CustomUserLogin
    @GetMapping("/order/queryById")
    public R queryById(@RequestParam("id") String id) {
        PlayCustomOrderDetailsReturnVo vo = playOrderInfoService
                .customSelectOrderDetails(ThreadLocalRequestDetail.getCustomUserInfo().getId(), id);
        return R.ok(vo);
    }

    /**
     * 顾客-完成订单
     *
     * @param id
     *            订单ID
     * @return com.starry.common.result.R
     * @author admin
     * @since 2024/5/8 15:57
     **/
    @CustomUserLogin
    @GetMapping("/order/end")
    public R endOrder(@RequestParam("id") String id) {
        playOrderInfoService.updateStateTo23("0", ThreadLocalRequestDetail.getCustomUserInfo().getId(), "3", id);
        return R.ok("成功");
    }

    /**
     * 顾客取消订单
     *
     * @param vo
     *            订单取消对象
     * @return com.starry.common.result.R
     * @author admin
     * @since 2024/5/8 15:57
     **/
    @CustomUserLogin
    @PostMapping("/order/cancellation")
    public R cancellationOrder(@Validated @RequestBody PlayOrderCancellationVo vo) {
        playOrderInfoService.updateStateTo4("0", ThreadLocalRequestDetail.getCustomUserInfo().getId(), vo.getOrderId(),
                vo.getRefundReason(), vo.getImages());
        return R.ok("取消成功");
    }

    /**
     * 新增订单评价信息
     *
     * @param vo
     *            订单评价信息实体
     * @return com.starry.common.result.R
     * @author admin
     * @since 2024/5/8 15:57
     **/
    @CustomUserLogin
    @PostMapping("/order/evaluate/add")
    public R addOrderEvaluate(@Validated @RequestBody PlayOrderEvaluateAddVo vo) {
        vo.setCustomId(ThreadLocalRequestDetail.getCustomUserInfo().getId());
        PlayOrderInfoEntity orderInfo = playOrderInfoService.selectOrderInfoById(vo.getOrderId());
        if (!orderInfo.getPurchaserBy().equals(vo.getCustomId())) {
            throw new CustomException("只有下单人才能评价");
        }
        PlayOrderEvaluateInfoEntity entity = ConvertUtil.entityToVo(vo, PlayOrderEvaluateInfoEntity.class);
        playOrderEvaluateInfoService.create(entity);
        return R.ok("评价成功");
    }

    /**
     * 根据订单ID查询当前顾客对当前人的评价
     *
     * @param orderId
     *            订单ID
     * @return com.starry.common.result.R
     * @author admin
     * @since 2024/5/8 15:57
     **/
    @CustomUserLogin
    @GetMapping("/order/evaluate/queryByOrderId")
    public R addOrderEvaluateByOrderId(@RequestParam("id") String orderId) {
        PlayOrderEvaluateInfoEntity orderEvaluateInfo = playOrderEvaluateInfoService
                .queryCustomToOrderEvaluateInfo(ThreadLocalRequestDetail.getCustomUserInfo().getId(), orderId);
        if (orderEvaluateInfo == null) {
            throw new CustomException("当前订单未评价");
        }
        return R.ok(ConvertUtil.entityToVo(orderEvaluateInfo, PlayCustomOrderEvaluateReturnVo.class));
    }

    /**
     * 新增订单投诉信息
     *
     * @param vo
     *            订单评价信息实体
     * @return com.starry.common.result.R
     * @author admin
     * @since 2024/5/8 15:57
     **/
    @CustomUserLogin
    @PostMapping("/order/complaint/add")
    public R addOrderComplaint(@Validated @RequestBody PlayOrderComplaintAddVo vo) {
        vo.setCustomId(ThreadLocalRequestDetail.getCustomUserInfo().getId());
        PlayOrderInfoEntity orderInfo = playOrderInfoService.selectOrderInfoById(vo.getOrderId());
        if (!orderInfo.getPurchaserBy().equals(vo.getCustomId())) {
            throw new CustomException("只有下单人才能投诉");
        }
        PlayOrderComplaintInfoEntity entity = ConvertUtil.entityToVo(vo, PlayOrderComplaintInfoEntity.class);
        playOrderComplaintInfoService.create(entity);
        return R.ok("新增成功");
    }

    /**
     * 顾客新增留言
     *
     * @param vo
     *            顾客新增留言对象
     * @return com.starry.common.result.R
     * @author admin
     * @since 2024/5/8 15:57
     **/
    @CustomUserLogin
    @PostMapping("/leave/add")
    public R cancellationOrder(@Validated @RequestBody PlayCustomLeaveAddVo vo) {
        vo.setCustomId(ThreadLocalRequestDetail.getCustomUserInfo().getId());
        PlayCustomLeaveMsgEntity entity = ConvertUtil.entityToVo(vo, PlayCustomLeaveMsgEntity.class);
        entity.setMsgTime(new Date());
        playCustomLeaveMsgService.create(entity);
        return R.ok("取消成功");
    }

    /**
     * 查询当前顾客是否允许留言
     *
     * @return com.starry.common.result.R
     * @author admin
     * @since 2024/5/8 15:57
     **/
    @CustomUserLogin
    @GetMapping("/leave/queryPermission")
    public R queryLeavePermission() {
        // 不允许留言的原因，如果原因为空，说明允许留言
        String noMessagesAllowedReason = playCustomLeaveMsgService
                .queryNoMessagesAllowedReason(ThreadLocalRequestDetail.getCustomUserInfo().getId());
        if (StringUtils.isEmpty(noMessagesAllowedReason)) {
            JSONObject jsonObject = new JSONObject();
            jsonObject.put("permission", true);
            jsonObject.put("msg", "");
            return R.ok(jsonObject);
        } else {
            JSONObject jsonObject = new JSONObject();
            jsonObject.put("permission", false);
            jsonObject.put("msg", noMessagesAllowedReason);
            return R.ok(jsonObject);
        }
    }

    /**
     * 顾客修改对陪玩的关注状态
     *
     * @param vo
     *            陪玩的关注状态对象
     * @return com.starry.common.result.R
     * @author admin
     * @since 2024/5/8 15:57
     **/
    @CustomUserLogin
    @PostMapping("/followState/update")
    public R followStateUpdate(@Validated @RequestBody PlayClerkFollowStateUpdate vo) {
        PlayClerkUserInfoEntity clerkUserInfo = clerkUserInfoService.selectById(vo.getClerkId());
        playCustomFollowInfoService.updateFollowState(ThreadLocalRequestDetail.getCustomUserInfo().getId(),
                clerkUserInfo.getId(), vo.getFollowState());
        return R.ok("修改成功");
    }

    /**
     * 顾客查询已关注陪聊列表
     *
     * @param vo
     *            陪玩的关注状态对象
     * @return com.starry.common.result.R
     * @author admin
     * @since 2024/5/8 15:57
     **/
    @CustomUserLogin
    @PostMapping("/follow/queryByPage")
    public R followQueryByPage(@Validated @RequestBody PlayClerkFollowQueryVo vo) {
        vo.setCustomId(ThreadLocalRequestDetail.getCustomUserInfo().getId());
        IPage<PlayClerkFollowReturnVo> iPage = playCustomFollowInfoService.selectByPage(vo);
        return R.ok(iPage);
    }

}


--- File: play-admin/src/main/java/com/starry/admin/modules/weichat/controller/WxGiftController.java ---
package com.starry.admin.modules.weichat.controller;

import cn.hutool.core.util.StrUtil;
import com.starry.admin.common.aspect.ClerkUserLogin;
import com.starry.admin.common.aspect.CustomUserLogin;
import com.starry.admin.common.conf.ThreadLocalRequestDetail;
import com.starry.admin.common.exception.CustomException;
import com.starry.admin.modules.shop.service.IPlayGiftInfoService;
import com.starry.admin.modules.weichat.entity.PlayGiftInfoDto;
import com.starry.admin.modules.weichat.entity.gift.PlayClerkGiftReturnVo;
import com.starry.common.result.R;
import com.starry.common.utils.ConvertUtil;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiResponse;
import io.swagger.annotations.ApiResponses;
import java.util.List;
import javax.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

/**
 * @author admin
 */
@Api(tags = "微信礼物接口", description = "微信端礼物相关接口")
@Slf4j
@RestController
@RequestMapping("/wx/gift/")
public class WxGiftController {
    @Resource
    private IPlayGiftInfoService giftInfoService;

    /**
     * 顾客查询礼物列表
     *
     * @return 礼物列表
     */
    @ApiOperation(value = "查询所有礼物", notes = "查询系统中所有礼物列表")
    @ApiResponses({
            @ApiResponse(code = 200, message = "操作成功", response = PlayGiftInfoDto.class, responseContainer = "List")})
    @GetMapping("/listByAll")
    public R customListByAll() {
        return R.ok(ConvertUtil.entityToVoList(giftInfoService.listByAll(), PlayGiftInfoDto.class));
    }

    /**
     * 顾客查询礼物列表
     *
     * @param obtained
     *            店员获得礼物状态,[0:未获得,1:已获得]
     * @return 礼物列表
     */
    @ApiOperation(value = "店员查询礼物", notes = "根据获得状态查询店员礼物列表")
    @ApiImplicitParam(name = "obtained", value = "获得状态[0:未获得,1:已获得]", required = true, dataType = "String", paramType = "query", allowableValues = "0,1")
    @ApiResponses({
            @ApiResponse(code = 200, message = "操作成功", response = PlayClerkGiftReturnVo.class, responseContainer = "List"),
            @ApiResponse(code = 500, message = "obtained参数异常")})
    @ClerkUserLogin
    @GetMapping("/clerk/listByAll")
    public R clerkListByAll(@RequestParam("obtained") String obtained) {
        if (StrUtil.isBlankIfStr(obtained)) {
            throw new CustomException("obtained参数异常");
        }
        if (!"0".equals(obtained) && !"1".equals(obtained)) {
            throw new CustomException("obtained参数异常");
        }
        List<PlayClerkGiftReturnVo> list = giftInfoService
                .clerkListByAll(ThreadLocalRequestDetail.getClerkUserInfo().getId(), obtained);
        return R.ok(list);
    }

    /**
     * 顾客获取礼物图鉴信息
     *
     * @param vo
     *            顾客获得礼物查询对象
     * @return 礼物列表
     */
    @ApiOperation(value = "顾客查询礼物", notes = "根据获得状态查询顾客礼物列表")
    @ApiImplicitParam(name = "obtained", value = "获得状态[0:未获得,1:已获得]", required = true, dataType = "String", paramType = "query", allowableValues = "0,1")
    @ApiResponses({
            @ApiResponse(code = 200, message = "操作成功", response = PlayClerkGiftReturnVo.class, responseContainer = "List"),
            @ApiResponse(code = 500, message = "obtained参数异常")})
    @CustomUserLogin
    @GetMapping("/custom/listByAll")
    public R customListByAll(@RequestParam("obtained") String obtained) {
        if (StrUtil.isBlankIfStr(obtained)) {
            throw new CustomException("obtained参数异常");
        }
        if (!"0".equals(obtained) && !"1".equals(obtained)) {
            throw new CustomException("obtained参数异常");
        }
        List<PlayClerkGiftReturnVo> list = giftInfoService
                .customListByAll(ThreadLocalRequestDetail.getCustomUserInfo().getId(), obtained);
        return R.ok(list);
    }

}


--- File: play-admin/src/main/java/com/starry/admin/modules/weichat/controller/WxLevelController.java ---
package com.starry.admin.modules.weichat.controller;

import com.starry.admin.common.aspect.CustomUserLogin;
import com.starry.admin.modules.custom.module.entity.PlayCustomLevelInfoEntity;
import com.starry.admin.modules.custom.service.IPlayCustomLevelInfoService;
import com.starry.admin.modules.weichat.entity.vo.PlayCustomLevelInfoReturnVo;
import com.starry.common.result.R;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiResponse;
import io.swagger.annotations.ApiResponses;
import java.util.ArrayList;
import java.util.List;
import javax.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 * @author admin
 */
@Api(tags = "微信等级接口", description = "微信端用户等级相关接口")
@Slf4j
@RestController
@RequestMapping("/wx/level/")
public class WxLevelController {

    @Resource
    private IPlayCustomLevelInfoService levelInfoService;

    /**
     * 顾客查询登记列表
     */
    @ApiOperation(value = "查询等级列表", notes = "查询所有顾客等级列表")
    @ApiResponses({
            @ApiResponse(code = 200, message = "操作成功", response = PlayCustomLevelInfoReturnVo.class, responseContainer = "List")})
    @CustomUserLogin
    @GetMapping("/custom/queryAll")
    public R queryById() {
        List<PlayCustomLevelInfoEntity> list = levelInfoService.selectAll();
        List<PlayCustomLevelInfoReturnVo> result = new ArrayList<>();
        for (PlayCustomLevelInfoEntity entity : list) {
            PlayCustomLevelInfoReturnVo vo = new PlayCustomLevelInfoReturnVo();
            BeanUtils.copyProperties(entity, vo);
            result.add(vo);
        }
        return R.ok(result);
    }

}


--- File: play-admin/src/main/java/com/starry/admin/modules/weichat/controller/WxOauthController.java ---
package com.starry.admin.modules.weichat.controller;

import static com.starry.common.constant.Constants.*;

import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.StrUtil;
import com.alibaba.fastjson2.JSONObject;
import com.starry.admin.common.aspect.ClerkUserLogin;
import com.starry.admin.common.aspect.CustomUserLogin;
import com.starry.admin.common.conf.ThreadLocalRequestDetail;
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.custom.module.entity.PlayCustomUserInfoEntity;
import com.starry.admin.modules.custom.service.IPlayCustomUserInfoService;
import com.starry.admin.modules.weichat.entity.WxUserLoginVo;
import com.starry.admin.modules.weichat.entity.WxUserQueryAddressVo;
import com.starry.admin.modules.weichat.service.WxCustomMpService;
import com.starry.admin.modules.weichat.service.WxOauthService;
import com.starry.admin.modules.weichat.service.WxTokenService;
import com.starry.common.redis.RedisCache;
import com.starry.common.result.R;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiParam;
import io.swagger.annotations.ApiResponse;
import io.swagger.annotations.ApiResponses;
import java.util.Date;
import javax.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import me.chanjar.weixin.common.bean.WxJsapiSignature;
import me.chanjar.weixin.common.error.WxErrorException;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;

/**
 * 微信用户
 *
 * @author admin
 * @since 2019-03-25 15:39:39
 */
@Api(tags = "微信授权接口", description = "微信端用户授权相关接口")
@Slf4j
@RestController
@RequestMapping("/wx/oauth2")
public class WxOauthController {

    @Resource
    private WxCustomMpService wxCustomMpService;
    @Resource
    private IPlayCustomUserInfoService customUserInfoService;
    @Resource
    private IPlayClerkUserInfoService clerkUserInfoService;
    @Resource
    private WxTokenService tokenService;
    @Resource
    private WxOauthService wxOauthService;
    @Resource
    private RedisCache redisCache;

    @ApiOperation(value = "获取配置地址", notes = "获取微信JSAPI配置签名")
    @ApiResponses({@ApiResponse(code = 200, message = "操作成功", response = WxJsapiSignature.class)})
    @PostMapping("/getConfigAddress")
    public R getConfigAddress(@ApiParam(value = "地址查询信息", required = true) @RequestBody WxUserQueryAddressVo vo)
            throws WxErrorException {
        // 默认回调地址
        String defaultAddress = "https://julyharbor.com/api/wx/oauth2/clerkLoginCallback";
        if (!StrUtil.isBlankIfStr(vo.getUrl())) {
            defaultAddress = vo.getUrl();
        }
        WxJsapiSignature wxJsapiSignature = wxCustomMpService.proxyWxMpService().createJsapiSignature(defaultAddress);
        return R.ok(wxJsapiSignature);
    }

    @ApiOperation(value = "获取店员登录地址", notes = "获取微信店员授权登录地址")
    @ApiResponses({@ApiResponse(code = 200, message = "操作成功", response = String.class)})
    @PostMapping("/getClerkLoginAddress")
    public R getClerkLoginAddress(@ApiParam(value = "地址查询信息", required = true) @RequestBody WxUserQueryAddressVo vo) {
        // 默认回调地址
        String defaultAddress = "https://julyharbor.com/api/wx/oauth2/clerkLoginCallback";
        if (!StrUtil.isBlankIfStr(vo.getUrl())) {
            defaultAddress = vo.getUrl();
        }
        String url = wxCustomMpService.proxyWxMpService().getOAuth2Service().buildAuthorizationUrl(defaultAddress,
                "snsapi_userinfo", "STATE");
        return R.ok(url);
    }

    @ApiOperation(value = "店员登录回调", notes = "微信店员授权登录回调处理")
    @ApiImplicitParam(name = "code", value = "授权码", required = true, dataType = "String", paramType = "query")
    @GetMapping("/clerkLoginCallback")
    public void clerkLoginCallback(@RequestParam("code") String code) {
    }

    @ApiOperation(value = "店员登录", notes = "微信店员授权登录")
    @ApiResponses({@ApiResponse(code = 200, message = "操作成功", response = JSONObject.class),
            @ApiResponse(code = 401, message = "登录失败"), @ApiResponse(code = 500, message = "用户不存在")})
    @PostMapping("/clerk/login")
    public R clerkLogin(@ApiParam(value = "登录信息", required = true) @Validated @RequestBody WxUserLoginVo vo) {
        try {
            String userId = wxOauthService.clerkUserLogin(vo.getCode());
            PlayClerkUserInfoEntity entity = clerkUserInfoService.selectById(userId);
            if (entity == null) {
                throw new CustomException("用户不存在");
            }
            // 缓存租户信息
            String redisKey = "TENANT_INFO:" + entity.getId();
            redisCache.setCacheObject(redisKey, entity.getTenantId());
            String tokenValue = tokenService.createWxUserToken(entity.getId());
            JSONObject jsonObject = JSONObject.from(clerkUserInfoService.getVo(entity));
            jsonObject.put("tokenValue", TOKEN_PREFIX + tokenValue);
            jsonObject.put("tokenName", CLERK_USER_LOGIN_TOKEN);
            jsonObject.put("loginDate", DateUtil.format(new Date(), "yyyy-MM-dd HH:mm:ss"));
            jsonObject.put("pcData", clerkUserInfoService.getPcData(entity));
            clerkUserInfoService.updateTokenById(entity.getId(), tokenValue);
            return R.ok(jsonObject);
        } catch (Exception e) {
            log.error("顾客登录失败，", e);
            return R.unauthorized();
        }
    }

    @ApiOperation(value = "店员开发登录", notes = "微信店员开发环境登录（固定用户）")
    @ApiResponses({@ApiResponse(code = 200, message = "操作成功", response = JSONObject.class),
            @ApiResponse(code = 401, message = "登录失败"), @ApiResponse(code = 500, message = "用户不存在")})
    @PostMapping("/clerk/login/dev")
    public R clerkLoginDev(@ApiParam(value = "登录信息", required = true) @Validated @RequestBody WxUserLoginVo vo) {
        try {
            String userId = "a4471ef596a1";
            PlayClerkUserInfoEntity entity = clerkUserInfoService.selectById(userId);
            if (entity == null) {
                throw new CustomException("用户不存在");
            }
            // 缓存租户信息
            String redisKey = "TENANT_INFO:" + entity.getId();
            redisCache.setCacheObject(redisKey, entity.getTenantId());
            String tokenValue = tokenService.createWxUserToken(entity.getId());
            JSONObject jsonObject = JSONObject.from(clerkUserInfoService.getVo(entity));
            jsonObject.put("tokenValue", TOKEN_PREFIX + tokenValue);
            jsonObject.put("tokenName", CLERK_USER_LOGIN_TOKEN);
            jsonObject.put("loginDate", DateUtil.format(new Date(), "yyyy-MM-dd HH:mm:ss"));
            jsonObject.put("pcData", clerkUserInfoService.getPcData(entity));
            clerkUserInfoService.updateTokenById(entity.getId(), tokenValue);
            return R.ok(jsonObject);
        } catch (Exception e) {
            log.error("顾客登录失败，", e);
            return R.unauthorized();
        }
    }

    @ApiOperation(value = "店员ID登录", notes = "根据店员ID直接登录")
    @ApiResponses({@ApiResponse(code = 200, message = "操作成功", response = JSONObject.class),
            @ApiResponse(code = 500, message = "用户不存在")})
    @PostMapping("/clerk/loginById")
    public R loginById(@ApiParam(value = "登录信息", required = true) @Validated @RequestBody WxUserLoginVo vo) {
        PlayClerkUserInfoEntity entity = clerkUserInfoService.selectById(vo.getCode());
        if (entity == null) {
            throw new CustomException("用户不存在");
        }
        // 缓存租户信息
        String redisKey = "TENANT_INFO:" + entity.getId();
        redisCache.setCacheObject(redisKey, entity.getTenantId());
        JSONObject jsonObject = JSONObject.from(clerkUserInfoService.getVo(entity));
        String tokenValue = tokenService.createWxUserToken(entity.getId());
        jsonObject.put("tokenValue", TOKEN_PREFIX + tokenValue);
        jsonObject.put("tokenName", CLERK_USER_LOGIN_TOKEN);
        jsonObject.put("loginDate", DateUtil.format(new Date(), "yyyy-MM-dd HH:mm:ss"));
        clerkUserInfoService.updateTokenById(entity.getId(), tokenValue);
        return R.ok(jsonObject);
    }

    @ApiOperation(value = "店员登出", notes = "店员退出登录")
    @ApiResponses({@ApiResponse(code = 200, message = "登出成功")})
    @ClerkUserLogin
    @GetMapping("/clerk/logout")
    public R clerkLogout() {
        wxOauthService.clerkUserLogout(ThreadLocalRequestDetail.getClerkUserInfo());
        return R.ok("登出成功");
    }

    @ApiOperation(value = "获取顾客登录地址", notes = "获取微信顾客授权登录地址")
    @ApiResponses({@ApiResponse(code = 200, message = "操作成功", response = String.class)})
    @PostMapping("/getCustomLoginAddress")
    public R getCustomLoginAddress(@ApiParam(value = "地址查询信息", required = true) @RequestBody WxUserQueryAddressVo vo) {
        // 默认回调地址
        String defaultAddress = "https://julyharbor.com/api/wx/oauth2/customLoginCallback";
        if (!StrUtil.isBlankIfStr(vo.getUrl())) {
            defaultAddress = vo.getUrl();
        }
        String url = wxCustomMpService.proxyWxMpService().getOAuth2Service().buildAuthorizationUrl(defaultAddress,
                "snsapi_userinfo", "STATE");
        return R.ok(url);
    }

    @ApiOperation(value = "顾客登录回调", notes = "微信顾客授权登录回调处理")
    @ApiImplicitParam(name = "code", value = "授权码", required = true, dataType = "String", paramType = "query")
    @GetMapping("/customLoginCallback")
    public void customLoginCallback(@RequestParam("code") String code) {

    }

    @ApiOperation(value = "顾客登录", notes = "微信顾客授权登录")
    @ApiResponses({@ApiResponse(code = 200, message = "操作成功", response = JSONObject.class),
            @ApiResponse(code = 401, message = "登录失败"), @ApiResponse(code = 500, message = "用户不存在")})
    @PostMapping("/custom/login")
    public R customLogin(@ApiParam(value = "登录信息", required = true) @Validated @RequestBody WxUserLoginVo vo) {
        log.info("顾客登录接口调用，code = {}", vo.getCode());
        try {
            String userId = wxOauthService.customUserLogin(vo.getCode());
            PlayCustomUserInfoEntity entity = customUserInfoService.selectById(userId);
            if (entity == null) {
                throw new CustomException("用户不存在");
            }
            // 缓存租户信息
            String redisKey = "TENANT_INFO:" + entity.getId();
            redisCache.setCacheObject(redisKey, entity.getTenantId());
            JSONObject jsonObject = JSONObject.from(entity);
            String tokenValue = tokenService.createWxUserToken(entity.getId());
            jsonObject.put("tokenValue", TOKEN_PREFIX + tokenValue);
            jsonObject.put("tokenName", CUSTOM_USER_LOGIN_TOKEN);
            jsonObject.put("loginDate", DateUtil.format(new Date(), "yyyy-MM-dd HH:mm:ss"));
            customUserInfoService.updateTokenById(entity.getId(), tokenValue);
            return R.ok(jsonObject);
        } catch (Exception e) {
            log.error("登录失败", e);
            return R.unauthorized();
        }
    }

    @ApiOperation(value = "顾客ID登录", notes = "根据顾客ID直接登录")
    @ApiResponses({@ApiResponse(code = 200, message = "操作成功", response = JSONObject.class),
            @ApiResponse(code = 500, message = "用户不存在")})
    @PostMapping("/custom/loginById")
    public R loginById1(@ApiParam(value = "登录信息", required = true) @Validated @RequestBody WxUserLoginVo vo) {
        PlayCustomUserInfoEntity entity = customUserInfoService.selectById(vo.getCode());
        if (entity == null) {
            throw new CustomException("用户不存在");
        }
        // 缓存租户信息
        String redisKey = "TENANT_INFO:" + entity.getId();
        redisCache.setCacheObject(redisKey, entity.getTenantId());
        JSONObject jsonObject = JSONObject.from(entity);
        String tokenValue = tokenService.createWxUserToken(entity.getId());
        jsonObject.put("tokenValue", TOKEN_PREFIX + tokenValue);
        jsonObject.put("tokenName", CLERK_USER_LOGIN_TOKEN);
        jsonObject.put("loginDate", DateUtil.format(new Date(), "yyyy-MM-dd HH:mm:ss"));
        customUserInfoService.updateTokenById(entity.getId(), tokenValue);
        return R.ok(jsonObject);
    }

    @ApiOperation(value = "顾客登出", notes = "顾客退出登录")
    @ApiResponses({@ApiResponse(code = 200, message = "登出成功")})
    @GetMapping("/custom/logout")
    @CustomUserLogin
    public R customLogout() {
        wxOauthService.customUserLogout(ThreadLocalRequestDetail.getCustomUserInfo());
        return R.ok("登出成功");
    }

}


--- File: play-admin/src/main/java/com/starry/admin/modules/weichat/controller/WxOrderInfoController.java ---
package com.starry.admin.modules.weichat.controller;

import com.baomidou.mybatisplus.core.metadata.IPage;
import com.starry.admin.common.aspect.ClerkUserLogin;
import com.starry.admin.common.aspect.CustomUserLogin;
import com.starry.admin.common.conf.ThreadLocalRequestDetail;
import com.starry.admin.common.exception.CustomException;
import com.starry.admin.modules.order.module.entity.PlayOrderContinueInfoEntity;
import com.starry.admin.modules.order.module.entity.PlayOrderInfoEntity;
import com.starry.admin.modules.order.module.vo.PlayOrderContinueQueryVo;
import com.starry.admin.modules.order.module.vo.PlayOrderContinueReturnVo;
import com.starry.admin.modules.order.module.vo.PlayOrderDetailsReturnVo;
import com.starry.admin.modules.order.module.vo.PlayOrderReviewStateEditVo;
import com.starry.admin.modules.order.service.IPlayOrderContinueInfoService;
import com.starry.admin.modules.order.service.IPlayOrderInfoService;
import com.starry.admin.modules.weichat.entity.order.PlayOrderInfoContinueAdd;
import com.starry.admin.modules.weichat.entity.order.PlayOrderInfoContinueQueryVo;
import com.starry.admin.modules.weichat.entity.order.PlayOrderInfoRandomQueryVo;
import com.starry.admin.modules.weichat.entity.order.PlayRewardOrderQueryVo;
import com.starry.common.result.R;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiParam;
import io.swagger.annotations.ApiResponse;
import io.swagger.annotations.ApiResponses;
import java.time.LocalDateTime;
import javax.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;

/**
 * 订单处理
 *
 * @author admin
 * @since 2024/5/25 下午2:45
 **/
@Api(tags = "微信订单接口", description = "微信端订单相关接口")
@Slf4j
@RestController
@RequestMapping("/wx/order")
public class WxOrderInfoController {

    @Resource
    private IPlayOrderInfoService playOrderInfoService;

    @Resource
    private IPlayOrderContinueInfoService playOrderContinueInfoService;

    /**
     * 续单申请-店员发起
     *
     * @param vo
     *            续单申请提交对象
     */
    @ApiOperation(value = "店员申请续单", notes = "店员为当前订单发起续单申请")
    @ApiResponses({@ApiResponse(code = 200, message = "操作成功"), @ApiResponse(code = 500, message = "非本人订单;无法续单"),
            @ApiResponse(code = 500, message = "同一场订单只能续单一次")})
    @ClerkUserLogin
    @PostMapping("/clerk/continue")
    public R continueToOrdder(
            @ApiParam(value = "续单信息", required = true) @Validated @RequestBody PlayOrderInfoContinueAdd vo) {
        PlayOrderInfoEntity entity = playOrderInfoService.selectOrderInfoById(vo.getOrderId());
        if (!entity.getAcceptBy().equals(ThreadLocalRequestDetail.getClerkUserInfo().getId())) {
            throw new CustomException("非本人订单;无法续单");
        }

        PlayOrderContinueInfoEntity orderContinueInfo = playOrderContinueInfoService.selectPlayOrderId(vo.getOrderId());
        if (orderContinueInfo != null) {
            throw new CustomException("同一场订单只能续单一次");
        }
        PlayOrderContinueInfoEntity orderContinueInfoEntity = new PlayOrderContinueInfoEntity();
        orderContinueInfoEntity.setOrderId(entity.getId());
        orderContinueInfoEntity.setOrderNo(entity.getOrderNo());
        orderContinueInfoEntity.setCustomId(entity.getPurchaserBy());
        orderContinueInfoEntity.setClerkId(entity.getAcceptBy());
        orderContinueInfoEntity.setPlaceType(entity.getPlaceType());
        orderContinueInfoEntity.setOrderMoney(entity.getOrderMoney());
        orderContinueInfoEntity.setFinalAmount(entity.getFinalAmount());
        orderContinueInfoEntity.setContinueMsg(entity.getRemark());
        orderContinueInfoEntity.setReviewedRequired("1");
        orderContinueInfoEntity.setReviewedState("0");
        orderContinueInfoEntity.setImages(vo.getImages());
        orderContinueInfoEntity.setContinueTime(LocalDateTime.now());
        playOrderContinueInfoService.create(orderContinueInfoEntity);
        return R.ok("下单成功");
    }

    /**
     * 店员查询随机单列表
     *
     * @param vo
     *            随机单列表查询对象
     * @return 订单列表
     */
    @ApiOperation(value = "查询随机单列表", notes = "店员查询可接的随机订单列表")
    @ApiResponses({@ApiResponse(code = 200, message = "操作成功")})
    @ClerkUserLogin
    @PostMapping("/clerk/selectRandomOrderByPage")
    public R selectUnacceptedOrderByPage(
            @ApiParam(value = "查询条件", required = true) @Validated @RequestBody PlayOrderInfoRandomQueryVo vo) {
        return R.ok(
                playOrderInfoService.selectRandomOrderByPage(vo, ThreadLocalRequestDetail.getClerkUserInfo().getId()));
    }

    /**
     * 店员查询随机单详情
     *
     * @param id
     *            订单ID
     * @return 订单列表
     */
    @ApiOperation(value = "查询随机单详情", notes = "店员查询随机订单的详细信息")
    @ApiImplicitParam(name = "id", value = "订单ID", required = true, dataType = "String", paramType = "query")
    @ApiResponses({@ApiResponse(code = 200, message = "操作成功", response = PlayOrderDetailsReturnVo.class),
            @ApiResponse(code = 500, message = "订单不存在")})
    @ClerkUserLogin
    @GetMapping("/clerk/selectRandomOrderById")
    public R selectUnacceptedOrderByPage(@RequestParam("id") String id) {
        PlayOrderDetailsReturnVo vo = playOrderInfoService.selectById(id);
        if (vo == null) {
            throw new CustomException("订单不存在");
        }
        return R.ok(vo);
    }

    /**
     * 店员查询打赏动态
     *
     * @param vo
     *            打赏动态查询列表
     * @return 打赏动态列表
     */
    @ApiOperation(value = "店员查询打赏动态", notes = "店员查询打赏订单动态")
    @ApiResponses({@ApiResponse(code = 200, message = "操作成功")})
    @PostMapping("/clerk/selectRewardByPage")
    public R clerkSelectRewardByPage(
            @ApiParam(value = "查询条件", required = true) @Validated @RequestBody PlayRewardOrderQueryVo vo) {
        return R.ok(playOrderInfoService.selectRewardByPage(vo));
    }

    /**
     * 顾客查询最新打赏动态
     *
     * @return 打赏动态列表
     */
    @ApiOperation(value = "顾客查询打赏动态", notes = "顾客查询最新打赏订单动态")
    @ApiResponses({@ApiResponse(code = 200, message = "操作成功")})
    @GetMapping("/custom/selectRewardByPage")
    public R customSelectReward() {
        PlayRewardOrderQueryVo vo = new PlayRewardOrderQueryVo();
        return R.ok(playOrderInfoService.selectRewardByPage(vo));
    }

    /**
     * 顾客-分页查询续单列表
     *
     * @param vo
     *            PlayOrderInfoContinueQueryVo
     * @return 续单历史
     */
    @ApiOperation(value = "查询续单列表", notes = "顾客分页查询续单申请列表")
    @ApiResponses({
            @ApiResponse(code = 200, message = "操作成功", response = PlayOrderContinueReturnVo.class, responseContainer = "Page")})
    @CustomUserLogin
    @PostMapping("/custom/continueListByPage")
    public R continueListByPage(
            @ApiParam(value = "查询条件", required = true) @Validated @RequestBody PlayOrderInfoContinueQueryVo vo) {
        PlayOrderContinueQueryVo queryVo = new PlayOrderContinueQueryVo();
        BeanUtils.copyProperties(vo, queryVo);
        queryVo.setCustomId(ThreadLocalRequestDetail.getCustomUserInfo().getId());
        IPage<PlayOrderContinueReturnVo> page = playOrderContinueInfoService.selectPlayByPage(queryVo);
        return R.ok(page);
    }

    /**
     * 审批续单申申请
     */
    @ApiOperation(value = "审批续单申请", notes = "顾客审批店员发起的续单申请")
    @ApiResponses({@ApiResponse(code = 200, message = "操作成功"), @ApiResponse(code = 500, message = "续单已处理")})
    @CustomUserLogin
    @PostMapping("/custom/updateReviewState")
    public R updateReviewState(
            @ApiParam(value = "审批信息", required = true) @Validated @RequestBody PlayOrderReviewStateEditVo vo) {
        PlayOrderContinueInfoEntity entity = playOrderContinueInfoService.selectPlayOrderContinueInfoById(vo.getId());
        if (!"0".equals(entity.getReviewedState())) {
            throw new CustomException("续单已处理");
        }
        playOrderContinueInfoService.updateReviewState(vo);
        return R.ok("成功");
    }

}


--- File: play-admin/src/main/java/com/starry/admin/modules/weichat/controller/WxPersonnelGroupInfoController.java ---
package com.starry.admin.modules.weichat.controller;

import com.starry.admin.common.aspect.ClerkUserLogin;
import com.starry.admin.modules.personnel.module.entity.PlayPersonnelGroupInfoEntity;
import com.starry.admin.modules.personnel.service.IPlayPersonnelGroupInfoService;
import com.starry.admin.modules.weichat.entity.PlayGroupWagesQueryVo;
import com.starry.admin.modules.weichat.entity.PlayGroupWagesReturnVo;
import com.starry.common.result.R;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiParam;
import io.swagger.annotations.ApiResponse;
import io.swagger.annotations.ApiResponses;
import java.util.ArrayList;
import java.util.List;
import javax.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 * 微信查询组长工资
 *
 * @author admin
 * @since 2024/7/6 下午3:46
 **/
@Api(tags = "微信分组管理", description = "微信端店员分组相关接口")
@Slf4j
@RestController
@RequestMapping("/wx/group")
public class WxPersonnelGroupInfoController {

    @Resource
    private IPlayPersonnelGroupInfoService playClerkGroupInfoService;

    /**
     * 查询组长公工资
     */
    @ApiOperation(value = "查询分组工资", notes = "获取所有店员分组的工资信息")
    @ApiResponses({
            @ApiResponse(code = 200, message = "操作成功", response = PlayGroupWagesReturnVo.class, responseContainer = "List")})
    @ClerkUserLogin
    @PostMapping("/wages/listAll")
    public R listGroupWagesAll(
            @ApiParam(value = "查询条件", required = true) @Validated @RequestBody PlayGroupWagesQueryVo vo) {
        List<PlayPersonnelGroupInfoEntity> list = playClerkGroupInfoService.selectAll();
        List<PlayGroupWagesReturnVo> data = new ArrayList<>();
        for (PlayPersonnelGroupInfoEntity entity : list) {
            PlayGroupWagesReturnVo bean = new PlayGroupWagesReturnVo();
            bean.setGroupName(entity.getGroupName());
            data.add(bean);
        }
        return R.ok(data);
    }

}


--- File: play-admin/src/main/java/com/starry/admin/modules/weichat/controller/WxPlayController.java ---
package com.starry.admin.modules.weichat.controller;

import cn.hutool.core.util.StrUtil;
import com.alibaba.fastjson2.JSONArray;
import com.alibaba.fastjson2.JSONObject;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.github.binarywang.wxpay.bean.notify.WxPayNotifyResponse;
import com.github.binarywang.wxpay.bean.profitsharing.ProfitSharingReceiverRequest;
import com.github.binarywang.wxpay.bean.profitsharing.ProfitSharingReceiverResult;
import com.github.binarywang.wxpay.bean.profitsharing.ProfitSharingRequest;
import com.github.binarywang.wxpay.bean.profitsharing.ProfitSharingResult;
import com.github.binarywang.wxpay.bean.request.WxPayUnifiedOrderRequest;
import com.github.binarywang.wxpay.bean.result.WxPayUnifiedOrderResult;
import com.github.binarywang.wxpay.exception.WxPayException;
import com.github.binarywang.wxpay.service.WxPayService;
import com.github.binarywang.wxpay.util.SignUtils;
import com.starry.admin.common.aspect.CustomUserLogin;
import com.starry.admin.common.conf.ThreadLocalRequestDetail;
import com.starry.admin.common.exception.CustomException;
import com.starry.admin.modules.custom.module.entity.PlayCustomUserInfoEntity;
import com.starry.admin.modules.custom.service.IPlayCustomUserInfoService;
import com.starry.admin.modules.order.module.entity.PlayOrderInfoEntity;
import com.starry.admin.modules.order.service.IPlayOrderInfoService;
import com.starry.admin.modules.order.service.impl.PlayOrderInfoServiceImpl;
import com.starry.admin.modules.system.module.entity.SysTenantEntity;
import com.starry.admin.modules.system.service.impl.SysTenantServiceImpl;
import com.starry.admin.modules.weichat.service.WxCustomMpService;
import com.starry.admin.utils.SecurityUtils;
import com.starry.common.result.R;
import com.starry.common.utils.StringUtils;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiResponse;
import io.swagger.annotations.ApiResponses;
import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.math.BigDecimal;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import lombok.extern.slf4j.Slf4j;
import org.dom4j.Document;
import org.dom4j.DocumentHelper;
import org.dom4j.Element;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

/**
 * @author admin
 */
@Api(tags = "微信支付接口", description = "微信端支付相关接口")
@Slf4j
@RestController
@RequestMapping("/wx/pay/")
public class WxPlayController {

    @Resource
    private SysTenantServiceImpl tenantService;
    @Resource
    private IPlayCustomUserInfoService customUserInfoService;
    @Resource
    private IPlayOrderInfoService orderInfoService;

    @Resource
    PlayOrderInfoServiceImpl playOrderInfoService;
    @Resource
    private WxCustomMpService mpService;

    /**
     * 微信支付-微信回调地址（https://pay.weixin.qq.com/wiki/doc/api/wxa/wxa_api.php?chapter=9_7&index=8）
     *
     * @author admin
     * @since 2024/5/8 11:25
     **/
    @ApiOperation(value = "微信支付回调", notes = "微信支付后的回调接口")
    @ApiResponses({@ApiResponse(code = 200, message = "成功")})
    @RequestMapping("/jsCallback")
    public String wxPayNotify(HttpServletRequest request) {
        try (InputStream inStream = request.getInputStream();
                ByteArrayOutputStream outSteam = new ByteArrayOutputStream()) {
            byte[] buffer = new byte[1024];
            int len;
            while ((len = inStream.read(buffer)) != -1) {
                outSteam.write(buffer, 0, len);
            }
            log.info("微信支付回调开始-参数解析-开始");
            // 获取微信调用我们notify_url的返回信息
            String result = outSteam.toString("utf-8");
            log.info("微信支付回调开始-参数解析-结束,result={}", result);
            this.dealNotify(result);
        } catch (Exception e) {
            log.error("微信支付回调业务处理异常", e);
        }
        return WxPayNotifyResponse.success("成功");
    }

    public void dealNotify(String xmlData) {
        try {
            Map<String, String> orderMap = readStringXmlOut(xmlData);
            String outTradeNo = orderMap.get("out_trade_no");
            String tenantId = orderMap.get("attach");
            if (StrUtil.isBlank(tenantId)) {
                log.error("支付对应业务处理异常-未查询到租户信息,xmlData={}", xmlData);
            }
            SecurityUtils.setTenantId(tenantId);
            PlayOrderInfoEntity orderInfoEntity = orderInfoService.queryByOrderNo(outTradeNo);
            if (Objects.isNull(orderInfoEntity)) {
                log.error("支付对应业务处理异常-未查询到对应的支付记录，xmlData={}", xmlData);
                return;
            }
            // TODO如果订单不是支付订单，并且支付状态不为未支付
            if (!"0".equals(orderInfoEntity.getOrderType()) || !"0".equals(orderInfoEntity.getPayState())) {
                log.error("支付对应业务处理异常-支付记录状态异常，支付记录：xmlData={}", xmlData);
                return;
            }
            // 修养订单状态
            orderInfoEntity.setPayState("1");
            orderInfoService.updateById(orderInfoEntity);
            // 修改账户余额
            customUserInfoService.customAccountBalanceRecharge(orderInfoEntity.getOrderMoney(),
                    orderInfoEntity.getPurchaserBy(), orderInfoEntity.getId());
            log.info("*********支付处理完成");
            // 推送通知
            mpService.sendBalanceMessage(orderInfoEntity);
            // 请求分账
            WxPayService wxPayService = mpService.getWxPay();
            this.profitSharing(wxPayService, orderMap, orderInfoEntity,
                    tenantService.selectSysTenantByTenantId(orderInfoEntity.getTenantId()));
        } catch (Exception e) {
            log.error("订单回调业务处理异常,xmlData={}", xmlData);
        }
    }

    @ApiOperation(value = "测试消息推送", notes = "测试充值消息推送功能")
    @ApiResponses({@ApiResponse(code = 200, message = "操作成功")})
    @GetMapping("/test")
    public R test() {
        PlayOrderInfoEntity orderInfo = playOrderInfoService.getById("0e3e7d7ec69a");
        mpService.sendBalanceMessage(orderInfo);
        return R.ok(true);
    }

    @ApiOperation(value = "获取支付金额", notes = "根据充值金额获取实际支付金额")
    @ApiImplicitParam(name = "money", value = "充值金额", required = true, dataType = "String", paramType = "query")
    @ApiResponses({@ApiResponse(code = 200, message = "操作成功"), @ApiResponse(code = 500, message = "请求参数错误,money不能为空")})
    @CustomUserLogin
    @GetMapping("/custom/getCustomPaymentAmount")
    public R getCustomPaymentAmount(@RequestParam("money") String money) {
        if (StringUtils.isEmpty(money)) {
            throw new CustomException("请求参数错误,money不能为空");
        }
        BigDecimal paymentAmount = customUserInfoService.getCustomPaymentAmount(new BigDecimal(money),
                ThreadLocalRequestDetail.getCustomUserInfo().getId());
        return R.ok(paymentAmount);
    }

    @ApiOperation(value = "创建充值订单", notes = "创建微信支付充值订单")
    @ApiImplicitParam(name = "money", value = "充值金额", required = true, dataType = "String", paramType = "query")
    @ApiResponses({@ApiResponse(code = 200, message = "操作成功"), @ApiResponse(code = 500, message = "请求参数错误,money不能为空"),
            @ApiResponse(code = 500, message = "充值金额不能小于10元"), @ApiResponse(code = 500, message = "系统错误,租户ID获取失败")})
    @CustomUserLogin
    @GetMapping("/custom/createOrder")
    public R createOrder(@RequestParam("money") String money) {
        if (StringUtils.isEmpty(money)) {
            throw new CustomException("请求参数错误,money不能为空");
        }
        if (new BigDecimal(money).compareTo(new BigDecimal("10")) < 0) {
            throw new CustomException("充值金额不能小于10元");
        }
        String tenantId = SecurityUtils.getTenantId();
        if (StrUtil.isBlankIfStr(tenantId)) {
            throw new CustomException("系统错误,租户ID获取失败");
        }
        // 用户信息
        PlayCustomUserInfoEntity customUserInfo = customUserInfoService
                .selectById(ThreadLocalRequestDetail.getCustomUserInfo().getId());
        // 租户信息
        SysTenantEntity tenant = tenantService.selectSysTenantByTenantId(tenantId);
        Boolean profitSharing = tenant.getProfitsharingRate() > 0;
        // 订单总金额，单位为分
        long totalFee = getTotalFee(money);
        // 创建订单信息
        String orderNo = playOrderInfoService.getOrderNo();
        orderInfoService.createRechargeOrder(orderNo, new BigDecimal(totalFee * 1.0 / 100),
                new BigDecimal(totalFee * 1.0 / 100), customUserInfo.getId());

        WxPayService wxPayService = mpService.getWxPay();
        WxPayUnifiedOrderRequest request = new WxPayUnifiedOrderRequest();
        request.setOpenid(customUserInfo.getOpenid());
        // 订单总金额，单位为分（开发阶段固定设置为支付1分钱）
        request.setAttach(tenantId);
        request.setTotalFee((int) totalFee);
        request.setOutTradeNo(orderNo);
        request.setTradeType("JSAPI");
        request.setSpbillCreateIp("101.43.206.16");
        request.setNotifyUrl(wxPayService.getConfig().getNotifyUrl());
        request.setBody("船票充值");
        if (profitSharing) {
            // 添加分账方
            this.profitSharingAddreceiver(wxPayService);
            request.setProfitSharing("Y");
        }

        WxPayUnifiedOrderResult orderResult;
        try {
            // 统一下单，商户系统先调用该接口在微信支付服务后台生成预支付交易单，返回正确的预支付交易会话标识后再在APP里面调起支付
            orderResult = wxPayService.unifiedOrder(request);
        } catch (WxPayException e) {
            throw new RuntimeException(e);
        }
        //
        String prepayId = orderResult.getPrepayId();
        // 组合参数构建支付
        Map<String, String> paySignInfo = new HashMap<>(5);
        String timeStamp = String.valueOf(System.currentTimeMillis());
        String nonceStr = "dalfhh241lnandnsklajax";
        paySignInfo.put("appId", wxPayService.getConfig().getAppId());
        paySignInfo.put("nonceStr", nonceStr);
        paySignInfo.put("timeStamp", timeStamp);
        paySignInfo.put("signType", "MD5");
        paySignInfo.put("package", "prepay_id=" + prepayId);
        String[] signInfo = new String[0];
        String paySign = SignUtils.createSign(paySignInfo, "MD5", wxPayService.getConfig().getMchKey(), signInfo);

        // 组合支付参数
        JSONObject jsonObject = new JSONObject().fluentPut("appId", wxPayService.getConfig().getAppId())
                .fluentPut("timeStamp", timeStamp).fluentPut("nonceStr", nonceStr)
                .fluentPut("package", "prepay_id=" + prepayId).fluentPut("signType", "MD5")
                .fluentPut("paySign", paySign);
        return R.ok(jsonObject);
    }

    private void profitSharingAddreceiver(WxPayService wxPayService) {
        ProfitSharingReceiverRequest request = new ProfitSharingReceiverRequest();
        request.setAppid(wxPayService.getConfig().getAppId());
        request.setMchId(wxPayService.getConfig().getMchId());
        String nonceStr = "dalfhh241lnandnsklajax";
        request.setNonceStr(nonceStr);
        request.setSignType("HMAC-SHA256");
        request.setReceiver(new JSONObject().fluentPut("type", "MERCHANT_ID").fluentPut("account", "1681470208")
                .fluentPut("name", "合肥经济技术开发区陪陪信息技术服务部（个体工商户）").fluentPut("relation_type", "SERVICE_PROVIDER")
                .toString());
        try {
            ProfitSharingReceiverResult result = wxPayService.getProfitSharingService().addReceiver(request);
            log.info("请求添加分账结果：" + JSONObject.toJSONString(result));
        } catch (WxPayException e) {
            throw new RuntimeException(e);
        }

    }

    /**
     * @param money
     *            金额,单位-元
     * @return 金额, 单位-分
     */
    public long getTotalFee(String money) {
        long totalFee;
        try {
            totalFee = (long) (Float.parseFloat(money) * 100);
        } catch (Exception e) {
            throw new CustomException("请求参数错误,money类型异常");
        }
        if (totalFee <= 0 || totalFee > 1000000) {
            throw new CustomException("请求参数错误,单笔支付金额必须大于0，且小于10000");
        }
        return totalFee;
    }

    public static Map<String, String> readStringXmlOut(String xml) {
        Map<String, String> map = new HashMap<String, String>();
        Document doc;
        try {
            doc = DocumentHelper.parseText(xml); // 将字符串转为XML
            Element rootElt = doc.getRootElement(); // 获取根节点
            List<Element> list = rootElt.elements();// 获取根节点下所有节点
            for (Element element : list) { // 遍历节点
                map.put(element.getName(), element.getText()); // 节点的name为map的key，text为map的value
            }
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
        return map;
    }

    private void profitSharing(WxPayService wxPayService, Map<String, String> orderMap,
            PlayOrderInfoEntity orderInfoEntity, SysTenantEntity tenant) throws WxPayException {
        Boolean profitSharing = tenant.getProfitsharingRate() > 0;
        if (!profitSharing) {
            return;
        }
        log.info("开始进行分账。。。。。。。。。。。。。。。");
        long totalFee = getTotalFee(orderInfoEntity.getOrderMoney().toString());
        int amount = new BigDecimal(totalFee).multiply(BigDecimal.valueOf((double) tenant.getProfitsharingRate() / 100))
                .intValue();
        if (amount == 0) {
            return;
        }
        ProfitSharingRequest request = new ProfitSharingRequest();
        request.setSignType("HMAC-SHA256");
        request.setTransactionId(orderMap.get("transaction_id"));
        request.setOutOrderNo("ps" + orderMap.get("out_trade_no"));

        request.setReceivers(new JSONArray().fluentAdd(
                new JSONObject().fluentPut("type", "MERCHANT_ID").fluentPut("name", "合肥经济技术开发区陪陪信息技术服务部（个体工商户）")
                        .fluentPut("account", "1681470208").fluentPut("amount", amount))
                .toString());
        ProfitSharingResult result = wxPayService.getProfitSharingService().profitSharing(request);
        log.info("分账结果：" + JSONObject.toJSONString(result));
        // 保存分账金额
        BigDecimal profitSharingAmount = new BigDecimal(amount).divide(new BigDecimal(100));
        orderInfoService.update(null,
                Wrappers.lambdaUpdate(PlayOrderInfoEntity.class).eq(PlayOrderInfoEntity::getId, orderInfoEntity.getId())
                        .set(PlayOrderInfoEntity::getProfitSharingAmount, profitSharingAmount));
    }

}


--- File: play-admin/src/main/java/com/starry/admin/modules/weichat/controller/WxPlayOrderRankingController.java ---
package com.starry.admin.modules.weichat.controller;

import com.starry.admin.common.aspect.ClerkUserLogin;
import com.starry.admin.common.conf.ThreadLocalRequestDetail;
import com.starry.admin.modules.clerk.service.IPlayClerkRankingInfoService;
import com.starry.admin.modules.weichat.entity.order.PlayOrderHistoryRankingReturnVo;
import com.starry.admin.modules.weichat.entity.order.PlayOrderRankingReturnVo;
import com.starry.common.result.R;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiResponse;
import io.swagger.annotations.ApiResponses;
import java.util.List;
import javax.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 * @author admin
 * @since 2024/5/25 下午10:25
 **/
@Api(tags = "微信订单排行接口", description = "微信端订单排行相关接口")
@Slf4j
@RestController
@RequestMapping("/wx/orderRanking")
public class WxPlayOrderRankingController {

    @Resource
    private IPlayClerkRankingInfoService playClerkRankingInfoService;

    /**
     * 查询当前排行
     */
    @ApiOperation(value = "查询当前排行", notes = "查询店员当前排行榜信息")
    @ApiResponses({@ApiResponse(code = 200, message = "操作成功", response = PlayOrderRankingReturnVo.class)})
    @ClerkUserLogin
    @GetMapping("/clerk/selectCurrentRanking")
    public R selectCurrentRanking() {
        PlayOrderRankingReturnVo result = playClerkRankingInfoService
                .selectCurrentRanking(ThreadLocalRequestDetail.getClerkUserInfo().getId());
        return R.ok(result);

    }

    /**
     * 查询历史排行
     */
    @ApiOperation(value = "查询历史排行", notes = "查询店员历史排行榜信息")
    @ApiResponses({
            @ApiResponse(code = 200, message = "操作成功", response = PlayOrderHistoryRankingReturnVo.class, responseContainer = "List")})
    @ClerkUserLogin
    @GetMapping("/clerk/selectHistoryRanking")
    public R selectHistoryRanking() {
        List<PlayOrderHistoryRankingReturnVo> result = playClerkRankingInfoService
                .selectHistoryRanking(ThreadLocalRequestDetail.getClerkUserInfo().getId());
        return R.ok(result);

    }
}


--- File: play-admin/src/main/java/com/starry/admin/modules/weichat/controller/WxShopController.java ---
package com.starry.admin.modules.weichat.controller;

import com.starry.admin.common.aspect.ClerkUserLogin;
import com.starry.admin.modules.shop.module.entity.PlayShopArticleInfoEntity;
import com.starry.admin.modules.shop.module.entity.PlayShopCarouselInfoEntity;
import com.starry.admin.modules.shop.service.IPlayShopArticleInfoService;
import com.starry.admin.modules.shop.service.IPlayShopCarouselInfoService;
import com.starry.admin.modules.weichat.entity.PlayShopReadArticleVo;
import com.starry.admin.modules.weichat.entity.shop.ShopHomeCarouseInfoReturnVo;
import com.starry.common.result.R;
import com.starry.common.utils.ConvertUtil;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiParam;
import io.swagger.annotations.ApiResponse;
import io.swagger.annotations.ApiResponses;
import java.util.List;
import javax.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 * 店铺接口
 *
 * @author admin
 * @since 2024/5/20 下午11:19
 **/
@Api(tags = "微信店铺接口", description = "微信端店铺相关接口")
@Slf4j
@RestController
@RequestMapping("/wx/shop")
public class WxShopController {

    @Resource
    private IPlayShopCarouselInfoService playShopCarouselInfoService;

    @Resource
    IPlayShopArticleInfoService playShopArticleInfoService;

    /**
     * 获取首页轮播图
     */
    @ApiOperation(value = "获取首页轮播图", notes = "获取店铺首页轮播图信息")
    @ApiResponses({
            @ApiResponse(code = 200, message = "操作成功", response = ShopHomeCarouseInfoReturnVo.class, responseContainer = "List")})
    @GetMapping(value = "custom/getShopHomeCarouseInfo")
    public R getShopHomeCarouseInfo() {
        List<PlayShopCarouselInfoEntity> entities = playShopCarouselInfoService.selectHomeCarouselInfo();
        return R.ok(ConvertUtil.entityToVoList(entities, ShopHomeCarouseInfoReturnVo.class));
    }

    /**
     * 获取店铺文章列表
     */
    @ApiOperation(value = "获取文章列表", notes = "获取店铺所有文章列表")
    @ApiResponses({
            @ApiResponse(code = 200, message = "操作成功", response = PlayShopArticleInfoEntity.class, responseContainer = "List")})
    @ClerkUserLogin
    @GetMapping(value = "clerk/getArticleList")
    public R getArticleList() {
        return R.ok(playShopArticleInfoService.selectByList());
    }

    /**
     * 阅读店铺文章
     */
    @ApiOperation(value = "阅读文章", notes = "阅读店铺文章并增加访问次数")
    @ApiResponses({@ApiResponse(code = 200, message = "操作成功")})
    @ClerkUserLogin
    @GetMapping(value = "clerk/readShopArticleInfo")
    public R readShopArticleInfo(@ApiParam(value = "文章参数", required = true) @RequestBody PlayShopReadArticleVo vo) {
        PlayShopArticleInfoEntity entity = playShopArticleInfoService.selectById(vo.getId());
        entity.setVisitsNumber(entity.getVisitsNumber() + 1);
        playShopArticleInfoService.update(entity);
        return R.ok("成功");
    }

}


--- File: play-admin/src/main/java/com/starry/admin/modules/weichat/controller/WxSmsController.java ---
package com.starry.admin.modules.weichat.controller;

import com.starry.admin.utils.SmsUtils;
import com.starry.common.result.R;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiResponse;
import io.swagger.annotations.ApiResponses;
import javax.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

/**
 * 动态接口
 *
 * @author admin
 * @since 2024/5/20 下午11:19
 **/
@Api(tags = "微信短信接口", description = "微信端短信发送相关接口")
@Slf4j
@RestController
@RequestMapping("/wx/sms")
public class WxSmsController {
    @Resource
    private SmsUtils smsUtils;

    @ApiOperation(value = "发送短信", notes = "向指定手机号发送短信验证码")
    @ApiImplicitParam(name = "phone", value = "手机号码", required = true, dataType = "String", paramType = "query")
    @ApiResponses({@ApiResponse(code = 200, message = "操作成功")})
    @GetMapping("/send")
    public R sendSms(@RequestParam("phone") String phone) {
        smsUtils.sendSmsApi(phone);
        return R.ok();
    }

}


--- File: play-admin/src/main/java/com/starry/admin/modules/weichat/entity/PlayClerkCommodityTreeData.java ---
package com.starry.admin.modules.weichat.entity;

import com.starry.admin.modules.clerk.module.entity.PlayClerkCommodityEntity;
import com.starry.common.utils.ConvertUtil;
import java.math.BigDecimal;
import java.util.List;
import lombok.Data;

/**
 * 店员商品树状数据
 *
 * @author admin
 */
@Data
public class PlayClerkCommodityTreeData {

    private String commodityType;

    private List<ClerkCommodityInfo> value;

    public PlayClerkCommodityTreeData(String commodityType, List<PlayClerkCommodityEntity> value) {
        this.commodityType = commodityType;
        this.value = ConvertUtil.entityToVoList(value, ClerkCommodityInfo.class);
    }

    @Data
    public static class ClerkCommodityInfo {

        private String id;

        private String commodityName;

        private BigDecimal commodityPrice;

        private String serviceDuration;

    }
}


--- File: play-admin/src/main/java/com/starry/admin/modules/weichat/entity/PlayClerkFollowQueryVo.java ---
package com.starry.admin.modules.weichat.entity;

import com.starry.common.domain.BasePageEntity;
import lombok.Data;
import lombok.EqualsAndHashCode;

/**
 * 顾客查询已关注陪聊接口
 *
 * @author admin
 */
@Data
@EqualsAndHashCode(callSuper = true)
public class PlayClerkFollowQueryVo extends BasePageEntity {

    private String customId;

    /**
     * 关注状态[0:未关注，1:已关注]
     */
    private String followState = "1";
}


--- File: play-admin/src/main/java/com/starry/admin/modules/weichat/entity/PlayClerkFollowReturnVo.java ---
package com.starry.admin.modules.weichat.entity;

import lombok.Data;

/**
 * 顾客查询已关注陪聊接口
 *
 * @author admin
 */
@Data
public class PlayClerkFollowReturnVo {

    private String id;
    /**
     * 顾客ID
     *
     * @since 2024/5/10 10:39
     **/
    private String customId;
    /**
     * 店员ID
     */
    private String clerkId;

    /**
     * 店员昵称
     */
    private String nickname;

    /**
     * 店员头像
     */
    private String avatar;

    /**
     * 关注状态[0:未关注，1:已关注]
     */
    private String followState;

    /**
     * 在线状态【1：在线，0：离线】
     */
    private String onlineState;

    /**
     * 最低消费
     */
    private String latestConsumption = "最低1船票";

    /**
     * 个性签名
     */
    private String signature;

    /**
     * 性别[0:未知;1:男;2:女]
     */
    private String sex;

    /**
     * 年龄
     */
    private Integer age;

    /**
     * 关注人数
     *
     * @since 2024/5/10 10:42
     **/
    private Integer followNumber;

}


--- File: play-admin/src/main/java/com/starry/admin/modules/weichat/entity/PlayClerkFollowStateUpdate.java ---
package com.starry.admin.modules.weichat.entity;

import javax.validation.constraints.NotBlank;
import javax.validation.constraints.Pattern;
import lombok.Data;

/**
 * 店员和陪聊关注状态
 *
 * @author admin
 */
@Data
public class PlayClerkFollowStateUpdate {

    /**
     * 店员用户ID
     */
    @NotBlank(message = "打赏对象不能为空")
    private String clerkId;

    @NotBlank(message = "关注状态不能为空")
    @Pattern(regexp = "[01]", message = "关注状态必须为0或者1")
    private String followState;
}


--- File: play-admin/src/main/java/com/starry/admin/modules/weichat/entity/PlayClerkUserAlbumVo.java ---
package com.starry.admin.modules.weichat.entity;

import java.util.List;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;
import lombok.Data;

/**
 * 店员相册实体
 *
 * @author admin
 * @since 2024/4/11 11:19
 **/

@Data
public class PlayClerkUserAlbumVo {

    @NotNull(message = "照片不能为空")
    @Size(min = 1, max = 5, message = "照片必须为1-5张")
    private List<String> album;
}


--- File: play-admin/src/main/java/com/starry/admin/modules/weichat/entity/PlayClerkUserAudioVo.java ---
package com.starry.admin.modules.weichat.entity;

import javax.validation.constraints.NotNull;
import lombok.Data;

/**
 * 店员录音文件
 *
 * @author admin
 * @since 2024/4/11 11:19
 **/
@Data
public class PlayClerkUserAudioVo {

    /**
     * 录音
     */
    @NotNull(message = "录音文件不能为空")
    private String audio;

}


--- File: play-admin/src/main/java/com/starry/admin/modules/weichat/entity/PlayClerkUserAvatarVo.java ---
package com.starry.admin.modules.weichat.entity;

import javax.validation.constraints.NotNull;
import lombok.Data;

/**
 * 店员头像信息
 *
 * @author admin
 * @since 2024/4/11 11:19
 **/
@Data
public class PlayClerkUserAvatarVo {

    /**
     * 头像
     */
    @NotNull(message = "头像不能为空")
    private String avatar;

    /**
     * 头像框
     */
    private String avatarFrameId;

}


--- File: play-admin/src/main/java/com/starry/admin/modules/weichat/entity/PlayClerkUserBindCodeVo.java ---
package com.starry.admin.modules.weichat.entity;

import javax.validation.constraints.NotBlank;
import lombok.Data;

/**
 * @author admin
 */
@Data
public class PlayClerkUserBindCodeVo {

    /**
     * 手机号码区号
     */
    @NotBlank(message = "手机号码区号不能为空")
    private String areaCode;

    /**
     * 手机号码
     */
    @NotBlank(message = "手机号码不能为空")
    private String phone;

    /**
     * 手机号码
     */
    @NotBlank(message = "验证码不能为空")
    private String code;

}


--- File: play-admin/src/main/java/com/starry/admin/modules/weichat/entity/PlayClerkUserByWxAddVo.java ---
package com.starry.admin.modules.weichat.entity;

import java.util.List;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;
import lombok.Data;

/**
 * 微信端申请成为店员
 *
 * @author admin
 */
@Data
public class PlayClerkUserByWxAddVo {

    /**
     * 店员昵称
     */
    @NotBlank(message = "昵称不能为空")
    private String nickname;

    /**
     * 店员性别（0:未知;1:男;2:女）
     */
    @NotNull(message = "性别不能为空")
    private String sex;

    /**
     * 年龄
     */
    @NotNull(message = "性别不能为空")
    private Integer age;

    /**
     * 微信号码
     */
    @NotNull(message = "微信号码不能为空")
    private String weiChatCode;

    /**
     * 手机号码区号
     */
    @NotBlank(message = "手机号码区号不能为空")
    private String areaCode;

    /**
     * 手机号码
     */
    @NotBlank(message = "手机号码不能为空")
    private String phone;

    /**
     * 所在国家
     */
    private String country = "中国";

    /**
     * 所在省份
     */
    @NotBlank(message = "省份不能为空")
    private String province;

    /**
     * 所在城市
     */
    @NotBlank(message = "城市不能为空")
    private String city;

    /**
     * 音频
     */
    @NotBlank(message = "音频不能为空")
    private String audio;

    @NotNull(message = "album不能为空")
    @Size(min = 1, max = 5, message = "照片必须为1-5张")
    private List<String> album;

    /**
     * 个性签名
     */
    private String signature;

    /**
     * 备注
     */
    private String remark;

    private String smsCode;

}


--- File: play-admin/src/main/java/com/starry/admin/modules/weichat/entity/PlayClerkUserByWxEditVo.java ---
package com.starry.admin.modules.weichat.entity;

import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
import lombok.Data;

/**
 * 微信端申请成为店员
 *
 * @author admin
 */
@Data
public class PlayClerkUserByWxEditVo {

    /**
     * 店员昵称
     */
    @NotBlank(message = "昵称不能为空")
    private String nickname;

    /**
     * 店员性别（0:未知;1:男;2:女）
     */
    @NotNull(message = "性别不能为空")
    private Integer sex;

    /**
     * 年龄
     */
    @NotNull(message = "性别不能为空")
    private Integer age;

    /**
     * 微信号码
     */
    @NotNull(message = "微信号码不能为空")
    private Integer weiChatCode;

    /**
     * 手机号码区号
     */
    @NotBlank(message = "手机号码区号不能为空")
    private String areaCode;

    /**
     * 手机号码
     */
    @NotBlank(message = "手机号码不能为空")
    private String phone;

    /**
     * 所在国家
     */
    private String country = "中国";

    /**
     * 所在省份
     */
    @NotBlank(message = "省份不能为空")
    private String province;

    /**
     * 所在城市
     */
    @NotBlank(message = "城市不能为空")
    private String city;

    /**
     * 音频
     */
    @NotBlank(message = "音频不能为空")
    private String audioFrequency;

    /**
     * 图片1
     **/
    private String image1;

    /**
     * 图片2
     **/
    private String image2;

    /**
     * 图片3
     **/
    private String image3;

    /**
     * 图片4
     **/
    private String image4;

    /**
     * 图片4
     **/
    private String image5;

    /**
     * 微信二维码
     **/
    private String weChatCodeImage;

    /**
     * 支付宝收款码图片
     **/
    private String weChatPayImage;

    /**
     * 支付宝收款码图片
     **/
    private String alipayImage;

    /**
     * 备注
     */
    private String remark;

}


--- File: play-admin/src/main/java/com/starry/admin/modules/weichat/entity/PlayClerkUserLoginResponseVo.java ---
package com.starry.admin.modules.weichat.entity;

import com.alibaba.fastjson2.JSONObject;
import com.starry.admin.modules.clerk.module.entity.PlayClerkLevelInfoEntity;
import com.starry.admin.modules.clerk.module.vo.PlayClerkCommodityQueryVo;
import java.util.ArrayList;
import java.util.List;
import lombok.Data;

/**
 * 微信端店员登录返回值
 *
 * @author admin
 */
@Data
public class PlayClerkUserLoginResponseVo {

    /**
     * UUID
     */
    private String id;

    /**
     * 店员昵称
     */
    private String nickname;

    /**
     * 头像
     */
    private String avatar;

    /**
     * 头像是否可编辑
     */
    private boolean avatarAllowEdit = true;

    /**
     * 相册
     */
    private List<String> album = new ArrayList<>();

    /**
     * 相册是否运行编辑
     */
    private boolean albumAllowEdit = true;

    /**
     * 音频
     */
    private String audio;

    /**
     * 音频是否可修改
     */
    private boolean audioAllowEdit = true;

    /**
     * 店员性别（0:未知;1:男;2:女）
     */
    private String sex;

    /**
     * 年龄
     */
    private Integer age;

    /**
     * 个性签名
     */
    private String signature;

    /**
     * 星座
     */
    private String constellation;

    /**
     * 地区
     */
    private String area;

    /**
     * 标签
     */
    private List<String> label = new ArrayList<>();

    /**
     * 分类
     */
    private String typeId;

    /**
     * 微信二维码
     **/
    private String weChatCodeImage;

    /**
     * 微信收款码图片
     **/
    private String weChatPayImage;

    /**
     * 支付宝收款码图片
     **/
    private String alipayImage;

    /**
     * 是否必须实名【0:非必须实名;1:必须实名;2:跟随店铺设置】
     */
    private String mandatoryRealState;

    /**
     * 在线状态【1：在线，0：离线】
     */
    private String onlineState;

    /**
     * 员工状态【1：是陪聊，0：不是陪聊】
     */
    private String clerkState;

    /**
     * 允许申请陪聊
     */
    private boolean clerkAllowEdit = true;

    /**
     * 服务项目
     */
    private List<PlayClerkCommodityQueryVo> commodity = new ArrayList<>();

    /**
     * 是否允许登录(0:允许，1:不允许)
     */
    private String allowLogin = "0";

    /**
     * 禁止登录原因
     */
    private String disableLoginReason;

    private JSONObject pcData;

    private PlayClerkLevelInfoEntity levelInfo;

}


--- File: play-admin/src/main/java/com/starry/admin/modules/weichat/entity/PlayClerkUserOnlineStateVo.java ---
package com.starry.admin.modules.weichat.entity;

import javax.validation.constraints.NotNull;
import javax.validation.constraints.Pattern;
import lombok.Data;

/**
 * 店员在线状态【1：在线，0：离线】
 *
 * @author admin
 * @since 2024/4/11 11:19
 **/
@Data
public class PlayClerkUserOnlineStateVo {

    /**
     * 在线状态
     */
    @NotNull(message = "在线状态不能为空")
    @Pattern(regexp = "[01]", message = "在线状态必须为0或1")
    private String onlineState;

}


--- File: play-admin/src/main/java/com/starry/admin/modules/weichat/entity/PlayClerkUserOtherVo.java ---
package com.starry.admin.modules.weichat.entity;

import java.util.ArrayList;
import java.util.List;
import lombok.Data;

/**
 * 店员录音文件
 *
 * @author admin
 * @since 2024/4/11 11:19
 **/
@Data
public class PlayClerkUserOtherVo {

    /**
     * 店员性别（0:未知;1:男;2:女）
     */
    private Integer sex;

    /**
     * 年龄
     */
    private Integer age;

    /**
     * 个性签名
     */
    private String signature;

    /**
     * 星座
     */
    private String constellation;

    /**
     * 所在国家
     */
    private String country = "中国";

    /**
     * 所在省份
     */
    private String province;

    /**
     * 所在城市
     */
    private String city;

    /**
     * 标签
     */
    private List<String> label = new ArrayList<>();

    /**
     * 分类
     */
    private String typeId;

    /**
     * 微信二维码
     **/
    private String weChatCodeImage;

    /**
     * 微信收款码图片
     **/
    private String weChatPayImage;

    /**
     * 支付宝收款码图片
     **/
    private String alipayImage;

}


--- File: play-admin/src/main/java/com/starry/admin/modules/weichat/entity/PlayClerkUserSendCodeVo.java ---
package com.starry.admin.modules.weichat.entity;

import javax.validation.constraints.NotBlank;
import lombok.Data;

/**
 * @author admin
 */
@Data
public class PlayClerkUserSendCodeVo {

    /**
     * 手机号码区号
     */
    @NotBlank(message = "手机号码区号不能为空")
    private String areaCode;

    /**
     * 手机号码
     */
    @NotBlank(message = "手机号码不能为空")
    private String phone;

}


--- File: play-admin/src/main/java/com/starry/admin/modules/weichat/entity/PlayClerkWxPerformanceInfoQueryVo.java ---
package com.starry.admin.modules.weichat.entity;

import lombok.Data;

/**
 * @author admin
 * @since 2024/6/10 下午8:58
 **/
@Data
public class PlayClerkWxPerformanceInfoQueryVo {

    /**
     * 店员ID
     */
    private String clerkId;

    /**
     * 订单开始时间
     */
    private String startTime;

    /**
     * 订单结束时间
     */
    private String endTime;
}


--- File: play-admin/src/main/java/com/starry/admin/modules/weichat/entity/PlayCommodityReturnVo.java ---
package com.starry.admin.modules.weichat.entity;

import java.math.BigDecimal;
import java.util.List;
import lombok.Data;

/**
 * 服务项目返回对象
 *
 * @author admin
 * @since 2024/5/29 16:41
 **/
@Data
public class PlayCommodityReturnVo {

    private String id;

    private String pId;
    /**
     * 项目类型
     */
    private String itemType;

    /**
     * 项目名称
     */
    private String itemName;

    /**
     * 服务时长（文字描述信息,不参与订单计算）
     */
    private String serviceDuration;

    /**
     * 服务单价
     */
    private BigDecimal price;

    List<PlayCommodityReturnVo> child;
}


--- File: play-admin/src/main/java/com/starry/admin/modules/weichat/entity/PlayCustomLeaveAddVo.java ---
package com.starry.admin.modules.weichat.entity;

import java.util.List;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;
import lombok.Data;
import org.hibernate.validator.constraints.Length;

/**
 * 顾客新增留言对象
 *
 * @author admin
 * @since 2024/5/8 下午10:03
 **/
@Data
public class PlayCustomLeaveAddVo {

    /**
     * 顾客ID
     */
    private String customId;

    /**
     * 留言内容
     */
    @NotBlank(message = "留言内容不能为空")
    @Length(max = 255, message = "留言内容不能超过255个字符")
    private String content;

    /**
     * 图片
     */

    @NotNull(message = "images不能为空")
    @Size(max = 3, message = "照片不能超过3张")
    private List<String> images;

    /**
     * 备注
     */
    private String remark;
}


--- File: play-admin/src/main/java/com/starry/admin/modules/weichat/entity/PlayGiftInfoDto.java ---
package com.starry.admin.modules.weichat.entity;

import java.math.BigDecimal;
import lombok.Data;

@Data
public class PlayGiftInfoDto {

    /**
     * UUID
     */
    private String id;

    /**
     * 租户ID
     */
    private String tenantId;

    /**
     * 礼物名称
     */
    private String name;

    /**
     * 礼物类型（0：盲盒,1:普通礼物）
     */
    private String type;

    /**
     * 礼物图片地址
     */
    private String url;

    /**
     * 单价
     */
    private BigDecimal price;

    /**
     * 价格单位
     */
    private String unit;
}


--- File: play-admin/src/main/java/com/starry/admin/modules/weichat/entity/PlayGroupWagesQueryVo.java ---
package com.starry.admin.modules.weichat.entity;

import java.util.List;
import lombok.Data;

/**
 * 组长工资查询对象
 *
 * @author admin
 * @since 2024/7/6 下午3:56
 **/
@Data
public class PlayGroupWagesQueryVo {

    /**
     * 统计时间
     */
    private List<String> statisticalTime;
}


--- File: play-admin/src/main/java/com/starry/admin/modules/weichat/entity/PlayGroupWagesReturnVo.java ---
package com.starry.admin.modules.weichat.entity;

import java.math.BigDecimal;
import lombok.Data;

/**
 * 组长工资查询对象
 *
 * @author admin
 * @since 2024/7/6 下午3:56
 **/
@Data
public class PlayGroupWagesReturnVo {

    /**
     * 组名称
     */
    private String groupName;

    /**
     * 订单金额
     */
    private BigDecimal orderMoney = BigDecimal.ZERO;

    /**
     * 订单最终金额（支付金额）
     */
    private BigDecimal finalAmount = BigDecimal.ZERO;

    /**
     * 续单比例
     */
    private final double orderContinueProportion = 0.0;
    /**
     * 续单金额
     */
    private final BigDecimal orderTotalAmount = BigDecimal.ZERO;

    /**
     * 续费率
     */
    private final BigDecimal orderContinueRechargeProportion = BigDecimal.ZERO;
    /**
     * 退单率
     */
    private final BigDecimal orderChargebackProportion = BigDecimal.ZERO;
    /**
     * 续客率
     */
    private final BigDecimal continuousCustomProportion = BigDecimal.ZERO;

    /**
     * 订单平均价格
     */
    private final BigDecimal averageUnitPrice = BigDecimal.ZERO;

    /**
     * 组长工资
     */
    private final BigDecimal commission = BigDecimal.ZERO;
}


--- File: play-admin/src/main/java/com/starry/admin/modules/weichat/entity/PlayOrderCancellationVo.java ---
package com.starry.admin.modules.weichat.entity;

import java.util.ArrayList;
import java.util.List;
import javax.validation.constraints.NotBlank;
import lombok.Data;

/**
 * @author admin
 * @since 2024/5/10 下午5:15
 **/
@Data
public class PlayOrderCancellationVo {

    /**
     * 申请人ID
     */
    private String customId;
    /**
     * 订单ID
     */
    @NotBlank(message = "订单ID不能为空")
    private String orderId;

    /**
     * 取消原因
     */
    @NotBlank(message = "取消原因不能为空")
    private String refundReason;

    private List<String> images = new ArrayList<>();
}


--- File: play-admin/src/main/java/com/starry/admin/modules/weichat/entity/PlayOrderComplaintAddVo.java ---
package com.starry.admin.modules.weichat.entity;

import java.util.Date;
import java.util.List;
import javax.validation.constraints.NotBlank;
import lombok.Data;
import org.hibernate.validator.constraints.Length;

/**
 * 订单投诉新增VO
 *
 * @author admin
 * @since 2024/5/8 下午10:49
 **/
@Data
public class PlayOrderComplaintAddVo {

    /**
     * 订单ID
     */
    @NotBlank(message = "订单ID不能为空")
    private String orderId;

    /**
     * 顾客ID
     */
    private String customId;

    /**
     * 投诉人微信号码
     */
    @NotBlank(message = "微信号码不能为空")
    private String wxChatCode;

    /**
     * 投诉内容
     */
    @NotBlank(message = "投诉内容不能为空")
    @Length(max = 255, message = "投诉内容最多255个字符")
    private String complaintCon;

    /**
     * 投诉时间
     */
    private Date complaintTime = new Date();

    /**
     * 图片列表
     */
    private List<String> images;
}


--- File: play-admin/src/main/java/com/starry/admin/modules/weichat/entity/PlayOrderEvaluateAddVo.java ---
package com.starry.admin.modules.weichat.entity;

import java.util.Date;
import javax.validation.constraints.*;
import lombok.Data;
import org.hibernate.validator.constraints.Length;

/**
 * 订单评价新增VO
 *
 * @author admin
 * @since 2024/5/8 下午10:49
 **/
@Data
public class PlayOrderEvaluateAddVo {

    /**
     * 订单ID
     */
    @NotBlank(message = "订单ID不能为空")
    private String orderId;

    /**
     * 顾客ID
     */
    private String customId;

    /**
     * 匿名评价(0:匿名，1:非匿名)
     */
    @NotBlank(message = "是否匿名不能为空")
    @Pattern(regexp = "[01]", message = "匿名评价只能为0或1")
    private String anonymous;

    /**
     * 评价类型(0:好评，1差评)
     */
    private String evaluateType = "0";

    /**
     * 评价等级【1-5星，最低1星，最高5星】
     */
    @NotNull(message = "评价等级不能为空")
    @Min(value = 1, message = "评价等级最低位1星")
    @Max(value = 5, message = "评价等级最高5星")
    private int evaluateLevel;

    /**
     * 评价内容
     */
    @NotNull(message = "评价内容不能为空")
    @Length(max = 255, message = "评价内容最多255个字符")
    private String evaluateCon;

    /**
     * 评价时间
     */
    private Date evaluateTime = new Date();
}


--- File: play-admin/src/main/java/com/starry/admin/modules/weichat/entity/PlayOrderInfoGiftAdd.java ---
package com.starry.admin.modules.weichat.entity;

import com.starry.admin.modules.shop.module.constant.GiftConstant;
import javax.validation.constraints.Max;
import javax.validation.constraints.Min;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
import lombok.Data;

/**
 * 新增赠礼订单实体
 *
 * @author admin
 */
@Data
public class PlayOrderInfoGiftAdd {

    /**
     * 店员用户ID
     */
    @NotBlank(message = "打赏对象不能为空")
    private String clerkId;

    @NotBlank(message = "礼物ID不能为空")
    private String giftId;

    @NotNull(message = "礼物数量不能为空")
    @Min(value = GiftConstant.MINIMUM_SINGLE_GIFT_QUANTITY, message = "礼物数量不能小于"
            + GiftConstant.MINIMUM_SINGLE_GIFT_QUANTITY)
    @Max(value = GiftConstant.MAXIMUM_SINGLE_GIFT_QUANTITY, message = "礼物数量不能大于"
            + GiftConstant.MAXIMUM_SINGLE_GIFT_QUANTITY)
    private int giftQuantity;

    private String weiChatCode;

    /**
     * 备注
     */
    private String remark;
}


--- File: play-admin/src/main/java/com/starry/admin/modules/weichat/entity/PlayOrderInfoRandomAdd.java ---
package com.starry.admin.modules.weichat.entity;

import com.starry.admin.modules.shop.module.constant.GiftConstant;
import java.util.List;
import javax.validation.constraints.Max;
import javax.validation.constraints.Min;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
import lombok.Data;

/**
 * 新增随机订单实体
 *
 * @author admin
 */
@Data
public class PlayOrderInfoRandomAdd {

    /**
     * 陪聊性别（0:未知;1:男;2:女）
     *
     * @since 2024/5/8 15:31
     **/
    private String sex;
    /**
     * 陪聊等级
     **/
    @NotBlank(message = "商品ID不能为空")
    private String levelId;

    /**
     * 标签列表
     *
     * @since 2024/5/8 15:26
     **/
    private List<String> labels;

    @NotBlank(message = "商品ID不能为空")
    private String commodityId;

    @NotNull(message = "数量不能为空")
    @Min(value = GiftConstant.MINIMUM_SINGLE_GIFT_QUANTITY, message = "数量不能小于"
            + GiftConstant.MINIMUM_SINGLE_GIFT_QUANTITY)
    @Max(value = GiftConstant.MAXIMUM_SINGLE_GIFT_QUANTITY, message = "数量不能大于"
            + GiftConstant.MAXIMUM_SINGLE_GIFT_QUANTITY)
    private int commodityQuantity;

    @NotBlank(message = "微信号不能为空")
    private String weiChatCode;

    /**
     * 是否排除下单过的成员（0:不排除;1:排除）
     *
     * @since 2024/5/8 15:28
     **/
    private String excludeHistory;

    /**
     * 优惠券ID列表
     */
    private List<String> couponIds;

    /**
     * 备注
     */
    private String remark;
}


--- File: play-admin/src/main/java/com/starry/admin/modules/weichat/entity/PlayOrderInfoRewardAdd.java ---
package com.starry.admin.modules.weichat.entity;

import javax.validation.constraints.NotBlank;
import lombok.Data;

/**
 * 新增打赏订单实体
 *
 * @author admin
 */
@Data
public class PlayOrderInfoRewardAdd {

    /**
     * 店员用户ID
     */
    @NotBlank(message = "打赏对象不能为空")
    private String clerkId;
    /**
     * 打赏金额
     */
    @NotBlank(message = "打赏金额不能为空")
    private String money;

    @NotBlank(message = "微信号不能为空")
    private String weiChatCode;

    /**
     * 备注
     */
    private String remark;
}


--- File: play-admin/src/main/java/com/starry/admin/modules/weichat/entity/PlayShopArticleInfoQueryVo.java ---
package com.starry.admin.modules.weichat.entity;

import com.starry.common.domain.BasePageEntity;
import lombok.Data;
import lombok.EqualsAndHashCode;

/**
 * @author admin
 * @since 2024/7/24 上午9:36
 **/
@EqualsAndHashCode(callSuper = true)
@Data
public class PlayShopArticleInfoQueryVo extends BasePageEntity {

}


--- File: play-admin/src/main/java/com/starry/admin/modules/weichat/entity/PlayShopArticleInfoReturnVo.java ---
package com.starry.admin.modules.weichat.entity;

import lombok.Data;

/**
 * @author admin
 * @since 2024/7/24 上午9:36
 **/
@Data
public class PlayShopArticleInfoReturnVo {

    private String id;
    /**
     * 文章标题
     **/
    private String articleTitle;

    /**
     * 文章内容
     **/
    private String articleContent;
}


--- File: play-admin/src/main/java/com/starry/admin/modules/weichat/entity/PlayShopReadArticleVo.java ---
package com.starry.admin.modules.weichat.entity;

import javax.validation.constraints.NotNull;
import lombok.Data;

/**
 * @author admin
 * @since 2024/7/24 上午9:36
 **/
@Data
public class PlayShopReadArticleVo {

    @NotNull(message = "id不能为空")
    private String id;
}


--- File: play-admin/src/main/java/com/starry/admin/modules/weichat/entity/WxCouponOrderQueryVo.java ---
package com.starry.admin.modules.weichat.entity;

import lombok.Data;

/**
 * 顾客优惠券下单查询对象
 *
 * @author admin
 * @since 2024/7/6 下午4:16
 **/
@Data
public class WxCouponOrderQueryVo {

    private String commodityId;

    /***
     * 店员等级ID
     */
    private String levelId;

    /***
     * 店员ID
     */
    private String clerkId;

    /**
     * 下单类型（-1:其他类型;0:指定单;1:随机单;2:打赏单）
     */
    private String placeType;

    private int commodityQuantity;
}


--- File: play-admin/src/main/java/com/starry/admin/modules/weichat/entity/WxCouponOrderReturnVo.java ---
package com.starry.admin.modules.weichat.entity;

import com.fasterxml.jackson.annotation.JsonFormat;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import lombok.Data;
import org.springframework.format.annotation.DateTimeFormat;

/**
 * 顾客优惠券下单查询返回对象
 *
 * @author admin
 * @since 2024/7/6 下午4:16
 **/
@Data
public class WxCouponOrderReturnVo {

    /**
     * 优惠券ID
     */
    private String id;

    /**
     * 优惠券名称
     **/
    private String couponName = "测试券";

    /**
     * 有效期类型（0:永久有效;1:固定时间内有效;2:领取后几天内有效）
     */
    private String validityPeriodType;

    /**
     * 生效时间
     */
    @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
    private LocalDateTime productiveTime;

    /**
     * 到期时间
     */
    @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
    private LocalDateTime expirationTime;

    /**
     * 生效时长,validityPeriodType=2时有效
     **/
    private Integer effectiveDay;

    /**
     * 使用优惠券最低消费金额
     **/
    private BigDecimal useMinAmount;

    /**
     * 优惠类型（0:满减;1:折扣）
     */
    private String discountType;

    /**
     * 折扣值
     */
    private BigDecimal discountAmount;

    private String discountContent;

    /**
     * 优惠是否可使用（0:不可使用,1:可使用）
     */
    private String available;

    /**
     * 优惠券不可用的原因
     */
    private String reasonForUnavailableUse;

}


--- File: play-admin/src/main/java/com/starry/admin/modules/weichat/entity/WxCouponQueryVo.java ---
package com.starry.admin.modules.weichat.entity;

import javax.validation.constraints.NotNull;
import javax.validation.constraints.Pattern;
import lombok.Data;

/**
 * 顾客优惠券查询对象
 *
 * @author admin
 * @since 2024/7/6 下午4:16
 **/
@Data
public class WxCouponQueryVo {

    private String commodityId;

    /**
     * 下单类型（-1:其他类型;0:指定单;1:随机单;2:打赏单）
     */
    @NotNull(message = "下单类型不能为空")
    @Pattern(regexp = "[0|1]", message = "订单类型必须为0或者1")
    private String placeType;

    private int commodityQuantity;
}


--- File: play-admin/src/main/java/com/starry/admin/modules/weichat/entity/WxCouponReceiveReturnVo.java ---
package com.starry.admin.modules.weichat.entity;

import com.baomidou.mybatisplus.annotation.TableField;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.starry.admin.common.conf.StringTypeHandler;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.List;
import lombok.Data;
import org.springframework.format.annotation.DateTimeFormat;

/**
 * 顾客优惠券领取查询返回对象
 *
 * @author admin
 * @since 2024/7/6 下午4:16
 **/
@Data
public class WxCouponReceiveReturnVo {

    /**
     * 优惠券ID
     */
    private String id;

    /**
     * 优惠券名称
     **/
    private String couponName;

    /**
     * 有效期类型（0:永久有效;1:固定时间内有效;2:领取后几天内有效）
     */
    private String validityPeriodType;

    /**
     * 生效时间
     */
    @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
    private LocalDateTime productiveTime;

    /**
     * 到期时间
     */
    @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
    private LocalDateTime expirationTime;

    /**
     * 生效时长,validityPeriodType=2时有效
     **/
    private Integer effectiveDay;

    /**
     * 使用优惠券最低消费金额
     **/
    private BigDecimal useMinAmount;

    /**
     * 优惠类型（0:满减;1:折扣）
     */
    private String discountType;

    /**
     * 折扣值
     */
    private BigDecimal discountAmount;

    /**
     * 下单类型（0：指定单，1：随机单。2：打赏单）
     */
    @TableField(typeHandler = StringTypeHandler.class)
    private List<String> placeType;

    /**
     * 优惠内容（文字展示）
     **/
    private String discountContent;

    /**
     * 优惠是否已领取（0:未领取,1:已领取）
     */
    private String obtained = "0";

}


--- File: play-admin/src/main/java/com/starry/admin/modules/weichat/entity/WxCouponReturnVo.java ---
package com.starry.admin.modules.weichat.entity;

import com.fasterxml.jackson.annotation.JsonFormat;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import lombok.Data;
import org.springframework.format.annotation.DateTimeFormat;

/**
 * 顾客优惠券查询对象
 *
 * @author admin
 * @since 2024/7/6 下午4:16
 **/
@Data
public class WxCouponReturnVo {

    /**
     * 优惠券ID
     */
    private String id;

    /**
     * 优惠券名称
     **/
    private String couponName;

    /**
     * 有效期类型（0:永久有效;1:固定时间内有效;2:领取后几天内有效）
     */
    private String validityPeriodType;

    /**
     * 生效时间
     */
    @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
    private LocalDateTime productiveTime;

    /**
     * 到期时间
     */
    @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
    private LocalDateTime expirationTime;

    /**
     * 生效时长,validityPeriodType=2时有效
     **/
    private Integer effectiveDay;

    /**
     * 使用优惠券最低消费金额
     **/
    private BigDecimal useMinAmount;

    /**
     * 优惠类型（0:满减;1:折扣）
     */
    private String discountType;

    /**
     * 折扣值
     */
    private BigDecimal discountAmount;

    /**
     * 优惠是否可使用（0:不可使用,1:可使用）
     */
    private String available;

    /**
     * 优惠券不可用的原因
     */
    private String reasonForUnavailableUse;

}


--- File: play-admin/src/main/java/com/starry/admin/modules/weichat/entity/WxPayReturnVo.java ---
package com.starry.admin.modules.weichat.entity;

import lombok.Data;

/**
 * 微信预支付接口返回信息
 *
 * @author admin
 * @since 2024/5/8 10:43
 **/
@Data
public class WxPayReturnVo {

    private String orderId;
    private String timeStamp;
    private String nonceStr;
    private String signType;
    private String paySign;
    private String prepayId;
    private String callback;

    public WxPayReturnVo(String orderId, String timeStamp, String nonceStr, String signType, String paySign,
            String prepayId, String callback) {
        this.orderId = orderId;
        this.timeStamp = timeStamp;
        this.nonceStr = nonceStr;
        this.signType = signType;
        this.paySign = paySign;
        this.prepayId = prepayId;
        this.callback = callback;
    }
}


--- File: play-admin/src/main/java/com/starry/admin/modules/weichat/entity/WxPlayClerkUserQueryVo.java ---
package com.starry.admin.modules.weichat.entity;

import com.starry.common.domain.BasePageEntity;
import lombok.Data;
import lombok.EqualsAndHashCode;

/**
 * 微信端分页查询店员文件
 *
 * @author admin
 * @since 2024/4/11 11:19
 **/
@EqualsAndHashCode(callSuper = true)
@Data
public class WxPlayClerkUserQueryVo extends BasePageEntity {

    /**
     * 分类
     */
    private String typeId;
    /**
     * 等级
     **/
    private String levelId;

    /**
     * 性别[0:未知;1:男;2:女]
     **/
    private Integer sex;

    /**
     * 省
     **/
    private String province;

}


--- File: play-admin/src/main/java/com/starry/admin/modules/weichat/entity/WxUserLoginVo.java ---
package com.starry.admin.modules.weichat.entity;

import javax.validation.constraints.NotBlank;
import lombok.Data;

@Data
public class WxUserLoginVo {

    @NotBlank(message = "code不能为空")
    private String code;
}


--- File: play-admin/src/main/java/com/starry/admin/modules/weichat/entity/WxUserQueryAddressVo.java ---
package com.starry.admin.modules.weichat.entity;

import lombok.Data;

/**
 * @author admin
 */
@Data
public class WxUserQueryAddressVo {

    private String url;
}


--- File: play-admin/src/main/java/com/starry/admin/modules/weichat/entity/article/PlayClerkAddArticleVo.java ---
package com.starry.admin.modules.weichat.entity.article;

import java.time.LocalDateTime;
import java.util.List;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Pattern;
import javax.validation.constraints.Size;
import lombok.Data;

/**
 * 店员新增动态
 *
 * @author admin
 * @since 2024/5/20 下午11:24
 **/
@Data
public class PlayClerkAddArticleVo {

    /**
     * 陪聊用户ID
     */
    private String clerkId;

    /**
     * 动态内容
     */
    @NotBlank(message = "动态内容不能为空")
    private String articleCon;

    /**
     * 动态附件类型（0：图片;1：视频;3:录音）
     */
    @NotBlank(message = "动态附件类型不能为空")
    @Pattern(regexp = "[012]", message = "动态附件类型只能为0或1")
    private String annexType;

    /**
     * 动态附件内容
     */
    @NotNull(message = "动态附件不能为空")
    @Size(min = 1, message = "附件数量必须大于0")
    @Size(max = 6, message = "附件数量必须小于6")
    private List<String> annexCon;

    /**
     * 发布时间
     */
    private LocalDateTime releaseTime = LocalDateTime.now();

    /**
     * 审核状态（0：未审核:1：审核通过，2：审核不通过）
     */
    private String reviewState = "0";
}


--- File: play-admin/src/main/java/com/starry/admin/modules/weichat/entity/article/PlayClerkArticleCustomQueryVo.java ---
package com.starry.admin.modules.weichat.entity.article;

import com.starry.common.domain.BasePageEntity;
import lombok.Data;
import lombok.EqualsAndHashCode;

/**
 * @author admin 顾客查询动态信息查询返回对象
 */
@EqualsAndHashCode(callSuper = true)
@Data
public class PlayClerkArticleCustomQueryVo extends BasePageEntity {

    /**
     * 收藏状态（0：未收藏，1:已收藏）
     */
    private String followState;

    /**
     * 店员ID
     */
    private String clerkId;

}


--- File: play-admin/src/main/java/com/starry/admin/modules/weichat/entity/article/PlayClerkArticleCustomReturnVo.java ---
package com.starry.admin.modules.weichat.entity.article;

import com.fasterxml.jackson.annotation.JsonFormat;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.starry.admin.modules.clerk.module.entity.PlayCustomArticleInfoEntity;
import java.time.LocalDateTime;
import java.util.List;
import lombok.Data;
import org.springframework.format.annotation.DateTimeFormat;

/**
 * @author admin 顾客查询动态信息查询返回对象
 */
@Data
public class PlayClerkArticleCustomReturnVo {

    /**
     * UUID
     */
    private String id;
    /**
     * 陪聊用户ID
     */
    private String clerkId;

    /**
     * 陪聊用户昵称
     */
    private String clerkNickname;

    /**
     * 陪聊用户头像
     */
    private String clerkAvatar;

    /**
     * 陪聊用户性别
     */
    private String clerkSex;

    /**
     * 动态内容
     */
    private String articleCon;

    /**
     * 动态附件类型（0:图片;1:视频;2:音频）
     */
    private String annexType;

    /**
     * 动态附件内容
     */
    private List<String> annexCon;

    /**
     * 发布时间
     */
    @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
    private LocalDateTime releaseTime;

    /**
     * 点赞总数
     */
    private Integer agreedQuantity = 0;

    /**
     * 点赞状态（0：未关注，1:已关注）
     */
    private String greedState = "0";

    /**
     * 收藏状态（0：未收藏，1:已收藏）
     */
    private String followState = "0";

    /**
     * 陪聊点赞动态
     */
    @JsonIgnore
    private List<PlayCustomArticleInfoEntity> articleInfoEntities;
}


--- File: play-admin/src/main/java/com/starry/admin/modules/weichat/entity/article/PlayClerkArticleFollowCustomFollowStateEditStateVo.java ---
package com.starry.admin.modules.weichat.entity.article;

import com.starry.common.domain.BasePageEntity;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.Pattern;
import lombok.Data;
import lombok.EqualsAndHashCode;

/**
 * @author admin 顾客收藏动态状态修改
 */
@EqualsAndHashCode(callSuper = true)
@Data
public class PlayClerkArticleFollowCustomFollowStateEditStateVo extends BasePageEntity {

    @NotBlank(message = "id不能为空")

    private String id;
    /**
     * 收藏状态（0：未收藏，1:已收藏）
     */
    @NotBlank(message = "followState不能为空")
    @Pattern(regexp = "[01]", message = "followState值必须为0或者1")
    private String followState;

}


--- File: play-admin/src/main/java/com/starry/admin/modules/weichat/entity/article/PlayClerkArticleFollowCustomGreedStateEditStateVo.java ---
package com.starry.admin.modules.weichat.entity.article;

import com.starry.common.domain.BasePageEntity;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.Pattern;
import lombok.Data;
import lombok.EqualsAndHashCode;

/**
 * @author admin 顾客点赞动态查询状态修改
 */
@EqualsAndHashCode(callSuper = true)
@Data
public class PlayClerkArticleFollowCustomGreedStateEditStateVo extends BasePageEntity {

    @NotBlank(message = "不能为空")
    private String id;

    /**
     * 点赞状态（0：未点赞，1:已关注）
     */
    @NotBlank(message = "greedState不能为空")
    @Pattern(regexp = "[01]", message = "greedState值必须为0或者1")
    private String greedState;

}


--- File: play-admin/src/main/java/com/starry/admin/modules/weichat/entity/clerk/PlayClerkLevelAndRankingVo.java ---
package com.starry.admin.modules.weichat.entity.clerk;

import lombok.Data;

/**
 * @author admin
 * @since 2024/6/10 下午8:58
 **/
@Data
public class PlayClerkLevelAndRankingVo {

    /**
     * 等级ID
     */
    private String levelName;

    /**
     * 开始名次
     */
    private Integer startRanking;

    /**
     * 开始名次
     */
    private Integer endRanking;

    private Integer sort;

    public PlayClerkLevelAndRankingVo(String levelName, Integer startRanking, Integer endRanking, Integer sort) {
        this.levelName = levelName;
        this.startRanking = startRanking;
        this.endRanking = endRanking;
        this.sort = sort;
    }
}


--- File: play-admin/src/main/java/com/starry/admin/modules/weichat/entity/clerk/PlayClerkLevelInfoReturnVo.java ---
package com.starry.admin.modules.weichat.entity.clerk;

import java.util.List;
import lombok.Data;

/**
 * @author admin
 * @since 2024/6/10 下午8:58
 **/
@Data
public class PlayClerkLevelInfoReturnVo {

    /**
     * 等级ID
     */
    private String levelId;

    /**
     * 等级名称
     */
    private String levelName;

    private List<PlayClerkLevelAndRankingVo> levelAndRanking;

}


--- File: play-admin/src/main/java/com/starry/admin/modules/weichat/entity/clerk/PlayClerkUserInfoQueryVo.java ---
package com.starry.admin.modules.weichat.entity.clerk;

import com.starry.common.domain.BasePageEntity;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import lombok.EqualsAndHashCode;

/**
 * 分页查询店员对象
 *
 * @author admin
 * @since 2024-03-30
 */
@Data
@EqualsAndHashCode(callSuper = false)
@ApiModel(value = "店员查询参数", description = "分页查询店员的条件参数对象")
public class PlayClerkUserInfoQueryVo extends BasePageEntity {

    /**
     * 店员昵称
     **/
    @ApiModelProperty(value = "店员昵称", example = "张三", notes = "支持模糊查询")
    private String nickname;
    /**
     * 店员等级
     */
    @ApiModelProperty(value = "店员等级ID")
    private String levelId;

    /**
     * 店员类型
     */
    @ApiModelProperty(value = "店员类型ID")
    private String typeId;

    /**
     * 性别[0:未知;1:男;2:女]
     */
    @ApiModelProperty(value = "性别", example = "1", notes = "0:未知;1:男;2:女")
    private String sex;

    /**
     * 所在省份
     */
    @ApiModelProperty(value = "所在省份", example = "广东省")
    private String province;

    /**
     * 上架状态【1：上架，0：下架】
     */
    @ApiModelProperty(value = "上架状态", example = "1", notes = "1：上架，0：下架")
    private String listingState;

    /**
     * 是否推荐状态（1：已推荐，0：未推荐）
     */
    @ApiModelProperty(value = "是否推荐", example = "1", notes = "1：已推荐，0：未推荐")
    private String recommendationState;

    /**
     * 员工状态【1：是陪聊，0：不是陪聊】
     */
    @ApiModelProperty(value = "员工状态", example = "1", notes = "1：是陪聊，0：不是陪聊", required = true)
    private String clerkState = "1";

    /**
     * 在职状态（1：在职，0：离职）
     */
    @ApiModelProperty(value = "在职状态", example = "1", notes = "1：在职，0：离职", required = true)
    private String onboardingState = "1";

    /**
     * 手机号码
     */
    @ApiModelProperty(value = "手机号码", example = "13800138000")
    private String phone;

}


--- File: play-admin/src/main/java/com/starry/admin/modules/weichat/entity/clerk/PlayClerkUserInfoResultVo.java ---
package com.starry.admin.modules.weichat.entity.clerk;

import com.starry.admin.modules.clerk.module.entity.PlayClerkLevelInfoEntity;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import java.util.ArrayList;
import java.util.List;
import lombok.Data;

/**
 * @author admin
 */
@Data
@ApiModel(value = "店员信息返回对象", description = "查询店员列表的返回结果对象")
public class PlayClerkUserInfoResultVo {

    /**
     * UUID
     */
    @ApiModelProperty(value = "店员ID", notes = "唯一标识")
    private String id;

    /**
     * 店员昵称
     */
    @ApiModelProperty(value = "店员昵称")
    private String nickname;

    /**
     * 店员等级
     */
    @ApiModelProperty(value = "店员等级ID")
    private String levelId;

    private PlayClerkLevelInfoEntity levelInfo;
    /**
     * 店员等级
     */
    @ApiModelProperty(value = "店员等级名称")
    private String levelName;

    /**
     * 性别[0:未知;1:男;2:女]
     */
    @ApiModelProperty(value = "性别", notes = "0:未知;1:男;2:女")
    private String sex;

    /**
     * 头像
     */
    @ApiModelProperty(value = "头像", notes = "头像图片URL")
    private String avatar;

    /**
     * 音频
     */
    @ApiModelProperty(value = "音频", notes = "音频文件URL")
    private String audio;

    /**
     * 星座
     */
    @ApiModelProperty(value = "星座", example = "天蝎座")
    private String constellation;

    /**
     * 标签
     */
    @ApiModelProperty(value = "标签列表", notes = "店员个人标签")
    private List<String> label = new ArrayList<>();

    /**
     * 相册
     */
    @ApiModelProperty(value = "相册列表", notes = "店员相册图片URL列表")
    private List<String> album = new ArrayList<>();

    /**
     * 个性签名
     */
    @ApiModelProperty(value = "个性签名")
    private String signature;

    /**
     * 年龄
     */
    @ApiModelProperty(value = "年龄", example = "25")
    private Integer age;

    /**
     * 所在省份
     */
    @ApiModelProperty(value = "所在省份", example = "广东省")
    private String province;

    /**
     * 所在城市
     */
    @ApiModelProperty(value = "所在城市", example = "深圳市")
    private String city;

    /**
     * 关注（0：未关注，1:已关注）
     */
    @ApiModelProperty(value = "关注状态", notes = "0：未关注，1:已关注", example = "0")
    private String followState = "0";

    /**
     * 在线状态【1：在线，0：离线】
     */
    @ApiModelProperty(value = "在线状态", notes = "1：在线，0：离线")
    private String onlineState;

    /**
     * 上架状态【1：上架，0：下架】
     */
    @ApiModelProperty(value = "上架状态", notes = "1：上架，0：下架")
    private String listingState;

    /**
     * 实名状态【1：已实名，0：未实名】
     */
    @ApiModelProperty(value = "实名状态", notes = "1：已实名，0：未实名")
    private String realState;

    /**
     * 是否必须实名【1：必须实名，0：非必须实名,2:跟随店铺设置】
     */
    @ApiModelProperty(value = "是否必须实名", notes = "1：必须实名，0：非必须实名,2:跟随店铺设置")
    private String mandatoryRealState;

    /**
     * 随机接单状态【1：允许，0：禁止】
     */
    @ApiModelProperty(value = "随机接单状态", notes = "1：允许，0：禁止")
    private String randomOrderState;

    /**
     * 服务项目
     */
    @ApiModelProperty(value = "服务项目列表", notes = "店员提供的服务项目")
    private List<String> commodity;

    /**
     * 地址
     */
    @ApiModelProperty(value = "地址", notes = "店员所在详细地址")
    private String address;

    /**
     * 最低消费
     */
    @ApiModelProperty(value = "最低消费", notes = "店员服务的最低消费", example = "最低1船票")
    private String latestConsumption = "最低1船票";

}


--- File: play-admin/src/main/java/com/starry/admin/modules/weichat/entity/costom/PlayCustomHideLevelStateEditVo.java ---
package com.starry.admin.modules.weichat.entity.costom;

import javax.validation.constraints.NotNull;
import javax.validation.constraints.Pattern;
import lombok.Data;

/**
 * 顾客状态修改
 *
 * @author admin
 * @since 2024/5/21 下午10:38
 **/
@Data
public class PlayCustomHideLevelStateEditVo {

    /**
     * UUID
     */
    private String id;

    /**
     * 隐藏等级[0:不隐藏;1:隐藏]
     */
    @NotNull(message = "hideLevelState不能为空")
    @Pattern(regexp = "[01]", message = "hideLevelState状态异常")
    private String hideLevelState;

}


--- File: play-admin/src/main/java/com/starry/admin/modules/weichat/entity/costom/PlayCustomHideRankingStateEditVo.java ---
package com.starry.admin.modules.weichat.entity.costom;

import javax.validation.constraints.NotNull;
import javax.validation.constraints.Pattern;
import lombok.Data;

/**
 * 顾客状态修改
 *
 * @author admin
 * @since 2024/5/21 下午10:38
 **/
@Data
public class PlayCustomHideRankingStateEditVo {

    /**
     * UUID
     */
    private String id;

    /**
     * 隐藏排名[0:不隐藏;1:隐藏]
     */
    @NotNull(message = "hideRankingState不能为空")
    @Pattern(regexp = "[01]", message = "hideRankingState状态异常")
    private String hideRankingState;
}


--- File: play-admin/src/main/java/com/starry/admin/modules/weichat/entity/evaluate/PlayCustomOrderEvaluateReturnVo.java ---
package com.starry.admin.modules.weichat.entity.evaluate;

import com.fasterxml.jackson.annotation.JsonFormat;
import java.util.Date;
import lombok.Data;

/**
 * 订单评价对象查询返回对象
 *
 * @author admin
 * @since 2024/5/13 16:15
 **/
@Data
public class PlayCustomOrderEvaluateReturnVo {

    private String id;

    /**
     * 匿名评价(0:匿名，1:非匿名)
     */
    private String anonymous;

    /**
     * 评价等级【1-5星，最低1星，最高5星】
     */
    private Integer evaluateLevel;

    /**
     * 评价内容
     */
    private String evaluateCon;

    /**
     * 评价时间
     */
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
    private Date evaluateTime;
}


--- File: play-admin/src/main/java/com/starry/admin/modules/weichat/entity/gift/PlayClerkGiftReturnVo.java ---
package com.starry.admin.modules.weichat.entity.gift;

import java.math.BigDecimal;
import lombok.Data;

/**
 * 店员礼物查询对象
 *
 * @author admin
 * @since 2024/5/26 上午12:20
 **/
@Data
public class PlayClerkGiftReturnVo {

    /**
     * UUID
     */
    private String id;

    /**
     * 礼物名称
     */
    private String name;

    /**
     * 礼物类型（0：盲盒,1:普通礼物）
     */
    private String type;

    /**
     * 礼物图片地址
     */
    private String url;

    /**
     * 单价
     */
    private BigDecimal price;

    /**
     * 价格单位
     */
    private String unit;

    /**
     * 礼物数量
     */
    private long giffNumber;

    /**
     * 礼物状态（0：正常，1：已下架）
     */
    private String state;
}


--- File: play-admin/src/main/java/com/starry/admin/modules/weichat/entity/gift/PlayCustomGiftQueryVo.java ---
package com.starry.admin.modules.weichat.entity.gift;

import lombok.Data;

/**
 * 店员礼物查询对象
 *
 * @author admin
 * @since 2024/5/26 上午12:20
 **/
@Data
public class PlayCustomGiftQueryVo {

    /**
     * 是否已获得礼物（0:已获取，1:所有礼物）
     */
    private String obtained;

    /**
     * 是否是历史礼物(0:不是,1:是)
     */
    private String history;

}


--- File: play-admin/src/main/java/com/starry/admin/modules/weichat/entity/level/PlayClerkLevelReturnVo.java ---
package com.starry.admin.modules.weichat.entity.level;

import lombok.Data;

/**
 * @author admin
 * @since 2024/5/29 上午6:29
 **/
@Data
public class PlayClerkLevelReturnVo {

    private String id;

    private String levelName;
}


--- File: play-admin/src/main/java/com/starry/admin/modules/weichat/entity/order/PlayClearOrderInfoQueryVo.java ---
package com.starry.admin.modules.weichat.entity.order;

import com.starry.common.domain.BasePageEntity;
import lombok.Data;
import lombok.EqualsAndHashCode;

/**
 * 店员分行查询订单-查询对象
 *
 * @author admin
 * @since 2024-03-20
 */
@Data
@EqualsAndHashCode(callSuper = false)
public class PlayClearOrderInfoQueryVo extends BasePageEntity {

    private String id;
    /**
     * 订单状态【0:1:2:3:4】 0：已下单 1：已接单 2：已开始 3：已完成 4：已取消
     */
    private String orderStatus;

    /**
     * 订单类型【0：充值订单；1：提现订单；2：普通订单】
     */
    private String orderType = "2";

    /**
     * 下单类型（0：指定单，1：随机单。2：打赏单）
     */
    private String placeType;

    /**
     * 下单人
     */
    private String purchaserBy;

}


--- File: play-admin/src/main/java/com/starry/admin/modules/weichat/entity/order/PlayClerkOrderDetailsReturnVo.java ---
package com.starry.admin.modules.weichat.entity.order;

import com.fasterxml.jackson.annotation.JsonFormat;
import java.math.BigDecimal;
import java.util.Date;
import lombok.Data;

/**
 * 店员订单详情返回信息
 *
 * @author admin
 * @since 2024/5/12 下午9:16
 **/
@Data
public class PlayClerkOrderDetailsReturnVo {

    /**
     * 订单ID
     */
    private String id;

    /**
     * 订单编号
     */
    private String orderNo;

    /**
     * 订单状态【0:1:2:3:4】 0：已下单（待接单） 1：已接单（待开始） 2：已开始（服务中） 3：已完成 4：已取消
     */
    private String orderStatus;

    /**
     * 下单类型（0：指定单，1：随机单。2：打赏单）
     */
    private String placeType;

    /**
     * 商品数量
     */
    private int commodityNumber;

    /**
     * 支付方式，0：余额支付,1:微信支付,2:支付宝支付
     */
    private String payMethod;

    /**
     * 订单备注
     */
    private String remark;

    /**
     * 买家微信号码
     */
    private String weiChatCode;

    /**
     * 订单金额
     */
    private BigDecimal orderMoney;

    /**
     * 下单时间
     */
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
    private Date purchaserTime;

    /**
     * 接单时间
     */
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
    private Date acceptTime;

    /**
     * 开始时间
     */
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
    private Date startTime;

    /**
     * 结束时间
     */
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
    private Date endTime;

    /**
     * 取消时间
     */
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
    private Date cancelTime;

    /**
     * 退款人类型[0:顾客;1:店员;2:管理员]
     */
    private String refundByType;

    /**
     * 退款人ID
     */
    private String refundById;

    /**
     * 退款原因
     **/
    private String refundReason;

    /**
     * 订单最终金额（支付金额）
     */
    private BigDecimal finalAmount;

    /**
     * 预计收入
     */
    private BigDecimal estimatedRevenue;

    /**
     * 优惠金额
     */
    private BigDecimal discountAmount;

    /**
     * 用户ID
     */
    private String customId;

    /**
     * 用户昵称
     */
    private String customNickname;

    /**
     * 用户头像
     */
    private String customAvatar;

    /**
     * 用户等级ID
     */
    private String customLevelId;

    /**
     * 用户等级名称
     */
    private String customLevelName;

    /**
     * 店员ID
     */
    private String clerkId;

    /**
     * 店员昵称
     */
    private String clerkNickname;

    /**
     * 店员头像
     */
    private String clerkAvatar;

    /**
     * 商品ID
     */
    private String commodityId;

    /**
     * 商品类型[0:礼物，1：服务]
     */
    private String commodityType;

    /**
     * 商品名称
     */
    private String commodityName;
    /**
     * 商品单价
     */
    private BigDecimal commodityPrice;

    /**
     * 服务时长
     *
     * @since 2024/5/8 16:44
     **/
    private String serviceDuration;

}


--- File: play-admin/src/main/java/com/starry/admin/modules/weichat/entity/order/PlayClerkOrderInfoQueryVo.java ---
package com.starry.admin.modules.weichat.entity.order;

import com.starry.common.domain.BasePageEntity;
import lombok.Data;
import lombok.EqualsAndHashCode;

/**
 * 订单查询对象
 *
 * @author admin
 * @since 2024-03-20
 */
@Data
@EqualsAndHashCode(callSuper = false)
public class PlayClerkOrderInfoQueryVo extends BasePageEntity {

    private String id;
    /**
     * 订单状态【0:1:2:3:4】 0：已下单 1：已接单 2：已开始 3：已完成 4：已取消
     */
    private String orderStatus;

    /**
     * 订单类型【0：充值订单；1：提现订单；2：普通订单】
     */
    private String orderType = "2";

    /**
     * 下单类型（0：指定单，1：随机单。2：打赏单）
     */
    private String placeType;

    /**
     * 下单人
     */
    private String acceptBy;

}


--- File: play-admin/src/main/java/com/starry/admin/modules/weichat/entity/order/PlayClerkOrderListReturnVo.java ---
package com.starry.admin.modules.weichat.entity.order;

import com.fasterxml.jackson.annotation.JsonFormat;
import java.math.BigDecimal;
import java.util.Date;
import lombok.Data;

/**
 * 店员查询订单列表返回对象
 *
 * @author admin
 * @since 2024-03-20
 */
@Data
public class PlayClerkOrderListReturnVo {

    /**
     * 订单ID
     */
    private String id;

    /**
     * 订单编号
     */
    private String orderNo;

    /**
     * 订单状态【0:1:2:3:4】 0：已下单（待接单） 1：已接单（待开始） 2：已开始（服务中） 3：已完成 4：已取消
     */
    private String orderStatus;

    /**
     * 下单类型（0：指定单，1：随机单。2：打赏单）
     */
    private String placeType;

    /**
     * 是否是首单【0：不是，1：是】
     */
    private String firstOrder;

    /**
     * 商品数量
     */
    private int commodityNumber;

    /**
     * 订单备注
     */
    private String remark;

    /**
     * 下单时间
     */
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
    private Date purchaserTime;

    /**
     * 订单最终金额（支付金额）
     */
    private BigDecimal finalAmount;

    /**
     * 用户ID
     */
    private String customId;

    /**
     * 用户昵称
     */
    private String customNickname;

    /**
     * 用户头像
     */
    private String customAvatar;

    /**
     * 用户等级ID
     */
    private String customLevelId;

    /**
     * 用户等级名称
     */
    private String customLevelName;

    /**
     * 店员ID
     */
    private String clerkId;

    /**
     * 店员昵称
     */
    private String clerkNickname;

    /**
     * 店员头像
     */
    private String clerkAvatar;

    /**
     * 商品ID
     */
    private String commodityId;

    /**
     * 商品类型[0:礼物，1：服务]
     */
    private String commodityType;

    /**
     * 商品类型[0:礼物，1：服务]
     */
    private String commodityName;
    /**
     * 商品单价
     */
    private BigDecimal commodityPrice;

    /**
     * 服务时长
     *
     * @since 2024/5/8 16:44
     **/
    private String serviceDuration;

}


--- File: play-admin/src/main/java/com/starry/admin/modules/weichat/entity/order/PlayClerkRandomOrderDetailReturnVo.java ---
package com.starry.admin.modules.weichat.entity.order;

import com.fasterxml.jackson.annotation.JsonFormat;
import java.math.BigDecimal;
import java.util.Date;
import java.util.List;
import lombok.Data;

/**
 * 随机单详情返回接口
 *
 * @author admin
 * @since 2024/5/30 10:00
 **/
@Data
public class PlayClerkRandomOrderDetailReturnVo {

    /**
     * 订单ID
     */
    private String id;

    /**
     * 订单编号
     */
    private String orderNo;

    /**
     * 订单状态【0:1:2:3:4】 0：已下单（待接单） 1：已接单（待开始） 2：已开始（服务中） 3：已完成 4：已取消
     */
    private final String orderStatus = "0";

    /**
     * 下单类型（0：指定单，1：随机单。2：打赏单）
     */
    private final String placeType = "1";

    /**
     * 商品数量
     */
    private int commodityNumber;

    /**
     * 支付方式，0：余额支付,1:微信支付,2:支付宝支付
     */
    private String payMethod;

    /**
     * 订单备注
     */
    private String remark;

    /**
     * 买家微信号码
     */
    private String weiChatCode;

    /**
     * 卖家要求
     *
     * @since 2024/5/30 10:02
     **/
    private List<String> labels;

    /**
     * 订单金额
     */
    private BigDecimal orderMoney;

    /**
     * 下单时间
     */
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
    private Date purchaserTime;

    /**
     * 订单最终金额（支付金额）
     */
    private BigDecimal finalAmount;

    /**
     * 预计收入
     */
    private BigDecimal estimatedRevenue;

    /**
     * 店员收入比例
     */
    private Integer estimatedRevenueRatio;

    /**
     * 优惠金额
     */
    private BigDecimal discountAmount;

    /**
     * 商品ID
     */
    private String commodityId;

    /**
     * 商品类型[0:礼物，1：服务]
     */
    private String commodityType;

    /**
     * 商品名称
     */
    private String commodityName;
    /**
     * 商品单价
     */
    private BigDecimal commodityPrice;

    /**
     * 服务时长
     *
     * @since 2024/5/8 16:44
     **/
    private String serviceDuration;

}


--- File: play-admin/src/main/java/com/starry/admin/modules/weichat/entity/order/PlayCustomOrderDetailsReturnVo.java ---
package com.starry.admin.modules.weichat.entity.order;

import com.fasterxml.jackson.annotation.JsonFormat;
import java.math.BigDecimal;
import java.util.Date;
import lombok.Data;

/**
 * 顾客查询订单详情返回对象
 *
 * @author admin
 * @since 2024-03-20
 */
@Data
public class PlayCustomOrderDetailsReturnVo {

    /**
     * 订单ID
     */
    private String id;

    /**
     * 订单编号
     */
    private String orderNo;

    /**
     * 订单状态【0:1:2:3:4】 0：已下单（待接单） 1：已接单（待开始） 2：已开始（服务中） 3：已完成 4：已取消
     */
    private String orderStatus;

    /**
     * 下单类型（0：指定单，1：随机单。2：打赏单）
     */
    private String placeType;

    /**
     * 商品数量
     */
    private int commodityNumber;

    /**
     * 支付方式，0：余额支付,1:微信支付,2:支付宝支付
     */
    private String payMethod;

    /**
     * 订单备注
     */
    private String remark;

    /**
     * 订单金额
     */
    private BigDecimal orderMoney;

    /**
     * 下单时间
     */
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
    private Date purchaserTime;

    /**
     * 接单时间
     */
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
    private Date acceptTime;

    /**
     * 开始时间
     */
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
    private Date startTime;

    /**
     * 结束时间
     */
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
    private Date endTime;

    /**
     * 取消时间
     */
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
    private Date cancelTime;

    /**
     * 退款人类型[0:顾客;1:店员;2:管理员]
     */
    private String refundByType;

    /**
     * 退款人ID
     */
    private String refundById;

    /**
     * 退款原因
     **/
    private String refundReason;

    /**
     * 是否评价（1：已评价;0:未评价）
     */
    private String evaluate = "0";

    /**
     * 是否投诉（1：已投诉;0:未投诉）
     */
    private String complaint = "0";

    /**
     * 订单最终金额（支付金额）
     */
    private BigDecimal finalAmount;

    /**
     * 优惠金额
     */
    private BigDecimal discountAmount;

    /**
     * 用户ID
     */
    private String customId;

    /**
     * 用户昵称
     */
    private String customNickname;

    /**
     * 用户头像
     */
    private String customAvatar;

    /**
     * 店员ID
     */
    private String clerkId;

    /**
     * 店员昵称
     */
    private String clerkNickname;

    /**
     * 店员头像
     */
    private String clerkAvatar;

    /**
     * 商品ID
     */
    private String commodityId;

    /**
     * 商品类型[0:礼物，1：服务]
     */
    private String commodityType;

    /**
     * 商品名称
     */
    private String commodityName;
    /**
     * 商品单价
     */
    private BigDecimal commodityPrice;

    /**
     * 服务时长
     *
     * @since 2024/5/8 16:44
     **/
    private String serviceDuration;

    /**
     * 微信号码
     *
     * @since 2024/5/13 11:29
     **/
    private String weiChatCode;

}


--- File: play-admin/src/main/java/com/starry/admin/modules/weichat/entity/order/PlayCustomOrderInfoQueryVo.java ---
package com.starry.admin.modules.weichat.entity.order;

import com.starry.common.domain.BasePageEntity;
import lombok.Data;
import lombok.EqualsAndHashCode;

/**
 * 订单查询对象
 *
 * @author admin
 * @since 2024-03-20
 */
@Data
@EqualsAndHashCode(callSuper = false)
public class PlayCustomOrderInfoQueryVo extends BasePageEntity {

    private String id;
    /**
     * 订单状态【0:1:2:3:4】 0：已下单 1：已接单 2：已开始 3：已完成 4：已取消
     */
    private String orderStatus;

    /**
     * 订单类型【0：充值订单；1：提现订单；2：普通订单】
     */
    private String orderType = "2";

    /**
     * 下单类型（0：指定单，1：随机单。2：打赏单）
     */
    private String placeType;

    /**
     * 下单人
     */
    private String purchaserBy;

}


--- File: play-admin/src/main/java/com/starry/admin/modules/weichat/entity/order/PlayCustomOrderListReturnVo.java ---
package com.starry.admin.modules.weichat.entity.order;

import com.fasterxml.jackson.annotation.JsonFormat;
import java.math.BigDecimal;
import java.util.Date;
import lombok.Data;

/**
 * 顾客查询订单列表返回对象
 *
 * @author admin
 * @since 2024-03-20
 */
@Data
public class PlayCustomOrderListReturnVo {

    /**
     * 订单ID
     */
    private String id;

    /**
     * 订单编号
     */
    private String orderNo;

    /**
     * 订单状态【0:1:2:3:4】 0：已下单（待接单） 1：已接单（待开始） 2：已开始（服务中） 3：已完成 4：已取消
     */
    private String orderStatus;

    /**
     * 下单类型（0：指定单，1：随机单。2：打赏单）
     */
    private String placeType;

    /**
     * 商品数量
     */
    private int commodityNumber;

    /**
     * 订单备注
     */
    private String remark;

    /**
     * 下单时间
     */
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
    private Date purchaserTime;

    /**
     * 是否评价（1：已评价;0:未评价）
     */
    private String evaluate = "0";

    /**
     * 是否投诉（1：已投诉;0:未投诉）
     */
    private String complaint = "0";

    /**
     * 订单最终金额（支付金额）
     */
    private BigDecimal finalAmount;

    /**
     * 用户ID
     */
    private String customId;

    /**
     * 用户昵称
     */
    private String customNickname;

    /**
     * 用户头像
     */
    private String customAvatar;

    /**
     * 店员ID
     */
    private String clerkId;

    /**
     * 店员昵称
     */
    private String clerkNickname;

    /**
     * 店员头像
     */
    private String clerkAvatar;

    /**
     * 商品ID
     */
    private String commodityId;

    /**
     * 商品类型[0:礼物，1：服务]
     */
    private String commodityType;

    /**
     * 商品类型[0:礼物，1：服务]
     */
    private String commodityName;
    /**
     * 商品单价
     */
    private BigDecimal commodityPrice;

    /**
     * 服务时长
     *
     * @since 2024/5/8 16:44
     **/
    private String serviceDuration;

}


--- File: play-admin/src/main/java/com/starry/admin/modules/weichat/entity/order/PlayOrderHistoryRankingReturnVo.java ---
package com.starry.admin.modules.weichat.entity.order;

import java.time.LocalDate;
import lombok.Data;
import org.springframework.format.annotation.DateTimeFormat;

/**
 * 订单排行分页查询对象
 *
 * @author admin
 * @since 2024/5/25 下午10:27
 **/
@Data
public class PlayOrderHistoryRankingReturnVo {

    /**
     * 开始统计时间
     */
    @DateTimeFormat(pattern = "yyyy-MM-dd")
    private LocalDate startCountDate;

    /**
     * 结束统计时间
     */
    @DateTimeFormat(pattern = "yyyy-MM-dd")
    private LocalDate endCountDate;

    /**
     * 排序名次
     */
    private Long rankingIndex;

}


--- File: play-admin/src/main/java/com/starry/admin/modules/weichat/entity/order/PlayOrderInfoCommodityAdd.java ---
package com.starry.admin.modules.weichat.entity.order;

import com.starry.admin.modules.shop.module.constant.GiftConstant;
import java.util.List;
import javax.validation.constraints.Max;
import javax.validation.constraints.Min;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
import lombok.Data;
import org.hibernate.validator.constraints.Length;

/**
 * 新增赠礼订单实体
 *
 * @author admin
 */
@Data
public class PlayOrderInfoCommodityAdd {

    /**
     * 店员用户ID
     */
    @NotBlank(message = "打赏对象不能为空")
    private String clerkId;

    @NotBlank(message = "商品ID不能为空")
    private String commodityId;

    @NotNull(message = "数量不能为空")
    @Min(value = GiftConstant.MINIMUM_SINGLE_GIFT_QUANTITY, message = "数量不能小于"
            + GiftConstant.MINIMUM_SINGLE_GIFT_QUANTITY)
    @Max(value = GiftConstant.MAXIMUM_SINGLE_GIFT_QUANTITY, message = "数量不能大于"
            + GiftConstant.MAXIMUM_SINGLE_GIFT_QUANTITY)
    private int commodityQuantity;

    @NotBlank(message = "微信号不能为空")
    @Length(max = 255, message = "微信号码长度不超过255个字符")
    private String weiChatCode;

    /**
     * 优惠券ID列表
     */
    private List<String> couponIds;

    /**
     * 备注
     */
    private String remark;
}


--- File: play-admin/src/main/java/com/starry/admin/modules/weichat/entity/order/PlayOrderInfoContinueAdd.java ---
package com.starry.admin.modules.weichat.entity.order;

import java.util.List;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;
import lombok.Data;

/**
 * 续单申请
 *
 * @author admin
 */
@Data
public class PlayOrderInfoContinueAdd {

    /**
     * orderId
     */
    @NotBlank(message = "orderId不能为空")
    private String orderId;

    /**
     * 订单备注
     */
    private String remark;

    /**
     * 图片列表
     */
    @NotNull(message = "images不能为空")
    @Size(min = 1, max = 6, message = "图片张数必须大于1张且小于6张")
    private List<String> images;
}


--- File: play-admin/src/main/java/com/starry/admin/modules/weichat/entity/order/PlayOrderInfoContinueQueryVo.java ---
package com.starry.admin.modules.weichat.entity.order;

import com.starry.common.domain.BasePageEntity;
import lombok.Data;
import lombok.EqualsAndHashCode;

/**
 * 续单申请列表查询
 *
 * @author admin
 */
@EqualsAndHashCode(callSuper = true)
@Data
public class PlayOrderInfoContinueQueryVo extends BasePageEntity {

    /**
     * 顾客ID
     */
    private String customId;
}


--- File: play-admin/src/main/java/com/starry/admin/modules/weichat/entity/order/PlayOrderInfoRandomQueryVo.java ---
package com.starry.admin.modules.weichat.entity.order;

import com.starry.common.domain.BasePageEntity;
import lombok.Data;
import lombok.EqualsAndHashCode;

/**
 * 待接单大厅
 *
 * @author admin
 */
@EqualsAndHashCode(callSuper = true)
@Data
public class PlayOrderInfoRandomQueryVo extends BasePageEntity {

}


--- File: play-admin/src/main/java/com/starry/admin/modules/weichat/entity/order/PlayOrderInfoRandomReturnVo.java ---
package com.starry.admin.modules.weichat.entity.order;

import com.fasterxml.jackson.annotation.JsonFormat;
import java.math.BigDecimal;
import java.util.Date;

/**
 * @author admin
 * @since 2024/5/30 9:22
 **/
public class PlayOrderInfoRandomReturnVo {

    /**
     * 订单ID
     */
    private String id;

    /**
     * 订单编号
     */
    private String orderNo;

    /**
     * 订单状态【0:1:2:3:4】 0：已下单（待接单） 1：已接单（待开始） 2：已开始（服务中） 3：已完成 4：已取消
     */
    private final String orderStatus = "0";

    /**
     * 下单类型（0：指定单，1：随机单。2：打赏单）
     */
    private final String placeType = "1";

    /**
     * 商品数量
     */
    private int commodityNumber;

    /**
     * 订单备注
     */
    private String remark;

    /**
     * 下单时间
     */
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
    private Date purchaserTime;

    /**
     * 订单最终金额（支付金额）
     */
    private BigDecimal finalAmount;

    /**
     * 用户ID
     */
    private String customId;

    /**
     * 用户昵称
     */
    private String customNickname;

    /**
     * 用户头像
     */
    private String customAvatar;

    /**
     * 用户等级ID
     */
    private String customLevelId;

    /**
     * 用户等级名称
     */
    private String customLevelName;

    /**
     * 店员ID
     */
    private String clerkId;

    /**
     * 店员昵称
     */
    private String clerkNickname;

    /**
     * 店员头像
     */
    private String clerkAvatar;

    /**
     * 商品ID
     */
    private String commodityId;

    /**
     * 商品类型[0:礼物，1：服务]
     */
    private String commodityType;

    /**
     * 商品类型[0:礼物，1：服务]
     */
    private String commodityName;
    /**
     * 商品单价
     */
    private BigDecimal commodityPrice;

    /**
     * 服务时长
     *
     * @since 2024/5/8 16:44
     **/
    private String serviceDuration;

}


--- File: play-admin/src/main/java/com/starry/admin/modules/weichat/entity/order/PlayOrderRankingListVo.java ---
package com.starry.admin.modules.weichat.entity.order;

import lombok.Data;

/**
 * 订单排行分页查询对象
 *
 * @author admin
 * @since 2024/5/25 下午10:27
 **/
@Data
public class PlayOrderRankingListVo {

    /**
     * 店员ID
     */
    private String clerkId;

    /**
     * 店员昵称
     */
    private String clerkNickname;

    /**
     * 店员头像
     */
    private String clerkAvatar;

    /**
     * 店员等级ID
     */
    private String levelId;

    /**
     * 店员等级名称
     */
    private String levelName;

    /**
     * 店员性别
     */
    private String clerkSex;

    /**
     * 排序名次
     */
    private Long rankingIndex;

}


--- File: play-admin/src/main/java/com/starry/admin/modules/weichat/entity/order/PlayOrderRankingQueryVo.java ---
package com.starry.admin.modules.weichat.entity.order;

import com.starry.common.domain.BasePageEntity;
import lombok.Data;
import lombok.EqualsAndHashCode;

/**
 * 订单排行分页查询对象
 *
 * @author admin
 * @since 2024/5/25 下午10:27
 **/
@EqualsAndHashCode(callSuper = true)
@Data
public class PlayOrderRankingQueryVo extends BasePageEntity {

    private String clerkId;
}


--- File: play-admin/src/main/java/com/starry/admin/modules/weichat/entity/order/PlayOrderRankingReturnVo.java ---
package com.starry.admin.modules.weichat.entity.order;

import java.math.BigDecimal;
import java.time.LocalDate;
import java.util.List;
import lombok.Data;
import org.springframework.format.annotation.DateTimeFormat;

/**
 * 订单排行分页查询对象
 *
 * @author admin
 * @since 2024/5/25 下午10:27
 **/
@Data
public class PlayOrderRankingReturnVo {

    /**
     * UUID
     */
    private String id;

    /**
     * 店员ID
     */
    private String clerkId;

    /**
     * 店员昵称
     */
    private String clerkNickname;

    /**
     * 店员头像
     */
    private String clerkAvatar;

    /**
     * 店员等级ID
     */
    private String levelId;

    /**
     * 店员等级名称
     */
    private String levelName;

    /**
     * 店员性别
     */
    private String clerkSex;

    /**
     * 开始统计时间
     */
    @DateTimeFormat(pattern = "yyyy-MM-dd")
    private LocalDate startCountDate;

    /**
     * 结束统计时间
     */
    @DateTimeFormat(pattern = "yyyy-MM-dd")
    private LocalDate endCountDate;

    /**
     * 是否是当前排名
     */
    private String currentRanking;

    /**
     * 订单总数
     */
    private String orderNumber;

    /**
     * 订单金额
     */
    private BigDecimal orderMoney;

    /**
     * 续单数
     */
    private Long orderContinueNumber;

    /**
     * 续单金额
     */
    private BigDecimal orderContinueMoney;

    /**
     * 超时未接单数量
     */
    private Long ordersExpiredNumber;

    /**
     * 距离前一名相差金额
     */
    private BigDecimal previousMoney;

    /**
     * 排序列表
     */
    private List<PlayOrderRankingListVo> rankings;

}


--- File: play-admin/src/main/java/com/starry/admin/modules/weichat/entity/order/PlayRandomOrderInfoReturnVo.java ---
package com.starry.admin.modules.weichat.entity.order;

import com.fasterxml.jackson.annotation.JsonFormat;
import java.math.BigDecimal;
import java.util.Date;
import lombok.Data;

/**
 * @author admin
 * @since 2024/6/1 下午11:20
 **/
@Data
public class PlayRandomOrderInfoReturnVo {

    /**
     * 订单ID
     */
    private String id;

    /**
     * 订单编号
     */
    private String orderNo;

    /**
     * 订单状态【0:1:2:3:4】 0：已下单（待接单） 1：已接单（待开始） 2：已开始（服务中） 3：已完成 4：已取消
     */
    private String orderStatus;

    /**
     * 下单类型（0：指定单，1：随机单。2：打赏单）
     */
    private String placeType;

    /**
     * 是否是首单【0：不是，1：是】
     */
    private String firstOrder;

    /**
     * 商品数量
     */
    private int commodityNumber;

    /**
     * 订单备注
     */
    private String remark;

    /**
     * 下单时间
     */
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
    private Date purchaserTime;

    /**
     * 订单最终金额（支付金额）
     */
    private BigDecimal finalAmount;

    /**
     * 用户ID
     */
    private String customId;

    /**
     * 用户昵称
     */
    private String customNickname;

    /**
     * 用户头像
     */
    private String customAvatar;

    /**
     * 用户等级ID
     */
    private String customLevelId;

    /**
     * 用户等级名称
     */
    private String customLevelName;

    /**
     * 商品ID
     */
    private String commodityId;

    /**
     * 商品类型[0:礼物，1：服务]
     */
    private String commodityType;

    /**
     * 商品名称
     */
    private String commodityName;
    /**
     * 商品单价
     */
    private BigDecimal commodityPrice;

    /**
     * 服务时长
     *
     * @since 2024/5/8 16:44
     **/
    private String serviceDuration;

}


--- File: play-admin/src/main/java/com/starry/admin/modules/weichat/entity/order/PlayRewardInfoReturnVo.java ---
package com.starry.admin.modules.weichat.entity.order;

import com.fasterxml.jackson.annotation.JsonFormat;
import java.math.BigDecimal;
import java.util.Date;
import lombok.Data;

/**
 * 打赏动态查询返回信息
 *
 * @author admin
 * @since 2024/5/25 下午7:49
 **/
@Data
public class PlayRewardInfoReturnVo {

    /**
     * UUID
     */
    private String id;

    /**
     * 店员ID
     */
    private String clerkId;

    /**
     * 店员昵称
     */
    private String clerkNickname;

    /**
     * 店员头像
     */
    private String clerkAvatar;

    /**
     * 打赏类型（0:余额;1:礼物）
     */
    private String rewardType;

    /**
     * 商品数量
     */
    private String commodityNumber;

    /**
     * 商品名称
     */
    private String commodityName;

    /**
     * 订单金额
     */
    private BigDecimal orderMoney;

    /**
     * 下单时间
     */
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
    private Date purchaserTime;

}


--- File: play-admin/src/main/java/com/starry/admin/modules/weichat/entity/order/PlayRewardOrderQueryVo.java ---
package com.starry.admin.modules.weichat.entity.order;

import com.starry.common.domain.BasePageEntity;

/**
 * 打赏动态分页查询对象
 *
 * @author admin
 * @since 2024/5/25 下午7:36
 **/
public class PlayRewardOrderQueryVo extends BasePageEntity {
}


--- File: play-admin/src/main/java/com/starry/admin/modules/weichat/entity/shop/ShopHomeCarouseInfoReturnVo.java ---
package com.starry.admin.modules.weichat.entity.shop;

import lombok.Data;

/**
 * @author admin
 * @since 2024/6/5 下午4:40
 **/
@Data
public class ShopHomeCarouseInfoReturnVo {

    /**
     * 图片URL
     */
    private String carouselUrl;
}


--- File: play-admin/src/main/java/com/starry/admin/modules/weichat/entity/user/PlayCustomUserReturnDetailVo.java ---
package com.starry.admin.modules.weichat.entity.user;

import com.fasterxml.jackson.annotation.JsonIgnore;
import java.math.BigDecimal;
import java.util.Date;
import lombok.Data;

/**
 * @author admin
 * @since 2024/5/28 下午5:07
 **/
@Data
public class PlayCustomUserReturnDetailVo {
    /**
     * UUID
     */
    private String id;
    /**
     * 租户ID
     */
    private String tenantId;
    /**
     * 用户的标识，对当前公众号唯一
     */
    private String openid;
    /**
     * 用户的标识，对当前公众号唯一
     */
    private String unionid;
    /**
     * 顾客昵称
     */
    private String nickname;
    /**
     * 顾客性别（0:未知;1:男,2:女）
     */
    private Integer sex;
    /**
     * 头像
     */
    private String avatar;
    /**
     * 手机号码
     */
    private String phone;
    /**
     * 微信号码
     */
    private String weiChatCode;
    /**
     * 等级ID
     */
    private String levelId;
    /**
     * 所在国家
     */
    private String country;
    /**
     * 所在省份
     */
    private String province;
    /**
     * 所在城市
     */
    private String city;
    /**
     * 账户余额
     */
    private BigDecimal accountBalance;
    /**
     * 余额状态[0:不存在余额，1:存在余额]
     */
    private String accountState;
    /**
     * 关注状态[0:未关注，1:已关注]
     */
    private String subscribeState;
    /**
     * 黑名单状态[0:非黑名单，1:黑名单]
     */
    private String blacklistState;
    /**
     * 违规状态[0:未违规,1:违规]
     */
    private String violationState;
    /**
     * 是否下单状态[0:未下单过，1:下单过]
     */
    private String purchaseState;
    /**
     * 绑定手机状态[0:未绑定,1:绑定]
     */
    private String mobilePhoneState;
    /**
     * 实名状态【1：已实名，0：未实名】
     */
    private String realState;
    /**
     * 是否必须实名【2:跟随店铺设置，1：必须实名，0：非必须实名】
     */
    private String mandatoryRealState;
    /**
     * 注册时间
     */
    private Date registrationTime;
    /**
     * 上次登录时间
     */
    private Date lastLoginTime;
    /**
     * 首次下单时间
     */
    private Date firstPurchaseTime;
    /**
     * 最后一次下单时间
     */
    private Date lastPurchaseTime;
    /**
     * 隐藏等级[0:不隐藏;1:隐藏]
     */
    private String hideLevelState;
    /**
     * 隐藏排名[0:不隐藏;1:隐藏]
     */
    private String hideRankingState;
    /**
     * 备注
     */
    private String remark;
    /**
     * 最近一次登录token
     */
    @JsonIgnore
    private String token;

    /**
     * 员工状态【1：是陪聊，0：不是陪聊】
     */
    public String clerkState = "0";

}


--- File: play-admin/src/main/java/com/starry/admin/modules/weichat/entity/vo/PlayCustomLevelInfoReturnVo.java ---
package com.starry.admin.modules.weichat.entity.vo;

import lombok.Data;

/**
 * @author admin
 * @since 2024/5/21 下午10:18
 **/
@Data
public class PlayCustomLevelInfoReturnVo {

    /**
     * 等级名称
     */
    private String name;

    /**
     * 等级数字（排序字段）
     */
    private Integer level;

    /**
     * 上一级消费金额
     */
    private String consumptionAmount;

    /**
     * 满减比例
     */
    private Integer discount;
}


--- File: play-admin/src/main/java/com/starry/admin/modules/weichat/entity/wages/ClerkCurrentPeriodWagesReturnVo.java ---
package com.starry.admin.modules.weichat.entity.wages;

import com.fasterxml.jackson.annotation.JsonFormat;
import java.math.BigDecimal;
import java.time.LocalDate;
import lombok.Data;
import org.springframework.format.annotation.DateTimeFormat;

/**
 * @author admin
 * @since 2024/6/1 上午11:44
 **/
@Data
public class ClerkCurrentPeriodWagesReturnVo {

    /**
     * UUID
     */
    private String id;

    /**
     * 总金额
     */
    private BigDecimal totalMoney;

    /**
     * 开始统计时间
     */
    @DateTimeFormat(pattern = "yyyy-MM-dd")
    @JsonFormat(pattern = "yyyy-MM-dd", timezone = "GMT+8")
    private LocalDate startCountDate;

    /**
     * 结束统计时间
     */
    @DateTimeFormat(pattern = "yyyy-MM-dd")
    @JsonFormat(pattern = "yyyy-MM-dd", timezone = "GMT+8")
    private LocalDate endCountDate;

    /**
     * 平台工资（订单）
     */
    private OrderWagesReturnVo orderWages;

}


--- File: play-admin/src/main/java/com/starry/admin/modules/weichat/entity/wages/ClerkHistoricalWagesReturnVo.java ---
package com.starry.admin.modules.weichat.entity.wages;

import com.fasterxml.jackson.annotation.JsonFormat;
import java.math.BigDecimal;
import java.time.LocalDate;
import lombok.Data;
import org.springframework.format.annotation.DateTimeFormat;

/**
 * @author admin
 * @since 2024/6/1 上午11:44
 **/
@Data
public class ClerkHistoricalWagesReturnVo {

    /**
     * UUID
     */
    private String id;

    /**
     * 订单金额
     */
    private BigDecimal orderMoney;

    /**
     * 开始统计时间
     */
    @DateTimeFormat(pattern = "yyyy-MM-dd")
    @JsonFormat(pattern = "yyyy-MM-dd", timezone = "GMT+8")
    private LocalDate startCountDate;

    /**
     * 结束统计时间
     */
    @DateTimeFormat(pattern = "yyyy-MM-dd")
    @JsonFormat(pattern = "yyyy-MM-dd", timezone = "GMT+8")
    private LocalDate endCountDate;

}


--- File: play-admin/src/main/java/com/starry/admin/modules/weichat/entity/wages/ClerkUnsettledWagesReturnVo.java ---
package com.starry.admin.modules.weichat.entity.wages;

import java.math.BigDecimal;
import lombok.Data;

/**
 * @author admin
 * @since 2024/6/1 下午1:52
 **/
@Data
public class ClerkUnsettledWagesReturnVo {

    /**
     * 订单金额
     */
    private BigDecimal orderMoney;

    /**
     * 订单总数
     */
    private Integer orderNumber;

    /**
     * 预计收入
     */
    private BigDecimal estimatedRevenue;

    public ClerkUnsettledWagesReturnVo(BigDecimal orderMoney, Integer orderNumber, BigDecimal estimatedRevenue) {
        this.orderMoney = orderMoney;
        this.orderNumber = orderNumber;
        this.estimatedRevenue = estimatedRevenue;
    }
}


--- File: play-admin/src/main/java/com/starry/admin/modules/weichat/entity/wages/ClerkWagesDetailsReturnVo.java ---
package com.starry.admin.modules.weichat.entity.wages;

import com.fasterxml.jackson.annotation.JsonFormat;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import lombok.Data;
import org.springframework.format.annotation.DateTimeFormat;

/**
 * @author admin
 * @since 2024/6/1 下午1:22
 **/
@Data
public class ClerkWagesDetailsReturnVo {
    /**
     * UUID
     */
    private String id;

    /**
     * 租户ID
     */
    private String tenantId;

    /**
     * 工资统计ID
     */
    private Long wagesId;

    /**
     * 店员ID
     */
    private String clerkId;

    /**
     * 订单ID
     */
    private String orderId;

    /**
     * 订单编号
     */
    private String orderNo;

    /**
     * 订单最终金额（支付金额）
     */
    private BigDecimal finalAmount;

    /**
     * 订单工资
     */
    private BigDecimal estimatedRevenue;

    /**
     * 订单完成时间
     */
    @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
    private LocalDateTime endOrderTime;
}


--- File: play-admin/src/main/java/com/starry/admin/modules/weichat/entity/wages/OrderWagesReturnVo.java ---
package com.starry.admin.modules.weichat.entity.wages;

import java.math.BigDecimal;
import lombok.Data;

/**
 * @author admin
 * @since 2024/6/1 上午11:44
 **/
@Data
public class OrderWagesReturnVo {

    /**
     * 订单总数
     */
    private Integer orderNumber = 0;

    /**
     * 订单金额
     */
    private BigDecimal orderMoney = BigDecimal.ZERO;

    /**
     * 续单数
     */
    private Integer orderContinueNumber = 0;

    /**
     * 续单比例
     */
    private double orderContinueProportion = 0.0;

    /**
     * 续单金额
     */
    private BigDecimal orderContinueMoney = BigDecimal.ZERO;

    /**
     * 超时未接单数
     */
    private Integer ordersExpiredNumber = 0;

    /**
     * 店员收入
     */
    private BigDecimal estimatedRevenue = BigDecimal.ZERO;

}


--- File: play-admin/src/main/java/com/starry/admin/modules/weichat/service/Constants.java ---
package com.starry.admin.modules.weichat.service;

import io.jsonwebtoken.Claims;

/**
 * 通用常量信息
 *
 * @author ruoyi
 */
public class Constants {
    /**
     * UTF-8 字符集
     */
    public static final String UTF8 = "UTF-8";

    /**
     * GBK 字符集
     */
    public static final String GBK = "GBK";

    /**
     * www主域
     */
    public static final String WWW = "www.";

    /**
     * http请求
     */
    public static final String HTTP = "http://";

    /**
     * https请求
     */
    public static final String HTTPS = "https://";

    /**
     * 通用成功标识
     */
    public static final String SUCCESS = "0";

    /**
     * 通用失败标识
     */
    public static final String FAIL = "1";

    /**
     * 登录成功
     */
    public static final String LOGIN_SUCCESS = "Success";

    /**
     * 注销
     */
    public static final String LOGOUT = "Logout";

    public static final String CHANGE_PHONE = "CHANGE_PHONE";

    /**
     * 注册
     */
    public static final String REGISTER = "Register";

    /**
     * 登录失败
     */
    public static final String LOGIN_FAIL = "Error";

    /**
     * 验证码有效期（分钟）
     */
    public static final Integer CAPTCHA_EXPIRATION = 2;

    /**
     * 令牌
     */
    public static final String TOKEN = "token";

    /**
     * 令牌前缀
     */
    public static final String TOKEN_PREFIX = "Bearer ";

    public static final String MINI_USER_LOGIN_TOKEN = "usertoken";
    public static final String MINI_COSER_LOGIN_TOKEN = "cosertoken";

    /**
     * 令牌前缀
     */
    public static final String LOGIN_USER_KEY = "login_user_key";
    public static final String LOGIN_USER_KEY_WX = "login_user_key_wx";
    public static final String LOGIN_USER_KEY_COSER = "login_user_key_coser";

    /**
     * 用户ID
     */
    public static final String JWT_USERID = "userid";

    /**
     * 用户名称
     */
    public static final String JWT_USERNAME = Claims.SUBJECT;

    /**
     * 用户头像
     */
    public static final String JWT_AVATAR = "avatar";

    /**
     * 创建时间
     */
    public static final String JWT_CREATED = "created";

    /**
     * 用户权限
     */
    public static final String JWT_AUTHORITIES = "authorities";

    /**
     * 资源映射路径 前缀
     */
    public static final String RESOURCE_PREFIX = "/profile";

    public static final String MINI_USER_DE_AVATAR = "https://live-cloud-cvoon.oss-cn-hangzhou.aliyuncs.com/cdn/img/1881712023451_.pic.jpg";
    public static final String MINI_COSER_DE_AVATAR = "https://live-cloud-cvoon.oss-cn-hangzhou.aliyuncs.com/cdn/img/1891712023452_.pic.jpg";

    /**
     * RMI 远程方法调用
     */
    public static final String LOOKUP_RMI = "rmi:";

    /**
     * LDAP 远程方法调用
     */
    public static final String LOOKUP_LDAP = "ldap:";

    /**
     * LDAPS 远程方法调用
     */
    public static final String LOOKUP_LDAPS = "ldaps:";

    /**
     * 定时任务白名单配置（仅允许访问的包名，如其他需要可以自行添加）
     */
    public static final String[] JOB_WHITELIST_STR = {"com.ruoyi"};

    /**
     * 定时任务违规的字符
     */
    public static final String[] JOB_ERROR_STR = {"java.net.URL", "javax.naming.InitialContext", "org.yaml.snakeyaml",
            "org.springframework", "org.apache", "com.ruoyi.common.utils.file", "com.ruoyi.common.config"};
}


--- File: play-admin/src/main/java/com/starry/admin/modules/weichat/service/WxAccessTokenService.java ---
package com.starry.admin.modules.weichat.service;

import javax.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import me.chanjar.weixin.common.error.WxErrorException;
import org.springframework.stereotype.Service;

/**
 * 微信公众号开发
 *
 * @author admin
 */
@Slf4j
@Service
public class WxAccessTokenService {

    @Resource
    private WxCustomMpService wxCustomMpService;

    /**
     * 获取微信AccessToken
     *
     * @return access_token
     */
    public String getAccessToken() throws WxErrorException {
        String token = wxCustomMpService.proxyWxMpService().getAccessToken();
        log.error("token = " + token);
        // 缓存业务处理
        return token;

    }

}


--- File: play-admin/src/main/java/com/starry/admin/modules/weichat/service/WxCustomMpService.java ---
package com.starry.admin.modules.weichat.service;

import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.github.binarywang.wxpay.config.WxPayConfig;
import com.github.binarywang.wxpay.service.WxPayService;
import com.github.binarywang.wxpay.service.impl.WxPayServiceImpl;
import com.starry.admin.common.exception.CustomException;
import com.starry.admin.modules.clerk.module.entity.PlayClerkUserInfoEntity;
import com.starry.admin.modules.clerk.module.entity.PlayClerkUserReviewInfoEntity;
import com.starry.admin.modules.clerk.service.IPlayClerkUserInfoService;
import com.starry.admin.modules.custom.module.entity.PlayCustomUserInfoEntity;
import com.starry.admin.modules.custom.service.IPlayCustomUserInfoService;
import com.starry.admin.modules.order.module.entity.PlayOrderComplaintInfoEntity;
import com.starry.admin.modules.order.module.entity.PlayOrderInfoEntity;
import com.starry.admin.modules.personnel.module.entity.PlayPersonnelAdminInfoEntity;
import com.starry.admin.modules.personnel.service.IPlayPersonnelAdminInfoService;
import com.starry.admin.modules.system.module.entity.SysTenantEntity;
import com.starry.admin.modules.system.service.impl.SysTenantServiceImpl;
import com.starry.admin.utils.SecurityUtils;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Objects;
import javax.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import me.chanjar.weixin.common.error.WxErrorException;
import me.chanjar.weixin.mp.api.WxMpService;
import me.chanjar.weixin.mp.bean.template.WxMpTemplateData;
import me.chanjar.weixin.mp.bean.template.WxMpTemplateMessage;
import me.chanjar.weixin.mp.config.impl.WxMpMapConfigImpl;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Service;

/**
 * @author admin
 */
@Slf4j
@Service
public class WxCustomMpService {

    @Resource
    private WxMpService wxMpService;

    @Resource
    private SysTenantServiceImpl tenantService;
    @Resource
    private IPlayCustomUserInfoService customUserInfoService;
    @Resource
    private IPlayPersonnelAdminInfoService playPersonnelAdminInfoService;
    @Resource
    private IPlayClerkUserInfoService clerkUserInfoService;

    /**
     * 支付成功回调地址
     */
    public static String NOTIFY_URL = "https://julyharbor.com/api/wx/pay/jsCallback";

    public WxMpService proxyWxMpService() {
        String tenantId = SecurityUtils.getTenantId();
        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() {
        String tenantId = SecurityUtils.getTenantId();
        if (StrUtil.isBlankIfStr(tenantId)) {
            throw new CustomException("系统错误,租户ID不能为空");
        }
        SysTenantEntity entity = tenantService.selectSysTenantByTenantId(tenantId);
        if (entity == null) {
            throw new CustomException("系统错误,租户ID不能为空");
        }
        if (StringUtils.isEmpty(entity.getMchId())) {
            throw new CustomException("商户号不能为空，请联系平台方进行配置");
        }
        WxPayConfig payConfig = new WxPayConfig();
        payConfig.setAppId(StringUtils.trimToNull(entity.getAppId()));
        payConfig.setMchId(StringUtils.trimToNull(entity.getMchId()));
        payConfig.setMchKey(StringUtils.trimToNull(entity.getMchKey()));

        payConfig.setNotifyUrl(StringUtils.trimToNull(NOTIFY_URL));
        payConfig.setTradeType("JSAPI");
        payConfig.setSignType("MD5");
        // 可以指定是否使用沙箱环境
        payConfig.setUseSandboxEnv(false);
        WxPayService wxPayService = new WxPayServiceImpl();
        wxPayService.setConfig(payConfig);
        return wxPayService;
    }

    /**
     * 充值成功-发送微信消息
     *
     * @param order
     *            订单信息
     */
    public void sendBalanceMessage(PlayOrderInfoEntity order) {
        PlayCustomUserInfoEntity customUserInfo = customUserInfoService.selectById(order.getPurchaserBy());
        SysTenantEntity tenant = tenantService.selectSysTenantByTenantId(order.getTenantId());
        WxMpTemplateMessage templateMessage = getWxMpTemplateMessage(tenant.getChongzhichenggongTemplateId(),
                customUserInfo.getOpenid(), "http://" + tenant.getTenantKey() + ".julyharbor.com/user/");
        List<WxMpTemplateData> data = new ArrayList<>();
        data.add(new WxMpTemplateData("time4", DateUtil.format(new Date(), "yyyy-MM-dd HH:mm:ss")));
        data.add(new WxMpTemplateData("amount2", order.getOrderMoney().toString()));
        data.add(new WxMpTemplateData("amount17", "0"));
        data.add(new WxMpTemplateData("thing10", tenant.getTenantName()));
        templateMessage.setData(data);
        try {
            proxyWxMpService().getTemplateMsgService().sendTemplateMsg(templateMessage);
        } catch (WxErrorException e) {
            log.error("充值成功发送消息异常", e);
        }
    }

    /**
     * 顾客下指定单后，给店员发新订单通知 通过微信公众号发送消息
     *
     * @param tenantId
     *            租户ID
     * @param openId
     *            消息接收人openID
     * @param orderNo
     *            消息内容-订单编号
     * @param money
     * @param commodityName
     */
    public void sendCreateOrderMessage(String tenantId, String openId, String orderNo, String money,
            String commodityName) {

        SysTenantEntity tenant = tenantService.selectSysTenantByTenantId(tenantId);
        WxMpTemplateMessage templateMessage = getWxMpTemplateMessage(tenant.getXindingdanshoulitongzhiTemplateId(),
                openId, "http://" + tenant.getTenantKey() + ".julyharbor.com/clerk/#/orderDetail/");
        List<WxMpTemplateData> data = new ArrayList<>();
        data.add(new WxMpTemplateData("time6", DateUtil.format(new Date(), "yyyy-MM-dd HH:mm:ss")));
        data.add(new WxMpTemplateData("character_string9", orderNo));
        data.add(new WxMpTemplateData("short_thing5", "陪聊下单"));
        data.add(new WxMpTemplateData("thing11", commodityName));
        data.add(new WxMpTemplateData("amount8", money));
        templateMessage.setData(data);
        try {
            proxyWxMpService().getTemplateMsgService().sendTemplateMsg(templateMessage);
        } catch (WxErrorException e) {
            log.error("接单成功发送消息异常", e);
        }

    }

    /**
     * 店员接单后，通过微信公众号发送消息
     *
     * @param orderInfo
     * @author admin
     * @since 2024/7/31 10:51
     **/
    public void sendOrderMessage(PlayOrderInfoEntity orderInfo) {
        SysTenantEntity tenant = tenantService.selectSysTenantByTenantId(orderInfo.getTenantId());
        PlayClerkUserInfoEntity clerkUserInfo = clerkUserInfoService.selectById(orderInfo.getAcceptBy());
        PlayCustomUserInfoEntity customUserInfo = customUserInfoService.selectById(orderInfo.getPurchaserBy());

        WxMpTemplateMessage templateMessage = getWxMpTemplateMessage(tenant.getJiedanchenggongtongzhiTemplateId(),
                clerkUserInfo.getOpenid(),
                "http://" + tenant.getTenantKey() + ".julyharbor.com/clerk/#/orderDetail/" + orderInfo.getId());
        List<WxMpTemplateData> data = new ArrayList<>();
        data.add(new WxMpTemplateData("character_string1", orderInfo.getOrderNo()));
        data.add(new WxMpTemplateData("time2", DateUtil.format(new Date(), "yyyy-MM-dd HH:mm:ss")));
        templateMessage.setData(data);
        try {
            proxyWxMpService().getTemplateMsgService().sendTemplateMsg(templateMessage);
        } catch (WxErrorException e) {
            log.error(e.getMessage(), e);
        }

        templateMessage.setToUser(customUserInfo.getOpenid());
        templateMessage
                .setUrl("http://" + tenant.getTenantKey() + ".julyharbor.com/user/#/orderDetail/" + orderInfo.getId());
        try {
            proxyWxMpService().getTemplateMsgService().sendTemplateMsg(templateMessage);
        } catch (WxErrorException e) {
            log.error(e.getMessage(), e);
        }

    }

    public void sendCheckMessage(PlayClerkUserReviewInfoEntity entity, PlayClerkUserInfoEntity userInfo,
            String reviewState) {
        String touser = userInfo.getOpenid();

        SysTenantEntity tenant = tenantService.selectSysTenantByTenantId(userInfo.getTenantId());
        String template_id = tenant.getRzsqshtxTemplateId();
        String tenantKey = tenant.getTenantKey();
        String url = "http://" + tenantKey + ".julyharbor.com/clerk/";

        WxMpTemplateMessage templateMessage = new WxMpTemplateMessage();
        templateMessage.setTemplateId(template_id);
        templateMessage.setToUser(touser);
        templateMessage.setUrl(url);
        List<WxMpTemplateData> data = new ArrayList<>();
        data.add(new WxMpTemplateData("short_thing7", reviewState.equals("1") ? "通过" : "拒绝"));
        data.add(new WxMpTemplateData("thing2", entity.getNickname()));
        data.add(new WxMpTemplateData("thing11", entity.getWeiChatCode()));
        data.add(new WxMpTemplateData("time8", DateUtil.format(new Date(), "yyyy-MM-dd HH:mm:ss")));
        templateMessage.setData(data);
        try {
            proxyWxMpService().getTemplateMsgService().sendTemplateMsg(templateMessage);
        } catch (WxErrorException e) {
            log.error(e.getMessage(), e);

        }

    }

    public void sendClerkApplyNoticeForAdmin(PlayClerkUserReviewInfoEntity entity, String tenantId) {
        SysTenantEntity tenant = tenantService.selectSysTenantByTenantId(tenantId);
        String template_id = tenant.getRzsqAdminTemplateId();
        String tenantKey = tenant.getTenantKey();
        String url = "http://" + tenantKey + ".julyharbor.com/clerk/#/admin/clerk-apply-list";
        List<PlayPersonnelAdminInfoEntity> adminInfoEntityList = playPersonnelAdminInfoService.list(Wrappers
                .lambdaQuery(PlayPersonnelAdminInfoEntity.class).select(PlayPersonnelAdminInfoEntity::getSysUserId));
        for (PlayPersonnelAdminInfoEntity adminInfoEntity : adminInfoEntityList) {
            PlayClerkUserInfoEntity clerkUserInfo = clerkUserInfoService
                    .getOne(Wrappers.lambdaQuery(PlayClerkUserInfoEntity.class)
                            .eq(PlayClerkUserInfoEntity::getSysUserId, adminInfoEntity.getSysUserId()), false);
            if (Objects.isNull(clerkUserInfo)) {
                continue;
            }
            WxMpTemplateMessage templateMessage = new WxMpTemplateMessage();
            templateMessage.setTemplateId(template_id);
            templateMessage.setToUser(clerkUserInfo.getOpenid());
            templateMessage.setUrl(url);
            List<WxMpTemplateData> data = new ArrayList<>();
            data.add(new WxMpTemplateData("phone_number15", entity.getPhone()));
            data.add(new WxMpTemplateData("thing11", entity.getNickname()));
            // data.add(new WxMpTemplateData("thing3", "有新的店员提交了审核，请前去查看"));
            data.add(new WxMpTemplateData("thing2", "有新的店员提交了审核，请前去查看"));
            templateMessage.setData(data);
            try {
                proxyWxMpService().getTemplateMsgService().sendTemplateMsg(templateMessage);
            } catch (WxErrorException e) {
                log.error(e.getMessage(), e);

            }
        }

    }

    /**
     * 获取微信发啥消息模板
     *
     * @param templateId
     *            消息模板ID
     * @param toUser
     *            发送目标用户ID
     * @param url
     *            消息回调地址
     * @return WxMpTemplateMessage
     */
    public WxMpTemplateMessage getWxMpTemplateMessage(String templateId, String toUser, String url) {
        WxMpTemplateMessage templateMessage = new WxMpTemplateMessage();
        templateMessage.setTemplateId(templateId);
        templateMessage.setToUser(toUser);
        templateMessage.setUrl(url);
        return templateMessage;
    }

    public void sendComplaintMessage(PlayOrderComplaintInfoEntity info, PlayOrderInfoEntity orderInfo) {

        SysTenantEntity tenant = tenantService.selectSysTenantByTenantId(info.getTenantId());
        List<PlayPersonnelAdminInfoEntity> adminInfoEntityList = playPersonnelAdminInfoService.list(Wrappers
                .lambdaQuery(PlayPersonnelAdminInfoEntity.class).select(PlayPersonnelAdminInfoEntity::getSysUserId));

        for (PlayPersonnelAdminInfoEntity adminInfo : adminInfoEntityList) {
            PlayClerkUserInfoEntity clerkUserInfo = clerkUserInfoService
                    .getOne(Wrappers.lambdaQuery(PlayClerkUserInfoEntity.class)
                            .eq(PlayClerkUserInfoEntity::getSysUserId, adminInfo.getSysUserId()), false);
            if (Objects.isNull(clerkUserInfo)) {
                continue;
            }
            WxMpTemplateMessage templateMessage = getWxMpTemplateMessage(tenant.getDingdantousuchuliTemplateId(),
                    clerkUserInfo.getOpenid(),
                    "http://" + tenant.getTenantKey() + ".julyharbor.com/clerk/#/admin/order/complain-list");
            List<WxMpTemplateData> data = new ArrayList<>();
            data.add(new WxMpTemplateData("character_string", orderInfo.getOrderNo()));
            data.add(new WxMpTemplateData("time2", DateUtil.format(new Date(), "yyyy-MM-dd HH:mm:ss")));
            templateMessage.setData(data);
            try {
                proxyWxMpService().getTemplateMsgService().sendTemplateMsg(templateMessage);
            } catch (WxErrorException e) {
                log.error("发送投诉消息异常", e);
            }
        }

    }

    public void sendOrderFinishMessage(PlayOrderInfoEntity order) {
        if (order.getPlaceType().equals("1") || order.getPlaceType().equals("2")) {
            SysTenantEntity tenant = tenantService.selectSysTenantByTenantId(order.getTenantId());
            PlayCustomUserInfoEntity customUserInfo = customUserInfoService.selectById(order.getPurchaserBy());
            WxMpTemplateMessage templateMessage = getWxMpTemplateMessage(tenant.getDingdanwanchengtongzhiTemplateId(),
                    customUserInfo.getOpenid(),
                    "http://" + tenant.getTenantKey() + ".julyharbor.com/user/#/orderDetail/" + order.getId());
            List<WxMpTemplateData> data = new ArrayList<>();
            data.add(new WxMpTemplateData("time18", DateUtil.format(new Date(), "yyyy-MM-dd HH:mm:ss")));
            data.add(new WxMpTemplateData("character_string8", order.getOrderNo()));
            data.add(new WxMpTemplateData("thing13", order.getCommodityName()));
            templateMessage.setData(data);
            try {
                proxyWxMpService().getTemplateMsgService().sendTemplateMsg(templateMessage);
            } catch (WxErrorException e) {
                log.error("订单完成发送消息异常", e);
            }
        }

    }

    public void sendOrderCancelMessage(PlayOrderInfoEntity orderInfo, String refundReason) {
        SysTenantEntity tenant = tenantService.selectSysTenantByTenantId(orderInfo.getTenantId());

        PlayCustomUserInfoEntity customUserInfo = customUserInfoService.selectById(orderInfo.getPurchaserBy());

        WxMpTemplateMessage templateMessage = getWxMpTemplateMessage(tenant.getQuxiaodingdantongzhiTemplateId(),
                customUserInfo.getOpenid(),
                "http://" + tenant.getTenantKey() + ".julyharbor.com/user/#/orderDetail/" + orderInfo.getId());
        List<WxMpTemplateData> data = new ArrayList<>();
        data.add(new WxMpTemplateData("character_string2", orderInfo.getOrderNo()));
        data.add(new WxMpTemplateData("thing5", StringUtils.isEmpty(refundReason) ? "已取消" : refundReason));
        data.add(new WxMpTemplateData("time18", DateUtil.format(new Date(), "yyyy-MM-dd HH:mm:ss")));
        templateMessage.setData(data);
        try {
            proxyWxMpService().getTemplateMsgService().sendTemplateMsg(templateMessage);
        } catch (WxErrorException e) {
            log.error(e.getMessage(), e);
        }

        if (StringUtils.isNotEmpty(orderInfo.getAcceptBy())) {
            PlayClerkUserInfoEntity clerkUserInfo = clerkUserInfoService.selectById(orderInfo.getAcceptBy());

            templateMessage.setToUser(clerkUserInfo.getOpenid());
            templateMessage.setUrl(
                    "http://" + tenant.getTenantKey() + ".julyharbor.com/clerk/#/orderDetail/" + orderInfo.getId());
            try {
                proxyWxMpService().getTemplateMsgService().sendTemplateMsg(templateMessage);
            } catch (WxErrorException e) {
                log.error(e.getMessage(), e);
            }
        }

    }

}


--- File: play-admin/src/main/java/com/starry/admin/modules/weichat/service/WxCustomUserService.java ---
package com.starry.admin.modules.weichat.service;

import com.starry.admin.modules.custom.module.entity.PlayCustomUserInfoEntity;
import com.starry.admin.modules.custom.service.IPlayCustomUserInfoService;
import com.starry.common.constant.Constants;
import com.starry.common.utils.StringUtils;
import java.util.Objects;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;

@Slf4j
@Service
public class WxCustomUserService {

    @Resource
    private IPlayCustomUserInfoService customUserInfoService;

    @Resource
    private HttpServletRequest request;

    @Resource
    private WxTokenService tokenService;

    /**
     * 获取当前登录顾客ID
     *
     * @return 当前登录顾客ID
     */
    public String getLoginUserId() {
        String userToken = request.getHeader(com.starry.common.constant.Constants.CUSTOM_USER_LOGIN_TOKEN);
        if (StringUtils.isEmpty(userToken)) {
            return "";
        }
        if (userToken.startsWith(com.starry.common.constant.Constants.TOKEN_PREFIX)) {
            userToken = userToken.replace(Constants.TOKEN_PREFIX, "");
        }
        // 解析token
        String userId;
        try {
            userId = tokenService.getWxUserIdByToken(userToken);
        } catch (Exception e) {
            return "";
        }
        PlayCustomUserInfoEntity entity = customUserInfoService.selectById(userId);
        if (Objects.isNull(entity)) {
            return "";
        }
        return userToken.equals(entity.getToken()) ? userId : "";
    }

}


--- File: play-admin/src/main/java/com/starry/admin/modules/weichat/service/WxOauthService.java ---
package com.starry.admin.modules.weichat.service;

import cn.hutool.core.util.StrUtil;
import com.alibaba.fastjson2.JSONObject;
import com.starry.admin.common.exception.ServiceException;
import com.starry.admin.modules.clerk.module.entity.PlayClerkUserInfoEntity;
import com.starry.admin.modules.clerk.service.IPlayClerkLevelInfoService;
import com.starry.admin.modules.clerk.service.IPlayClerkUserInfoService;
import com.starry.admin.modules.custom.module.entity.PlayCustomUserInfoEntity;
import com.starry.admin.modules.custom.service.IPlayCustomLevelInfoService;
import com.starry.admin.modules.custom.service.IPlayCustomUserInfoService;
import com.starry.common.utils.ConvertUtil;
import com.starry.common.utils.IdUtils;
import java.util.Date;
import javax.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import me.chanjar.weixin.common.bean.WxOAuth2UserInfo;
import me.chanjar.weixin.common.bean.oauth2.WxOAuth2AccessToken;
import me.chanjar.weixin.common.error.WxErrorException;
import org.springframework.stereotype.Service;

/**
 * 微信授权
 *
 * @author admin
 */
@Slf4j
@Service
public class WxOauthService {

    @Resource
    private WxCustomMpService wxCustomMpService;

    @Resource
    private IPlayCustomUserInfoService customUserInfoService;

    @Resource
    private IPlayClerkUserInfoService clerkUserInfoService;

    @Resource
    private IPlayClerkLevelInfoService playClerkLevelInfoService;

    @Resource
    private IPlayCustomLevelInfoService playCustomLevelInfoService;

    /**
     * 微信用户登录 如果用户不存在，初始化用户并登录
     *
     * @param code
     *            微信授权code
     * @return String 用户ID
     * @author admin
     * @since 2024/4/15 11:01
     **/
    public String clerkUserLogin(String code) {
        WxOAuth2AccessToken token = getWxOAuth2AccessToken(code);
        log.info("token = " + token);
        String openId = getOpenId(token);
        log.info("openId = " + openId);
        WxOAuth2UserInfo userInfo = getWxOAuth2UserInfo(token);
        PlayClerkUserInfoEntity item = clerkUserInfoService.selectByOpenid(openId);
        if (item == null) {
            PlayClerkUserInfoEntity entity = ConvertUtil.entityToVo(userInfo, PlayClerkUserInfoEntity.class);
            entity.setAvatar(userInfo.getHeadImgUrl());
            entity.setWeiChatAvatar(userInfo.getHeadImgUrl());
            entity.setId(IdUtils.getUuid());
            entity.setLevelId(playClerkLevelInfoService.getDefaultLevel().getId());
            clerkUserInfoService.create(entity);
            return entity.getId();
        } else {
            PlayClerkUserInfoEntity entity = new PlayClerkUserInfoEntity();
            entity.setId(item.getId());
            entity.setAvatar(userInfo.getHeadImgUrl());
            entity.setWeiChatAvatar(userInfo.getHeadImgUrl());
            clerkUserInfoService.updateById(entity);
            return item.getId();
        }
    }

    public void clerkUserLogout(PlayClerkUserInfoEntity entity) {
        entity.setToken("empty");
        clerkUserInfoService.update(entity);
    }

    public void customUserLogout(PlayCustomUserInfoEntity entity) {
        entity.setToken("empty");
        customUserInfoService.update(entity);
    }

    public String customUserLogin(String code) {
        WxOAuth2AccessToken token = getWxOAuth2AccessToken(code);
        String openId = getOpenId(token);
        WxOAuth2UserInfo userInfo = getWxOAuth2UserInfo(token);
        log.info("微信登录返回用户信息：" + JSONObject.toJSONString(userInfo));
        PlayCustomUserInfoEntity entity = ConvertUtil.entityToVo(userInfo, PlayCustomUserInfoEntity.class);
        entity.setAvatar(userInfo.getHeadImgUrl());
        PlayCustomUserInfoEntity item = customUserInfoService.selectByOpenid(openId);
        if (item == null) {
            entity.setId(IdUtils.getUuid());
            entity.setRegistrationTime(new Date());
            entity.setLevelId(playCustomLevelInfoService.getDefaultLevel().getId());
        } else {
            entity.setId(item.getId());
        }
        entity.setLastLoginTime(new Date());
        customUserInfoService.saveOrUpdate(entity);
        return entity.getId();
    }

    /**
     * 获取微信授权Token
     *
     * @param code
     *            code
     * @return WxOAuth2AccessToken
     * @author admin
     * @since 2024/4/8 14:47
     **/
    public WxOAuth2AccessToken getWxOAuth2AccessToken(String code) {
        if (StrUtil.isBlankIfStr(code)) {
            throw new ServiceException("不能为空");
        }
        synchronized (code.intern()) {
            try {
                return wxCustomMpService.proxyWxMpService().getOAuth2Service().getAccessToken(code);
            } catch (WxErrorException e) {
                throw new RuntimeException(e);
            }
        }
    }

    /**
     * 获取微信用户ID
     *
     * @param token
     *            code
     * @return WxOAuth2AccessToken
     * @author admin
     * @since 2024/4/8 14:47
     **/
    public String getOpenId(WxOAuth2AccessToken token) {
        if (token == null) {
            throw new ServiceException("获取微信授权异常，WxOAuth2AccessToken不能为空");
        }
        String openId = token.getOpenId();
        if (StrUtil.isBlankIfStr(openId)) {
            throw new ServiceException("获取微信授权异常，openId不能为空");
        }
        return openId;
    }

    /**
     * 获取微信用户ID
     *
     * @param token
     *            WxOAuth2AccessToken
     * @return WxOAuth2UserInfo
     * @author admin
     * @since 2024/4/8 14:47
     **/
    public WxOAuth2UserInfo getWxOAuth2UserInfo(WxOAuth2AccessToken token) {
        if (token == null) {
            throw new ServiceException("获取微信授权异常，WxOAuth2AccessToken不能为空");
        }
        try {
            return wxCustomMpService.proxyWxMpService().getOAuth2Service().getUserInfo(token, null);
        } catch (WxErrorException e) {
            throw new RuntimeException(e);
        }
    }
}


--- File: play-admin/src/main/java/com/starry/admin/modules/weichat/service/WxTokenService.java ---
package com.starry.admin.modules.weichat.service;

import cn.hutool.core.date.DateUtil;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;

/**
 * token验证处理
 *
 * @author ruoyi
 */
@Slf4j
@Service
public class WxTokenService {
    /**
     * 令牌自定义标识
     *
     * @since 2024/4/10 11:20
     **/
    @Value("${token.header}")
    private String header;

    /**
     * 令牌秘钥
     *
     * @since 2024/4/10 11:20
     **/
    @Value("${token.secret}")
    private String secret;

    /**
     * 令牌有效期（默认30分钟）
     *
     * @since 2024/4/10 11:20
     **/
    @Value("${token.expireTime}")
    private int expireTime;

    protected static final long MILLIS_SECOND = 1000;

    /**
     * 根据微信用户id创建token
     *
     * @param userId
     *            微信用户ID
     * @return String token
     * @author admin
     * @since 2024/4/10 11:21
     **/
    public String createWxUserToken(String userId) {
        if (Objects.isNull(userId)) {
            throw new RuntimeException("用户id不能为空");
        }
        Map<String, Object> claims = new HashMap<>(16);
        claims.put(Constants.LOGIN_USER_KEY_WX, userId);
        return Jwts.builder().setClaims(claims).setExpiration(DateUtil.offsetMinute(new Date(), expireTime))
                .signWith(SignatureAlgorithm.HS512, secret).compact();
    }

    /**
     * 根据token获取微信用户ID
     *
     * @param token
     *            token
     * @return String 微信用户ID
     * @author admin
     * @since 2024/4/10 11:24
     **/
    public String getWxUserIdByToken(String token) {
        if (StringUtils.isEmpty(token)) {
            throw new RuntimeException("token不能为空");
        }
        if (token.startsWith(Constants.TOKEN_PREFIX)) {
            token = token.replace(Constants.TOKEN_PREFIX, "");
        }
        Claims claims = parseToken(token);
        return claims.get(Constants.LOGIN_USER_KEY_WX).toString();
    }

    /**
     * 根据token获取微信用户TenantId
     *
     * @param token
     *            token
     * @param identity
     *            用户身份（0:租户，1:顾客）
     * @return String 微信用户租户ID
     * @author admin
     * @since 2024/4/10 11:24
     **/
    public String getWxUserTenantIdByToken(String token) {
        if (StringUtils.isEmpty(token)) {
            throw new RuntimeException("token不能为空");
        }
        Claims claims = parseToken(token);
        return claims.get(Constants.LOGIN_USER_KEY_WX).toString();
    }

    // public String getTenantId(String token) {
    // if (StringUtils.isEmpty(token)) {
    // throw new RuntimeException("token不能为空");
    // }
    // Map<String, Object> claims = new HashMap<>();
    // claims.put(Constants.LOGIN_USER_KEY_MINI, miniUserId);
    // return
    // Jwts.builder().setClaims(claims).setExpiration(DateUtil.offsetMinute(new
    // Date(), expireTime)).signWith(SignatureAlgorithm.HS512, secret).compact();
    // }
    //
    // // 小程序端-coser
    // public String createMiniCoserToken(Long miniCoserId) {
    // if (Objects.isNull(miniCoserId)) {
    // throw new RuntimeException("用户id不能为空");
    // }
    // Map<String, Object> claims = new HashMap<>();
    // claims.put(Constants.LOGIN_USER_KEY_COSER, miniCoserId);
    // return
    // Jwts.builder().setClaims(claims).setExpiration(DateUtil.offsetMinute(new
    // Date(), expireTime)).signWith(SignatureAlgorithm.HS512, secret).compact();
    // }
    //
    // // 小程序端-coser
    // public Long getMiniCoserIdByToken(String token) {
    // if (StringUtils.isEmpty(token)) {
    // throw new RuntimeException("token不能为空");
    // }
    // Claims claims = parseToken(token);
    // String userId = claims.get(Constants.LOGIN_USER_KEY_COSER).toString();
    // return Long.parseLong(userId);
    // }
    //
    //
    // /**
    // * 验证令牌有效期，相差不足20分钟，自动刷新缓存
    // *
    // * @param loginUser
    // * @return 令牌
    // */
    // public void verifyToken(LoginUser loginUser) {
    // long expireTime = loginUser.getExpireTime();
    // long currentTime = System.currentTimeMillis();
    // if (expireTime - currentTime <= MILLIS_MINUTE_TEN) {
    // refreshToken(loginUser);
    // }
    // }
    //
    // /**
    // * 刷新令牌有效期
    // *
    // * @param loginUser 登录信息
    // */
    // public void refreshToken(LoginUser loginUser) {
    // loginUser.setLoginTime(System.currentTimeMillis());
    // loginUser.setExpireTime(loginUser.getLoginTime() + expireTime *
    // MILLIS_MINUTE);
    // // 根据uuid将loginUser缓存
    // String userKey = getTokenKey(loginUser.getToken());
    // redisCache.setCacheObject(userKey, loginUser, expireTime, TimeUnit.MINUTES);
    // }
    //
    //
    // /**
    // * 设置用户代理信息
    // *
    // * @param loginUser 登录信息
    // */
    // public void setUserAgent(LoginUser loginUser) {
    // UserAgent userAgent =
    // UserAgent.parseUserAgentString(ServletUtils.getRequest().getHeader("User-Agent"));
    // String ip = IpUtils.getIpAddr();
    // loginUser.setIpaddr(ip);
    // loginUser.setLoginLocation(AddressUtils.getRealAddressByIP(ip));
    // loginUser.setBrowser(userAgent.getBrowser().getName());
    // loginUser.setOs(userAgent.getOperatingSystem().getName());
    // }
    //
    // /**
    // * 从数据声明生成令牌
    // *
    // * @param claims 数据声明
    // * @return 令牌
    // */
    // private String createToken(Map<String, Object> claims) {
    // String token =
    // Jwts.builder().setClaims(claims).signWith(SignatureAlgorithm.HS512,
    // secret).compact();
    // return token;
    // }
    //

    /**
     * 从令牌中获取数据声明
     *
     * @param token
     *            令牌
     * @return 数据声明
     */
    private Claims parseToken(String token) {
        return Jwts.parser().setSigningKey(secret).parseClaimsJws(token).getBody();
    }
    //
    // /**
    // * 获取请求token
    // *
    // * @param request
    // * @return token
    // */
    // private String getToken(HttpServletRequest request) {
    // String token = request.getHeader(header);
    // if (StrUtil.isNotBlank(token) && token.startsWith(Constants.TOKEN_PREFIX)) {
    // token = token.replace(Constants.TOKEN_PREFIX, "");
    // }
    // return token;
    // }
    //
    // private String getTokenKey(String uuid) {
    // return CacheConstants.LOGIN_TOKEN_KEY + uuid;
    // }
}


--- File: play-admin/src/main/java/com/starry/admin/modules/weichat/utils/WxFileUtils.java ---
package com.starry.admin.modules.weichat.utils;

import cn.hutool.core.io.FileUtil;
import com.alibaba.fastjson2.JSONObject;
import com.starry.admin.common.exception.CustomException;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.util.HashMap;
import lombok.extern.slf4j.Slf4j;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import ws.schild.jave.Encoder;
import ws.schild.jave.MultimediaObject;
import ws.schild.jave.encode.AudioAttributes;
import ws.schild.jave.encode.EncodingAttributes;

@Slf4j
public class WxFileUtils {

    /**
     * 下载微信临时素材文件
     *
     * @param access_token
     *            微信token
     * @param mediaId
     *            素材ID
     * @return InputStream
     * @throws IOException
     *             IOException
     */
    public static InputStream getTemporaryMaterial(String access_token, String mediaId) throws IOException {
        String url = "https://api.weixin.qq.com/cgi-bin/media/get?access_token=" + access_token + "&media_id="
                + mediaId;
        // 请求参数
        HashMap<String, String> param = new HashMap<>();
        param.put("media_id", mediaId);
        // 获取到http连接对象
        HttpPost httpPost = new HttpPost(url);
        StringEntity stringEntity = new StringEntity(JSONObject.toJSONString(param));
        httpPost.setEntity(stringEntity);
        // 打开链接发送请求 获取到返回的流
        CloseableHttpClient build = HttpClients.custom().build();
        CloseableHttpResponse execute = build.execute(httpPost);
        return execute.getEntity().getContent();
    }

    public static void audioConvert2Mp3(File source, File target) {
        if (FileUtil.isEmpty(source)) {
            log.error("从微信下载的临时素材文件为空");
            throw new CustomException("音频文件上传失败");
        }
        try {
            AudioAttributes audio = new AudioAttributes();
            audio.setCodec("libmp3lame");
            audio.setBitRate(228000);
            audio.setChannels(2);
            audio.setSamplingRate(44100);
            EncodingAttributes attrs = new EncodingAttributes();
            attrs.setOutputFormat("mp3");
            attrs.setAudioAttributes(audio);
            Encoder encoder = new Encoder();
            encoder.encode(new MultimediaObject(source), target, attrs);
        } catch (Exception ex) {
            log.error("音频文件格式转化失败", ex);
            throw new CustomException("音频文件上传失败");
        }
    }

}


--- File: play-admin/src/main/java/com/starry/admin/utils/DictUtils.java ---
package com.starry.admin.utils;

import com.alibaba.fastjson2.JSONArray;
import com.starry.admin.modules.system.module.entity.SysDictDataEntity;
import com.starry.common.constant.CacheConstants;
import com.starry.common.redis.RedisCache;
import com.starry.common.utils.SpringUtils;
import com.starry.common.utils.StringUtils;
import java.util.Collection;
import java.util.List;

/**
 * @author admin
 */
public class DictUtils {

    /**
     * 获取 cache key
     *
     * @param key
     *            参数键
     * @return 缓存键key
     */
    public static String getCacheKey(String key) {
        return CacheConstants.SYS_DICT_KEY + key;
    }

    /**
     * 获取字典缓存
     *
     * @param key
     *            参数键
     * @return dictDatas 字典数据列表
     */
    public static List<SysDictDataEntity> getDictCache(String key) {
        JSONArray arrayCache = SpringUtils.getBean(RedisCache.class).getCacheObject(getCacheKey(key));
        if (StringUtils.isNotNull(arrayCache)) {
            return arrayCache.toList(SysDictDataEntity.class);
        }
        return null;
    }

    /**
     * 设置字典缓存
     *
     * @param key
     *            参数键
     * @param dataList
     *            字典数据列表
     */
    public static void setDictCache(String key, List<SysDictDataEntity> dataList) {
        SpringUtils.getBean(RedisCache.class).setCacheObject(getCacheKey(key), dataList);
    }

    /**
     * 清空字典缓存
     */
    public static void clearDictCache() {
        Collection<String> keys = SpringUtils.getBean(RedisCache.class).keys(CacheConstants.SYS_DICT_KEY + "*");
        SpringUtils.getBean(RedisCache.class).deleteObject(keys);
    }
}


--- File: play-admin/src/main/java/com/starry/admin/utils/ExcelUtils.java ---
package com.starry.admin.utils;

import com.alibaba.excel.EasyExcel;
import java.io.IOException;
import java.io.InputStream;
import java.util.List;
import javax.servlet.http.HttpServletResponse;

/**
 * @author admin
 */
public class ExcelUtils {

    public static List<?> importEasyExcel(InputStream is, Class<?> pojoClass) {
        return EasyExcel.read(is).head(pojoClass).sheet().doReadSync();
    }

    public static void exportEasyExcel(HttpServletResponse response, Class<?> pojoClass, List<?> list,
            String sheetName) {
        try {
            EasyExcel.write(response.getOutputStream(), pojoClass).sheet(sheetName).doWrite(list);
        } catch (IOException e) {
            throw new RuntimeException(e.getMessage());
        }
    }
}


--- File: play-admin/src/main/java/com/starry/admin/utils/HttpUtils.java ---
package com.starry.admin.utils;

import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.security.KeyManagementException;
import java.security.NoSuchAlgorithmException;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;
import org.apache.commons.lang3.StringUtils;
import org.apache.http.HttpResponse;
import org.apache.http.NameValuePair;
import org.apache.http.client.HttpClient;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.HttpDelete;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.methods.HttpPut;
import org.apache.http.conn.ClientConnectionManager;
import org.apache.http.conn.scheme.Scheme;
import org.apache.http.conn.scheme.SchemeRegistry;
import org.apache.http.conn.ssl.SSLSocketFactory;
import org.apache.http.entity.ByteArrayEntity;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.message.BasicNameValuePair;

public class HttpUtils {

    /**
     * get
     *
     * @param host
     * @param path
     * @param method
     * @param headers
     * @param querys
     * @return
     * @throws Exception
     */
    public static HttpResponse doGet(String host, String path, String method, Map<String, String> headers,
            Map<String, String> querys) throws Exception {
        HttpClient httpClient = wrapClient(host);

        HttpGet request = new HttpGet(buildUrl(host, path, querys));
        for (Map.Entry<String, String> e : headers.entrySet()) {
            request.addHeader(e.getKey(), e.getValue());
        }

        return httpClient.execute(request);
    }

    /**
     * post form
     *
     * @param host
     * @param path
     * @param method
     * @param headers
     * @param querys
     * @param bodys
     * @return
     * @throws Exception
     */
    public static HttpResponse doPost(String host, String path, String method, Map<String, String> headers,
            Map<String, String> querys, Map<String, String> bodys) throws Exception {
        HttpClient httpClient = wrapClient(host);

        HttpPost request = new HttpPost(buildUrl(host, path, querys));
        for (Map.Entry<String, String> e : headers.entrySet()) {
            request.addHeader(e.getKey(), e.getValue());
        }

        if (bodys != null) {
            List<NameValuePair> nameValuePairList = new ArrayList<NameValuePair>();

            for (String key : bodys.keySet()) {
                nameValuePairList.add(new BasicNameValuePair(key, bodys.get(key)));
            }
            UrlEncodedFormEntity formEntity = new UrlEncodedFormEntity(nameValuePairList, "utf-8");
            formEntity.setContentType("application/x-www-form-urlencoded; charset=UTF-8");
            request.setEntity(formEntity);
        }

        return httpClient.execute(request);
    }

    /**
     * Post String
     *
     * @param host
     * @param path
     * @param method
     * @param headers
     * @param querys
     * @param body
     * @return
     * @throws Exception
     */
    public static HttpResponse doPost(String host, String path, String method, Map<String, String> headers,
            Map<String, String> querys, String body) throws Exception {
        HttpClient httpClient = wrapClient(host);

        HttpPost request = new HttpPost(buildUrl(host, path, querys));
        for (Map.Entry<String, String> e : headers.entrySet()) {
            request.addHeader(e.getKey(), e.getValue());
        }

        if (StringUtils.isNotBlank(body)) {
            request.setEntity(new StringEntity(body, "utf-8"));
        }

        return httpClient.execute(request);
    }

    /**
     * Post stream
     *
     * @param host
     * @param path
     * @param method
     * @param headers
     * @param querys
     * @param body
     * @return
     * @throws Exception
     */
    public static HttpResponse doPost(String host, String path, String method, Map<String, String> headers,
            Map<String, String> querys, byte[] body) throws Exception {
        HttpClient httpClient = wrapClient(host);

        HttpPost request = new HttpPost(buildUrl(host, path, querys));
        for (Map.Entry<String, String> e : headers.entrySet()) {
            request.addHeader(e.getKey(), e.getValue());
        }

        if (body != null) {
            request.setEntity(new ByteArrayEntity(body));
        }

        return httpClient.execute(request);
    }

    /**
     * Put String
     *
     * @param host
     * @param path
     * @param method
     * @param headers
     * @param querys
     * @param body
     * @return
     * @throws Exception
     */
    public static HttpResponse doPut(String host, String path, String method, Map<String, String> headers,
            Map<String, String> querys, String body) throws Exception {
        HttpClient httpClient = wrapClient(host);

        HttpPut request = new HttpPut(buildUrl(host, path, querys));
        for (Map.Entry<String, String> e : headers.entrySet()) {
            request.addHeader(e.getKey(), e.getValue());
        }

        if (StringUtils.isNotBlank(body)) {
            request.setEntity(new StringEntity(body, "utf-8"));
        }

        return httpClient.execute(request);
    }

    /**
     * Put stream
     *
     * @param host
     * @param path
     * @param method
     * @param headers
     * @param querys
     * @param body
     * @return
     * @throws Exception
     */
    public static HttpResponse doPut(String host, String path, String method, Map<String, String> headers,
            Map<String, String> querys, byte[] body) throws Exception {
        HttpClient httpClient = wrapClient(host);

        HttpPut request = new HttpPut(buildUrl(host, path, querys));
        for (Map.Entry<String, String> e : headers.entrySet()) {
            request.addHeader(e.getKey(), e.getValue());
        }

        if (body != null) {
            request.setEntity(new ByteArrayEntity(body));
        }

        return httpClient.execute(request);
    }

    /**
     * Delete
     *
     * @param host
     * @param path
     * @param method
     * @param headers
     * @param querys
     * @return
     * @throws Exception
     */
    public static HttpResponse doDelete(String host, String path, String method, Map<String, String> headers,
            Map<String, String> querys) throws Exception {
        HttpClient httpClient = wrapClient(host);

        HttpDelete request = new HttpDelete(buildUrl(host, path, querys));
        for (Map.Entry<String, String> e : headers.entrySet()) {
            request.addHeader(e.getKey(), e.getValue());
        }

        return httpClient.execute(request);
    }

    private static String buildUrl(String host, String path, Map<String, String> querys)
            throws UnsupportedEncodingException {
        StringBuilder sbUrl = new StringBuilder();
        sbUrl.append(host);
        if (!StringUtils.isBlank(path)) {
            sbUrl.append(path);
        }
        if (null != querys) {
            StringBuilder sbQuery = new StringBuilder();
            for (Map.Entry<String, String> query : querys.entrySet()) {
                if (0 < sbQuery.length()) {
                    sbQuery.append("&");
                }
                if (StringUtils.isBlank(query.getKey()) && !StringUtils.isBlank(query.getValue())) {
                    sbQuery.append(query.getValue());
                }
                if (!StringUtils.isBlank(query.getKey())) {
                    sbQuery.append(query.getKey());
                    if (!StringUtils.isBlank(query.getValue())) {
                        sbQuery.append("=");
                        sbQuery.append(URLEncoder.encode(query.getValue(), "utf-8"));
                    }
                }
            }
            if (0 < sbQuery.length()) {
                sbUrl.append("?").append(sbQuery);
            }
        }

        return sbUrl.toString();
    }

    private static HttpClient wrapClient(String host) {
        HttpClient httpClient = new DefaultHttpClient();
        if (host.startsWith("https://")) {
            sslClient(httpClient);
        }

        return httpClient;
    }

    private static void sslClient(HttpClient httpClient) {
        try {
            SSLContext ctx = SSLContext.getInstance("TLS");
            X509TrustManager tm = new X509TrustManager() {
                public X509Certificate[] getAcceptedIssuers() {
                    return null;
                }

                public void checkClientTrusted(X509Certificate[] xcs, String str) {

                }

                public void checkServerTrusted(X509Certificate[] xcs, String str) {

                }
            };
            ctx.init(null, new TrustManager[]{tm}, null);
            SSLSocketFactory ssf = new SSLSocketFactory(ctx);
            ssf.setHostnameVerifier(SSLSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER);
            ClientConnectionManager ccm = httpClient.getConnectionManager();
            SchemeRegistry registry = ccm.getSchemeRegistry();
            registry.register(new Scheme("https", 443, ssf));
        } catch (KeyManagementException ex) {
            throw new RuntimeException(ex);
        } catch (NoSuchAlgorithmException ex) {
            throw new RuntimeException(ex);
        }
    }
}


--- File: play-admin/src/main/java/com/starry/admin/utils/ListToStringHandle.java ---
package com.starry.admin.utils;

/**
 * @Author: huchuansai
 * @Date: 2024/3/5 11:52 AM
 * @Description:
 */

import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONArray;
import java.sql.CallableStatement;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.ibatis.type.BaseTypeHandler;
import org.apache.ibatis.type.JdbcType;

/**
 * List<Integer> ==> string
 *
 * @Date 2022-10-08
 */
public class ListToStringHandle extends BaseTypeHandler<List> {

    @Override
    public void setNonNullParameter(PreparedStatement preparedStatement, int i, List list, JdbcType jdbcType)
            throws SQLException {
        if (CollectionUtils.isNotEmpty(list)) {
            preparedStatement.setString(i, JSON.toJSONString(list));
        } else {
            preparedStatement.setString(i, null);
        }

    }

    @Override
    public List getNullableResult(ResultSet resultSet, String s) throws SQLException {
        String result = resultSet.getString(s);
        return result == null ? new ArrayList<>() : JSONArray.parseArray(result);
    }

    @Override
    public List getNullableResult(ResultSet resultSet, int i) throws SQLException {
        String result = resultSet.getString(i);
        return result == null ? new ArrayList<>() : JSONArray.parseArray(result);
    }

    @Override
    public List getNullableResult(CallableStatement callableStatement, int i) throws SQLException {
        String result = callableStatement.getString(i);
        return result == null ? new ArrayList<>() : JSONArray.parseArray(result);
    }
}


--- File: play-admin/src/main/java/com/starry/admin/utils/MoneyUtils.java ---
package com.starry.admin.utils;

import com.starry.admin.common.exception.CustomException;

/**
 * 金额辅助类
 *
 * @author admin
 */
public class MoneyUtils {

    /**
     * 校验金钱值是否正常
     *
     * @param money
     *            金钱值
     */
    public static void verificationTypeIsNormal(String money) {
        if (money == null || money.isEmpty()) {
            throw new CustomException("金额类型异常");
        }
        float item;
        try {
            item = Float.parseFloat(money);
        } catch (Exception e) {
            throw new CustomException("金额类型异常");
        }
        if (item < 0) {
            throw new CustomException("金额不能小于0");
        }
        if (item > Integer.MAX_VALUE) {
            throw new CustomException("金额超出计算范围");
        }
    }

}


--- File: play-admin/src/main/java/com/starry/admin/utils/SecurityUtils.java ---
package com.starry.admin.utils;

import com.starry.admin.common.domain.LoginUser;
import com.starry.admin.common.security.entity.JwtUser;
import com.starry.admin.modules.system.module.entity.SysUserEntity;
import com.starry.common.context.CustomSecurityContextHolder;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;

/**
 * 安全服务工具类
 *
 * @author admin
 */
public class SecurityUtils {
    /**
     * 用户ID
     **/
    public static String getUserId() {
        try {
            return getLoginUser().getUser().getUserId();
        } catch (Exception e) {
            throw new RuntimeException("获取用户ID异常");
        }
    }

    /**
     * 获取用户账户
     **/
    public static String getUsername() {
        try {
            return getLoginUser().getUsername();
        } catch (Exception e) {
            throw new RuntimeException("获取用户账户异常");
        }
    }

    /**
     * 获取用户
     **/
    public static JwtUser getOldLoginUser() {
        try {
            return (JwtUser) getAuthentication().getPrincipal();
        } catch (Exception e) {
            throw new RuntimeException("获取用户信息异常");
        }
    }

    /**
     * 获取用户
     **/
    public static LoginUser getLoginUser() {
        try {
            return (LoginUser) getAuthentication().getPrincipal();
        } catch (Exception e) {
            throw new RuntimeException("获取用户信息异常");
        }
    }

    /**
     * 获取Authentication
     */
    public static Authentication getAuthentication() {
        return SecurityContextHolder.getContext().getAuthentication();
    }

    /**
     * 是否登录
     **/
    public static boolean isLogin() {
        return getAuthentication() != null && getAuthentication().getPrincipal() != null;
    }

    public static boolean isAdmin(SysUserEntity user) {
        return user.getSuperAdmin();
    }

    /**
     * 生成BCryptPasswordEncoder密码
     *
     * @param password
     *            密码
     * @return 加密字符串
     */
    public static String encryptPassword(String password) {
        BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
        return passwordEncoder.encode(password);
    }

    /**
     * 判断密码是否相同
     *
     * @param rawPassword
     *            真实密码
     * @param encodedPassword
     *            加密后字符
     * @return 结果
     */
    public static boolean matchesPassword(String rawPassword, String encodedPassword) {
        BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
        return passwordEncoder.matches(rawPassword, encodedPassword);
    }

    /**
     * 获取租户ID
     */
    public static String getTenantId() {
        return CustomSecurityContextHolder.getTenantId();
    }

    /**
     * 设置租户ID
     */
    public static void setTenantId(String tenantId) {
        CustomSecurityContextHolder.setTenantId(tenantId);
    }
}


--- File: play-admin/src/main/java/com/starry/admin/utils/SmsUtils.java ---
package com.starry.admin.utils;

import cn.hutool.core.util.RandomUtil;
import com.starry.common.redis.RedisCache;
import java.util.HashMap;
import java.util.Map;
import javax.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.apache.http.HttpResponse;
import org.apache.http.util.EntityUtils;
import org.springframework.stereotype.Service;

/**
 * @Author: huchuansai
 * @Date: 2024/7/29 5:41 PM
 * @Description:
 */
@Slf4j
@Service
public class SmsUtils {
    @Resource
    private RedisCache redisCache;

    // 发送短信验证码接口
    public void sendSmsApi(String phone) {
        String code = RandomUtil.randomNumbers(4);
        sendSms(phone, code, "CST_paqequfcmkuv11286");
        String key = "sms:phone:" + phone;
        // 存放到redis里
        redisCache.setCacheObject(key, code, 10L, java.util.concurrent.TimeUnit.MINUTES);
    }

    // 校验短信验证码是否正确
    public void checkSmsCode(String phone, String code) {
        // if(StringUtils.isEmpty(code)){
        // throw new RuntimeException("短信验证码必填");
        // }
        // String key = "sms:phone:" + phone;
        // Object data = redisCache.getCacheObject(key);
        // if (Objects.isNull(data)) {
        // throw new RuntimeException("短信验证码无效或已过期");
        // }
        // if (!code.equals(data.toString())) {
        // throw new RuntimeException("短信验证码错误");
        // }
    }

    private static void sendSms(String phone, String code, String templateId) {
        String host = "https://wwsms.market.alicloudapi.com";
        String path = "/send_sms";
        String method = "POST";
        String appcode = "18639b38538849e1bc8a3413a8d0c0e5";
        Map<String, String> headers = new HashMap<>();
        headers.put("Authorization", "APPCODE " + appcode);
        headers.put("Content-Type", "application/x-www-form-urlencoded; charset=UTF-8");
        Map<String, String> querys = new HashMap<>();
        Map<String, String> bodys = new HashMap<>();
        bodys.put("content", "code:" + code);
        bodys.put("template_id", templateId); // 注意，模板CST_11253 仅作调试使用，下发短信不稳定，请联系客服报备自己的专属签名模板，以保障业务稳定使用
        bodys.put("phone_number", phone);
        try {
            HttpResponse response = HttpUtils.doPost(host, path, method, headers, querys, bodys);
            String resp = EntityUtils.toString(response.getEntity());
            log.info("send sms result：" + resp);
        } catch (Exception e) {
            log.error(e.getMessage(), e);
            throw new RuntimeException(e);
        }
    }

    public static void main(String[] args) {
        sendSms("13210055630", "5566", "CST_paqequfcmkuv11286");
    }

}


--- File: play-admin/src/main/resources/application-dev.yml ---
# 服务名
spring:
  application:
    name: ${SPRING_APPLICATION_NAME:admin-tenant}
  
  flyway:
    table: admin_flyway_schema_history
    baseline-on-migrate: true
    baseline-version: 1
    enabled: true
    locations: classpath:db/migration
    clean-disabled: true
    validate-on-migrate: false
    out-of-order: false
    
  # druid数据源配置
  datasource:
    type: com.alibaba.druid.pool.DruidDataSource
    # 配置MySQL的驱动程序类
    driver-class-name: com.p6spy.engine.spy.P6SpyDriver
    # 数据库连接地址(以MySql为例)
    url: ${SPRING_DATASOURCE_URL:jdbc:p6spy:mysql://primary:3306/play-with?useUnicode=true&characterEncoding=utf8&characterSetResults=utf8}
    # 数据库对应的用户名
    username: ${SPRING_DATASOURCE_USERNAME:root}
    # 数据库对应的密码
    password: ${SPRING_DATASOURCE_PASSWORD:123456}
    druid:
      enable: true
      db-type: mysql
      # 配置监控统计拦截的filters，stat是sql监控，wall是防火墙(如果不添加则监控无效)，添加log4j需要引入jar包
      filters: stat,wall,config
      # 连接池最大活跃连接数
      max-active: ${SPRING_DATASOURCE_DRUID_MAX_ACTIVE:100}
      # 连接池初始化连接数量
      initial-size: ${SPRING_DATASOURCE_DRUID_INITIAL_SIZE:1}
      # 配置获取连接等待超时的时间
      max-wait: ${SPRING_DATASOURCE_DRUID_MAX_WAIT:60000}
      # 连接池最小空闲数
      min-idle: ${SPRING_DATASOURCE_DRUID_MIN_IDLE:1}
      # 配置间隔多久才进行一次检测，检测需要关闭的空闲连接，单位是毫秒
      timeBetweenEvictionRunsMillis: ${SPRING_DATASOURCE_DRUID_TIME_BETWEEN_EVICTION_RUNS_MILLIS:60000}
      # 配置一个连接在池中最小生存的时间，单位是毫秒
      minEvictableIdleTimeMillis: ${SPRING_DATASOURCE_DRUID_MIN_EVICTABLE_IDLE_TIME_MILLIS:300000}
      # 指定空闲连接检查、废弃连接清理、空闲连接池大小调整之间的操作时间间隔
      time-between-eviction-runs-millis: ${SPRING_DATASOURCE_DRUID_TIME_BETWEEN_EVICTION_RUNS_MILLIS:60000}
      # 指定一个空闲连接最少空闲多久后可被清除
      min-evictable-idle-time-millis: ${SPRING_DATASOURCE_DRUID_MIN_EVICTABLE_IDLE_TIME_MILLIS:300000}
      # 连接是否有效的查询语句
      validation-query: select 'x'
      test-while-idle: true
      test-on-borrow: false
      test-on-return: false
      # 打开 PSCache，并且指定每个连接上 PSCache 的大小
      pool-prepared-statements: true
      max-open-prepared-statements: ${SPRING_DATASOURCE_DRUID_MAX_OPEN_PREPARED_STATEMENTS:50}
      max-pool-prepared-statement-per-connection-size: ${SPRING_DATASOURCE_DRUID_MAX_POOL_PREPARED_STATEMENT_PER_CONNECTION_SIZE:20}
      # 配置 DruidStatFilter
      web-stat-filter:
        enabled: true
        # 排除一些不必要的url，比如.js,/jslib/等
        exclusions: "*.js,*.gif,*.jpg,*.png,*.css,*.ico,/druid/*"
        # 过滤规则
        url-pattern: /*
      # 配置 DruidStatViewServlet
      stat-view-servlet:
        # 手动重置监控数据
        enabled: true
        # IP白名单，没有配置或者为空，则允许所有访问
        allow: ${SPRING_DATASOURCE_DRUID_STAT_VIEW_SERVLET_ALLOW:127.0.0.1}
        # IP黑名单，若白名单也存在，则优先使用
        deny:
        # 配置druid登录用户名、密码
        login-username: ${SPRING_DATASOURCE_DRUID_STAT_VIEW_SERVLET_LOGIN_USERNAME:admin}
        login-password: ${SPRING_DATASOURCE_DRUID_STAT_VIEW_SERVLET_LOGIN_PASSWORD:admin}
        # HTML 中 Reset All 按钮
        reset-enable: true
  redis:
    host: ${SPRING_REDIS_HOST:127.0.0.1} # Redis服务器地址
    database: ${SPRING_REDIS_DATABASE:10} # Redis数据库索引（默认为0）
    port: ${SPRING_REDIS_PORT:6379} # Redis服务器连接端口
    password: ${SPRING_REDIS_PASSWORD:Spinfo@0123}
    timeout: ${SPRING_REDIS_TIMEOUT:3000ms} # 连接超时时间（毫秒）

# 全局日志级别
logging:
  level:
    root: ${LOGGING_LEVEL_ROOT:info}
    com.starry: debug
    com.starry.admin.modules.weichat: debug
    com.starry.admin.modules.order: debug
    com.starry.admin.modules.clerk: debug
    com.starry.admin.modules.custom: debug
    com.starry.admin.modules.system: debug
    org.springframework.web.servlet.DispatcherServlet: debug
    org.springframework.security: debug

jwt:
  tokenHeader: X-Token #JWT存储的请求头
  tokenHead: Bearer  #JWT负载中拿到开头
  secret: yz-admin-secret #JWT加解密使用的密钥
  expiration: 360000 #JWT的超期限时间秒(60*60*24)

# token配置
token:
  # 令牌自定义标识
  header: Authorization
  # 令牌密钥
  secret: abcdefghijklmnopqrstuvwxyz
  # 令牌有效期，单位分钟（默认30分钟）
  expireTime: 129600

# xl自定义配置
xl:
  login:
    authCode:
      # 登录验证码是否开启，开发环境配置false方便测试
      enable: ${XL_LOGIN_AUTHCODE_ENABLE:false}




--- File: play-admin/src/main/resources/application-prod.yml ---
# 服务名
spring:
  application:
    name: admin-ucenter

  flyway:
    table: admin_flyway_schema_history
    baseline-on-migrate: true
    baseline-version: 1
    enabled: true
    locations: classpath:db/migration
    clean-disabled: true
    validate-on-migrate: false
    out-of-order: false
  # druid数据源配置
  datasource:
    type: com.alibaba.druid.pool.DruidDataSource
    # 配置MySQL的驱动程序类
    driver-class-name: com.mysql.cj.jdbc.Driver
    # 数据库连接地址(以MySql为例)
    url: jdbc:mysql://localhost:3306/admin?useUnicode=true&characterEncoding=utf8&characterSetResults=utf8
    # 数据库对应的用户名
    username: root
    # 数据库对应的密码
    password: root
    druid:
      # 配置监控统计拦截的filters，stat是sql监控，wall是防火墙(如果不添加则监控无效)，添加log4j需要引入jar包
      filters: stat,wall,config
      # 连接池最大活跃连接数
      max-active: 100
      # 连接池初始化连接数量
      initial-size: 1
      # 配置获取连接等待超时的时间
      max-wait: 60000
      # 连接池最小空闲数
      min-idle: 1
      # 配置间隔多久才进行一次检测，检测需要关闭的空闲连接，单位是毫秒
      timeBetweenEvictionRunsMillis: 60000
      # 配置一个连接在池中最小生存的时间，单位是毫秒
      minEvictableIdleTimeMillis: 300000
      # 指定空闲连接检查、废弃连接清理、空闲连接池大小调整之间的操作时间间隔
      time-between-eviction-runs-millis: 60000
      # 指定一个空闲连接最少空闲多久后可被清除
      min-evictable-idle-time-millis: 300000
      # 连接是否有效的查询语句
      validation-query: select 'x'
      test-while-idle: true
      test-on-borrow: false
      test-on-return: false
      # 打开 PSCache，并且指定每个连接上 PSCache 的大小
      pool-prepared-statements: true
      max-open-prepared-statements: 50
      max-pool-prepared-statement-per-connection-size: 20
      # 配置 DruidStatFilter
      web-stat-filter:
        enabled: true #\u662F\u5426\u542F\u7528StatFilter\u9ED8\u8BA4\u503Ctrue
        # 排除一些不必要的url，比如.js,/jslib/等
        exclusions: "*.js,*.gif,*.jpg,*.png,*.css,*.ico,/druid/*"
        # 过滤规则
        url-pattern: /*
      # 配置 DruidStatViewServlet
      stat-view-servlet:
        # 手动重置监控数据
        enabled: true
        # IP白名单，没有配置或者为空，则允许所有访问
        allow: 127.0.0.1
        # IP黑名单，若白名单也存在，则优先使用
        deny:
        # 配置druid登录用户名、密码
        login-username: admin
        login-password: admin
        # HTML 中 Reset All 按钮
        reset-enable: true
  redis:
    host: localhost # Redis服务器地址
    database: 0 # Redis数据库索引（默认为0）
    port: 6379 # Redis服务器连接端口
    password: yz_admin # Redis服务器连接密码（默认为空）
    timeout: 3000ms # 连接超时时间（毫秒）

# 全局日志级别
logging:
  level:
    root: info

jwt:
  tokenHeader: X-Token #JWT存储的请求头
  tokenHead: Bearer  #JWT负载中拿到开头
  secret: yz-admin-secret #JWT加解密使用的密钥
  expiration: 360000 #JWT的超期限时间秒(60*60*24)

# xl自定义配置
xl:
  login:
    authCode:
      # 登录验证码是否开启，开发环境配置false方便测试
      enable: false

--- File: play-admin/src/main/resources/application-test.yml ---
# 服务名
spring:
  application:
    name: admin-tenant
  
  flyway:
    table: admin_flyway_schema_history
    baseline-on-migrate: true
    baseline-version: 1
    enabled: true
    locations: classpath:db/migration
    clean-disabled: true
    validate-on-migrate: false
    out-of-order: false

  # druid数据源配置
  datasource:
    type: com.alibaba.druid.pool.DruidDataSource
    # 配置MySQL的驱动程序类
    driver-class-name: com.p6spy.engine.spy.P6SpyDriver
    # 数据库连接地址(以MySql为例)
    url: jdbc:p6spy:mysql://122.51.20.105:3306/play-with?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8&allowMultiQueries=true&rewriteBatchedStatements=true
    # 数据库对应的用户名
    username: root
    # 数据库对应的密码
    password: KdaKRZ2trpdhNePa
    # 批量插入
    rewriteBatchedStatements: true
    druid:
      enable: true
      db-type: mysql
      # 配置监控统计拦截的filters，stat是sql监控，wall是防火墙(如果不添加则监控无效)，添加log4j需要引入jar包
      filters: stat,wall,config
      # 连接池最大活跃连接数
      max-active: 100
      # 连接池初始化连接数量
      initial-size: 1
      # 配置获取连接等待超时的时间
      max-wait: 60000
      # 连接池最小空闲数
      min-idle: 1
      # 配置间隔多久才进行一次检测，检测需要关闭的空闲连接，单位是毫秒
      timeBetweenEvictionRunsMillis: 60000
      # 配置一个连接在池中最小生存的时间，单位是毫秒
      minEvictableIdleTimeMillis: 300000
      # 指定空闲连接检查、废弃连接清理、空闲连接池大小调整之间的操作时间间隔
      time-between-eviction-runs-millis: 60000
      # 指定一个空闲连接最少空闲多久后可被清除
      min-evictable-idle-time-millis: 300000
      # 连接是否有效的查询语句
      validation-query: select 'x'
      test-while-idle: true
      test-on-borrow: false
      test-on-return: false
      # 打开 PSCache，并且指定每个连接上 PSCache 的大小
      pool-prepared-statements: true
      max-open-prepared-statements: 50
      max-pool-prepared-statement-per-connection-size: 20
      # 配置 DruidStatFilter
      web-stat-filter:
        enabled: true #\u662F\u5426\u542F\u7528StatFilter\u9ED8\u8BA4\u503Ctrue
        # 排除一些不必要的url，比如.js,/jslib/等
        exclusions: "*.js,*.gif,*.jpg,*.png,*.css,*.ico,/druid/*"
        # 过滤规则
        url-pattern: /*
      # 配置 DruidStatViewServlet
      stat-view-servlet:
        # 手动重置监控数据
        enabled: true
        # IP白名单，没有配置或者为空，则允许所有访问
        allow: 127.0.0.1
        # IP黑名单，若白名单也存在，则优先使用
        deny:
        # 配置druid登录用户名、密码
        login-username: admin
        login-password: admin
        # HTML 中 Reset All 按钮
        reset-enable: true
  redis:
    host: 122.51.20.105 # Redis服务器地址
    database: 10 # Redis数据库索引（默认为0）
    port: 19087 # Redis服务器连接端口
    password: arcikXa7ZkEBGtLf
    timeout: 3000ms # 连接超时时间（毫秒）



# 全局日志级别
logging:
  level:
    root: info

jwt:
  tokenHeader: X-Token #JWT存储的请求头
  tokenHead: Bearer  #JWT负载中拿到开头
  secret: yz-admin-secret #JWT加解密使用的密钥
  expiration: 360000 #JWT的超期限时间秒(60*60*24)

# token配置
token:
  # 令牌自定义标识
  header: Authorization
  # 令牌密钥
  secret: abcdefghijklmnopqrstuvwxyz
  # 令牌有效期，单位分钟（默认30分钟）
  expireTime: 129600

# xl自定义配置
xl:
  login:
    authCode:
      # 登录验证码是否开启，开发环境配置false方便测试
      enable: false


--- File: play-admin/src/main/resources/application.yml ---
server:
  port: 7002
  servlet:
    context-path: /api
spring:
  profiles:
    active: test
  servlet:
    multipart:
      max-file-size: 100MB
      max-request-size: 1000MB
logging:
  config: classpath:logback-spring.xml
# mybatis日志
mybatis-plus:
  global-config:
    # 逻辑删除
    db-config:
      logic-delete-field: deleted  # 全局逻辑删除的实体字段名
      logic-delete-value: 1
      logic-not-delete-value: 0
  # xml文件路径，classpath* 代表所有模块的resources目录 classpath 不加星号代表当前模块下的resources目录
  mapper-locations: classpath*:mapper/**/*.xml

wx:
  mp:
    app-id: xxx
    secret: ddd
    config-storage:
      key-prefix: peipei
      http-client-type: HttpClient
      redis:
        host: 12
        database: ${spring.redis.database}
        port: ${spring.redis.port}
        password: ${spring.redis.password}
      type: RedisTemplate

--- File: play-admin/src/main/resources/db/migration/V1.2025.0609.10.11__change_tables.sql ---
ALTER TABLE `play_clerk_level_info`
    ADD COLUMN `style_type` int(10) NULL COMMENT '等级样式' AFTER `version`,
ADD COLUMN `style_image_url` varchar(255) NULL COMMENT '等级图片' AFTER `style_type`;


update `play_clerk_level_info` set `style_type` = `level`;
CREATE TABLE `shop_ui_setting` (
                                   `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 '逻辑删除 1已删除 0未删除',
                                   `version` int(11) NOT NULL DEFAULT '1' COMMENT '数据版本',
                                   `setting_config` text COMMENT '配置内容',
                                   PRIMARY KEY (`id`) USING BTREE,
                                   KEY `key_tenant_id` (`tenant_id`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC COMMENT='店员等级';

--- File: play-admin/src/main/resources/db/migration/V1__init_schema.sql ---
-- MySQL dump 10.13  Distrib 8.0.41, for Linux (x86_64)
--
-- Host: primary    Database: play-with
-- ------------------------------------------------------
-- Server version	8.2.0

/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */;
/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */;
/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */;
/*!50503 SET NAMES utf8mb4 */;
/*!40103 SET @OLD_TIME_ZONE=@@TIME_ZONE */;
/*!40103 SET TIME_ZONE='+00:00' */;
/*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */;
/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */;
/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */;
/*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */;

--
-- Table structure for table `commodity_info`
--

DROP TABLE IF EXISTS `commodity_info`;
/*!40101 SET @saved_cs_client     = @@character_set_client */;
/*!50503 SET character_set_client = utf8mb4 */;
CREATE TABLE `commodity_info` (
  `id` varchar(32) NOT NULL COMMENT 'UUID',
  `money` varchar(255) DEFAULT NULL COMMENT '价格',
  `updated_time` datetime DEFAULT NULL COMMENT '修改时间',
  `updated_by` varchar(32) DEFAULT NULL COMMENT '修改人的id',
  `created_time` datetime DEFAULT NULL COMMENT '创建时间',
  `created_by` varchar(32) DEFAULT NULL COMMENT '创建人的id',
  `deleted` tinyint(1) NOT NULL DEFAULT '0' COMMENT '逻辑删除 1已删除 0未删除',
  `version` int NOT NULL DEFAULT '1' COMMENT '数据版本',
  PRIMARY KEY (`id`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci ROW_FORMAT=DYNAMIC;
/*!40101 SET character_set_client = @saved_cs_client */;

--
-- Table structure for table `gen_table`
--

DROP TABLE IF EXISTS `gen_table`;
/*!40101 SET @saved_cs_client     = @@character_set_client */;
/*!50503 SET character_set_client = utf8mb4 */;
CREATE TABLE `gen_table` (
  `table_id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键ID',
  `table_name` varchar(50) NOT NULL DEFAULT '0' COMMENT '表名称',
  `table_comment` varchar(64) NOT NULL COMMENT '表描述',
  `sub_table_name` varchar(64) DEFAULT NULL COMMENT '关联子表的表名',
  `sub_table_fk_name` varchar(50) DEFAULT NULL COMMENT '子表关联的外键名',
  `class_name` varchar(100) DEFAULT NULL COMMENT '实体类名称',
  `tpl_category` varchar(30) DEFAULT 'crud' COMMENT '使用的模板（crud单表操作 tree树表操作）',
  `package_name` varchar(100) DEFAULT '0' COMMENT '生成包路径',
  `module_name` varchar(30) DEFAULT NULL COMMENT '生成模块名',
  `business_name` varchar(30) DEFAULT NULL COMMENT '生成业务名',
  `function_name` varchar(30) DEFAULT NULL COMMENT '生成功能名',
  `function_author` varchar(30) DEFAULT NULL COMMENT '生成功能作者',
  `gen_type` char(1) DEFAULT '0' COMMENT '生成代码方式（0zip压缩包 1自定义路径）',
  `gen_path` varchar(100) DEFAULT '/' COMMENT '生成路径（不填默认项目路径）',
  `options` varchar(1000) DEFAULT NULL COMMENT '其它生成选项',
  `updated_time` datetime DEFAULT NULL COMMENT '修改时间',
  `updated_by` varchar(32) DEFAULT NULL COMMENT '修改人的id',
  `created_time` datetime DEFAULT NULL COMMENT '创建时间',
  `created_by` varchar(32) DEFAULT NULL COMMENT '创建人的id',
  `deleted` tinyint(1) NOT NULL DEFAULT '0' COMMENT '逻辑删除 1已删除 0未删除',
  `version` int NOT NULL DEFAULT '1' COMMENT '数据版本',
  PRIMARY KEY (`table_id`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=73 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci ROW_FORMAT=DYNAMIC COMMENT='代码生成业务表';
/*!40101 SET character_set_client = @saved_cs_client */;

--
-- Table structure for table `gen_table_column`
--

DROP TABLE IF EXISTS `gen_table_column`;
/*!40101 SET @saved_cs_client     = @@character_set_client */;
/*!50503 SET character_set_client = utf8mb4 */;
CREATE TABLE `gen_table_column` (
  `column_id` int NOT NULL AUTO_INCREMENT COMMENT '主键ID',
  `table_id` int DEFAULT NULL COMMENT '归属表id',
  `column_name` varchar(50) NOT NULL DEFAULT '0' COMMENT '列名称',
  `column_comment` varchar(1000) NOT NULL COMMENT '列描述',
  `column_type` varchar(64) DEFAULT NULL COMMENT '列类型',
  `java_type` varchar(50) DEFAULT NULL COMMENT 'JAVA类型',
  `java_field` varchar(100) DEFAULT NULL COMMENT 'JAVA字段名',
  `is_pk` char(1) DEFAULT NULL COMMENT '是否主键（1是）',
  `is_increment` char(1) DEFAULT NULL COMMENT '是否自增（1是）',
  `is_required` char(1) DEFAULT NULL COMMENT '是否必填（1是）',
  `is_insert` char(1) DEFAULT NULL COMMENT '是否为插入字段（1是）',
  `is_edit` char(1) DEFAULT NULL COMMENT '是否编辑字段（1是）',
  `is_list` char(1) DEFAULT NULL COMMENT '是否列表字段（1是）',
  `is_query` char(1) DEFAULT '0' COMMENT '是否查询字段（1是）',
  `query_type` varchar(10) DEFAULT 'EQ' COMMENT '查询方式（等于、不等于、大于、小于、范围）',
  `html_type` varchar(100) DEFAULT NULL COMMENT '显示类型（文本框、文本域、下拉框、复选框、单选框、日期控件）',
  `dict_type` varchar(100) DEFAULT NULL COMMENT '字典类型',
  `sort` int DEFAULT NULL COMMENT '排序',
  `updated_time` datetime DEFAULT NULL COMMENT '修改时间',
  `updated_by` varchar(32) DEFAULT NULL COMMENT '修改人的id',
  `created_time` datetime DEFAULT NULL COMMENT '创建时间',
  `created_by` varchar(32) DEFAULT NULL COMMENT '创建人的id',
  `deleted` tinyint(1) NOT NULL DEFAULT '0' COMMENT '逻辑删除 1已删除 0未删除',
  `version` int NOT NULL DEFAULT '1' COMMENT '数据版本',
  PRIMARY KEY (`column_id`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=976 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci ROW_FORMAT=DYNAMIC COMMENT='代码生成业务表字段';
/*!40101 SET character_set_client = @saved_cs_client */;

--
-- Table structure for table `order_details_info`
--

DROP TABLE IF EXISTS `order_details_info`;
/*!40101 SET @saved_cs_client     = @@character_set_client */;
/*!50503 SET character_set_client = utf8mb4 */;
CREATE TABLE `order_details_info` (
  `id` varchar(32) NOT NULL COMMENT 'UUID',
  `order_id` varchar(32) DEFAULT NULL COMMENT '订单ID',
  `commodity_id` varchar(32) DEFAULT NULL COMMENT '商品类型',
  `commodity_type` varchar(1) DEFAULT NULL COMMENT '商品类型【0:1】\r\n0：服务\r\n1：优惠券',
  `commodity_money` varchar(255) DEFAULT NULL COMMENT '商品金额',
  `remake` varchar(1000) DEFAULT NULL COMMENT '备注',
  `updated_by` varchar(32) DEFAULT NULL COMMENT '修改人的id',
  `created_time` datetime DEFAULT NULL COMMENT '创建时间',
  `created_by` varchar(32) DEFAULT NULL COMMENT '创建人的id',
  `deleted` tinyint(1) NOT NULL DEFAULT '0' COMMENT '逻辑删除 1已删除 0未删除',
  `version` int NOT NULL DEFAULT '1' COMMENT '数据版本',
  PRIMARY KEY (`id`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci ROW_FORMAT=DYNAMIC COMMENT='订单详情';
/*!40101 SET character_set_client = @saved_cs_client */;

--
-- Table structure for table `order_log_info`
--

DROP TABLE IF EXISTS `order_log_info`;
/*!40101 SET @saved_cs_client     = @@character_set_client */;
/*!50503 SET character_set_client = utf8mb4 */;
CREATE TABLE `order_log_info` (
  `id` varchar(255) NOT NULL COMMENT 'uuid',
  `tenant_id` varchar(32) DEFAULT NULL COMMENT '租户ID',
  `order_id` varchar(1) DEFAULT NULL COMMENT '订单ID',
  `oper_type` varchar(1) DEFAULT NULL COMMENT '操作类型\r\n0:下单\r\n1:审核\r\n2:接单\r\n3:服务\r\n4:完成\r\n5:取消',
  `oper_time` datetime(1) DEFAULT NULL COMMENT '操作时间',
  `remark` varchar(255) DEFAULT NULL COMMENT '备注',
  `updated_time` datetime DEFAULT NULL COMMENT '修改时间',
  `updated_by` varchar(32) DEFAULT NULL COMMENT '修改人的id',
  `created_time` datetime DEFAULT NULL COMMENT '创建时间',
  `created_by` varchar(32) DEFAULT NULL COMMENT '创建人的id',
  `deleted` tinyint(1) NOT NULL DEFAULT '0' COMMENT '逻辑删除 1已删除 0未删除',
  `version` int NOT NULL DEFAULT '1' COMMENT '数据版本',
  PRIMARY KEY (`id`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci ROW_FORMAT=DYNAMIC COMMENT='订单日志';
/*!40101 SET character_set_client = @saved_cs_client */;

--
-- Table structure for table `play_account info`
--

DROP TABLE IF EXISTS `play_account info`;
/*!40101 SET @saved_cs_client     = @@character_set_client */;
/*!50503 SET character_set_client = utf8mb4 */;
CREATE TABLE `play_account info` (
  `id` varchar(32) NOT NULL COMMENT 'UUID',
  `tenant_id` varchar(32) NOT NULL COMMENT '租户ID',
  `avatar_frame_type` bigint DEFAULT NULL COMMENT '头像框分类\r\n0：店员头像框\r\n1：客户头像框\r\n',
  `avatar_frame_address` varchar(1000) DEFAULT NULL COMMENT '头像框地址',
  `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 '逻辑删除 1已删除 0未删除',
  `version` int NOT NULL DEFAULT '1' COMMENT '数据版本',
  PRIMARY KEY (`id`) USING BTREE,
  KEY `key_tenant_id` (`tenant_id`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci ROW_FORMAT=DYNAMIC COMMENT='店员头像框';
/*!40101 SET character_set_client = @saved_cs_client */;

--
-- Table structure for table `play_avatar_frame_info`
--

DROP TABLE IF EXISTS `play_avatar_frame_info`;
/*!40101 SET @saved_cs_client     = @@character_set_client */;
/*!50503 SET character_set_client = utf8mb4 */;
CREATE TABLE `play_avatar_frame_info` (
  `id` varchar(32) NOT NULL COMMENT 'UUID',
  `tenant_id` varchar(32) NOT NULL COMMENT '租户ID',
  `avatar_frame_name` varchar(255) DEFAULT NULL COMMENT '头像框名称',
  `avatar_frame_address` varchar(255) DEFAULT NULL COMMENT '头像框地址',
  `access_type` varchar(1) DEFAULT '0' COMMENT '获取方式(0:手动赠送;1:自动赠送)',
  `access_name` text COMMENT '获取条件名称',
  `access_value` text COMMENT '获取条件',
  `period_validity` varchar(10) DEFAULT NULL COMMENT '有效期（单位：天，-1标识永久有效）',
  `add_time` datetime(1) DEFAULT NULL COMMENT '添加时间',
  `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 '逻辑删除 1已删除 0未删除',
  `version` int NOT NULL DEFAULT '1' COMMENT '数据版本',
  PRIMARY KEY (`id`) USING BTREE,
  KEY `key_tenant_id` (`tenant_id`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci ROW_FORMAT=DYNAMIC COMMENT='店员头像框';
/*!40101 SET character_set_client = @saved_cs_client */;

--
-- Table structure for table `play_balance_details_info`
--

DROP TABLE IF EXISTS `play_balance_details_info`;
/*!40101 SET @saved_cs_client     = @@character_set_client */;
/*!50503 SET character_set_client = utf8mb4 */;
CREATE TABLE `play_balance_details_info` (
  `id` varchar(32) NOT NULL COMMENT 'UUID',
  `tenant_id` varchar(32) NOT NULL COMMENT '租户ID',
  `user_id` varchar(32) DEFAULT NULL COMMENT '用户ID',
  `user_type` varchar(1) DEFAULT NULL COMMENT '用户类型[0:陪玩;1:顾客]',
  `balance_before_operation` decimal(10,2) DEFAULT NULL COMMENT '操作前余额',
  `balance_after_operation` decimal(10,2) DEFAULT NULL COMMENT '操作后余额',
  `operation_type` varchar(1) DEFAULT NULL COMMENT '操作类型（0:充值;1:消费;2:服务）',
  `operation_action` varchar(255) DEFAULT NULL COMMENT '操作动作',
  `operation_time` datetime(1) DEFAULT NULL COMMENT '操作时间',
  `balance_money` decimal(10,2) DEFAULT '0.00' COMMENT '操作金额',
  `gift_amount` decimal(10,2) DEFAULT '0.00' COMMENT '赠送金额',
  `payment_amount` decimal(10,2) DEFAULT '0.00' COMMENT '支付金额',
  `order_id` varchar(32) DEFAULT 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 '逻辑删除 1已删除 0未删除',
  `version` int NOT NULL DEFAULT '1' COMMENT '数据版本',
  PRIMARY KEY (`id`) USING BTREE,
  KEY `key_tenant_id` (`tenant_id`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci ROW_FORMAT=DYNAMIC COMMENT='余额明细';
/*!40101 SET character_set_client = @saved_cs_client */;

--
-- Table structure for table `play_clerk_article_info`
--

DROP TABLE IF EXISTS `play_clerk_article_info`;
/*!40101 SET @saved_cs_client     = @@character_set_client */;
/*!50503 SET character_set_client = utf8mb4 */;
CREATE TABLE `play_clerk_article_info` (
  `id` varchar(32) NOT NULL DEFAULT '0' COMMENT 'UUID',
  `tenant_id` varchar(32) NOT NULL COMMENT '租户ID',
  `clerk_id` varchar(32) DEFAULT NULL COMMENT '陪玩用户ID',
  `article_title` longtext COMMENT '动态标题',
  `article_con` text COMMENT '动态内容',
  `annex_type` varchar(1) NOT NULL COMMENT '动态附件类型（0：图片，1：视频）',
  `annex_con` text COMMENT '附件内容',
  `release_time` datetime DEFAULT NULL COMMENT '发布时间',
  `review_state` varchar(1) DEFAULT NULL COMMENT '审核状态（0：未审核:1：审核通过，2：审核不通过）',
  `review_by` varchar(32) DEFAULT NULL COMMENT '审核人',
  `review_time` datetime DEFAULT NULL COMMENT '审核时间',
  `review_con` text COMMENT '备注',
  `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` varchar(1) NOT NULL DEFAULT '0' COMMENT '逻辑删除 1已删除 0未删除',
  `version` int NOT NULL DEFAULT '1' COMMENT '数据版本',
  PRIMARY KEY (`id`) USING BTREE,
  KEY `key_tenant_id` (`tenant_id`) USING BTREE,
  KEY `key_openid` (`annex_type`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci ROW_FORMAT=DYNAMIC COMMENT='店员动态信息';
/*!40101 SET character_set_client = @saved_cs_client */;

--
-- Table structure for table `play_clerk_classification_info`
--

DROP TABLE IF EXISTS `play_clerk_classification_info`;
/*!40101 SET @saved_cs_client     = @@character_set_client */;
/*!50503 SET character_set_client = utf8mb4 */;
CREATE TABLE `play_clerk_classification_info` (
  `id` varchar(32) NOT NULL COMMENT 'UUID',
  `tenant_id` varchar(32) NOT NULL COMMENT '租户ID',
  `name` varchar(255) DEFAULT NULL COMMENT '类型名称',
  `sort` tinyint(1) DEFAULT NULL COMMENT '（排序字段）',
  `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 '逻辑删除 1已删除 0未删除',
  `version` int NOT NULL DEFAULT '1' COMMENT '数据版本',
  PRIMARY KEY (`id`) USING BTREE,
  KEY `key_tenant_id` (`tenant_id`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci ROW_FORMAT=DYNAMIC COMMENT='店员分类';
/*!40101 SET character_set_client = @saved_cs_client */;

--
-- Table structure for table `play_clerk_commodity_info`
--

DROP TABLE IF EXISTS `play_clerk_commodity_info`;
/*!40101 SET @saved_cs_client     = @@character_set_client */;
/*!50503 SET character_set_client = utf8mb4 */;
CREATE TABLE `play_clerk_commodity_info` (
  `id` varchar(32) NOT NULL COMMENT 'UUID',
  `tenant_id` varchar(32) NOT NULL COMMENT '租户ID',
  `clerk_id` varchar(32) DEFAULT NULL COMMENT '店员用户ID',
  `commodity_id` varchar(32) DEFAULT NULL COMMENT '服务项目ID',
  `commodity_name` varchar(255) DEFAULT NULL COMMENT '服务项目名称',
  `sort` varchar(255) DEFAULT NULL COMMENT '服务项目排序',
  `enabling_state` tinyint(1) DEFAULT NULL COMMENT '服务启动状态\r\n0:停用\r\n1:启用',
  `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 '逻辑删除 1已删除 0未删除',
  `version` int NOT NULL DEFAULT '1' COMMENT '数据版本',
  PRIMARY KEY (`id`) USING BTREE,
  KEY `key_tenant_id` (`tenant_id`) USING BTREE,
  KEY `key_play_user_id` (`clerk_id`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci ROW_FORMAT=DYNAMIC COMMENT='陪玩和项目关系';
/*!40101 SET character_set_client = @saved_cs_client */;

--
-- Table structure for table `play_clerk_data_review_info`
--

DROP TABLE IF EXISTS `play_clerk_data_review_info`;
/*!40101 SET @saved_cs_client     = @@character_set_client */;
/*!50503 SET character_set_client = utf8mb4 */;
CREATE TABLE `play_clerk_data_review_info` (
  `id` varchar(32) NOT NULL COMMENT 'UUID',
  `tenant_id` varchar(32) NOT NULL COMMENT '租户ID',
  `clerk_id` varchar(32) DEFAULT NULL COMMENT '店员ID',
  `data_type` varchar(1) DEFAULT NULL COMMENT '资料类型[0:昵称;1:头像;2:相册;3:录音]',
  `data_content` text COMMENT '资料内容',
  `review_state` varchar(1) DEFAULT NULL COMMENT ' 审核状态（0：未审核:1：审核通过，2：审核不通过）',
  `add_time` datetime(1) DEFAULT NULL COMMENT '资料添加时间',
  `review_time` datetime(1) DEFAULT NULL COMMENT '审核时间',
  `review_by` varchar(32) DEFAULT NULL COMMENT '审核人',
  `review_con` text COMMENT '审核内容',
  `remark` text COMMENT '备注',
  `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 '逻辑删除 1已删除 0未删除',
  `version` int NOT NULL DEFAULT '1' COMMENT '数据版本',
  PRIMARY KEY (`id`) USING BTREE,
  KEY `key_tenant_id` (`tenant_id`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci ROW_FORMAT=DYNAMIC COMMENT='店员资料审核';
/*!40101 SET character_set_client = @saved_cs_client */;

--
-- Table structure for table `play_clerk_gift_info`
--

DROP TABLE IF EXISTS `play_clerk_gift_info`;
/*!40101 SET @saved_cs_client     = @@character_set_client */;
/*!50503 SET character_set_client = utf8mb4 */;
CREATE TABLE `play_clerk_gift_info` (
  `id` varchar(32) NOT NULL COMMENT 'UUID',
  `tenant_id` varchar(32) NOT NULL COMMENT '租户ID',
  `clerk_id` varchar(32) DEFAULT NULL COMMENT '店员ID',
  `giff_id` varchar(32) DEFAULT NULL COMMENT '礼物ID',
  `giff_number` int DEFAULT NULL COMMENT '礼物数量',
  `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 '逻辑删除 1已删除 0未删除',
  `version` int NOT NULL DEFAULT '1' COMMENT '数据版本',
  PRIMARY KEY (`id`) USING BTREE,
  KEY `key_tenant_id` (`tenant_id`) USING BTREE,
  KEY `key_openid` (`clerk_id`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci ROW_FORMAT=DYNAMIC COMMENT='店员和礼物关系';
/*!40101 SET character_set_client = @saved_cs_client */;

--
-- Table structure for table `play_clerk_level_info`
--

DROP TABLE IF EXISTS `play_clerk_level_info`;
/*!40101 SET @saved_cs_client     = @@character_set_client */;
/*!50503 SET character_set_client = utf8mb4 */;
CREATE TABLE `play_clerk_level_info` (
  `id` varchar(32) NOT NULL COMMENT 'UUID',
  `tenant_id` varchar(32) NOT NULL COMMENT '租户ID',
  `name` varchar(255) DEFAULT NULL COMMENT '等级名称',
  `level` tinyint(1) DEFAULT NULL COMMENT '等级数字（排序字段）',
  `not_first_regular_ratio` tinyint(1) DEFAULT NULL COMMENT '非首次固定单比例[0 - 100%]',
  `first_regular_ratio` tinyint(1) DEFAULT NULL COMMENT '首次固定单比例[0 - 100%]',
  `first_reward_ratio` tinyint(1) DEFAULT NULL COMMENT '首次打赏比例[0 - 100%]',
  `not_first_reward_ratio` tinyint(1) DEFAULT NULL COMMENT '非首次打赏比例[0 - 100%]',
  `first_random_radio` tinyint(1) DEFAULT NULL COMMENT '首次随机单比例[0 - 100%]',
  `not_first_random_radio` tinyint(1) DEFAULT NULL COMMENT '非首次随机单比例[0 - 100%]',
  `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 '逻辑删除 1已删除 0未删除',
  `version` int NOT NULL DEFAULT '1' COMMENT '数据版本',
  PRIMARY KEY (`id`) USING BTREE,
  KEY `key_tenant_id` (`tenant_id`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci ROW_FORMAT=DYNAMIC COMMENT='店员等级';
/*!40101 SET character_set_client = @saved_cs_client */;

--
-- Table structure for table `play_clerk_operation_log`
--

DROP TABLE IF EXISTS `play_clerk_operation_log`;
/*!40101 SET @saved_cs_client     = @@character_set_client */;
/*!50503 SET character_set_client = utf8mb4 */;
CREATE TABLE `play_clerk_operation_log` (
  `id` varchar(32) NOT NULL COMMENT 'UUID',
  `tenant_id` varchar(32) NOT NULL COMMENT '租户ID',
  `play_user_id` varchar(32) DEFAULT NULL COMMENT '店员用户ID',
  `play_user_name` tinyint DEFAULT NULL COMMENT '店员用户名称',
  `operation_type` varchar(2) DEFAULT NULL COMMENT '操作类型\r\n0:上线\r\n1:离线\r\n2:上架\r\n3:下架\r\n4:显示\r\n5:影藏\r\n6:开启随机单\r\n7:关闭随机单\r\n8:开启影藏排名\r\n9:关闭影藏排名\r\n10:入职\r\n11:离职\r\n12:获得头像框\r\n13:分成变动\r\n14:等级变动\r\n14:\r\n15:\r\n16:',
  `operation_by` varchar(32) DEFAULT NULL COMMENT '操作人ID',
  `remark` varchar(1000) DEFAULT NULL COMMENT '备注',
  `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 '逻辑删除 1已删除 0未删除',
  `version` int NOT NULL DEFAULT '1' COMMENT '数据版本',
  PRIMARY KEY (`id`) USING BTREE,
  KEY `key_tenant_id` (`tenant_id`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci ROW_FORMAT=DYNAMIC COMMENT='店员操作日志';
/*!40101 SET character_set_client = @saved_cs_client */;

--
-- Table structure for table `play_clerk_pk`
--

DROP TABLE IF EXISTS `play_clerk_pk`;
/*!40101 SET @saved_cs_client     = @@character_set_client */;
/*!50503 SET character_set_client = utf8mb4 */;
CREATE TABLE `play_clerk_pk` (
  `id` varchar(32) NOT NULL DEFAULT '0' 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` varchar(1) NOT NULL DEFAULT '0' COMMENT '逻辑删除 1已删除 0未删除',
  `version` int NOT NULL DEFAULT '1' COMMENT '数据版本',
  `clerk_a` varchar(255) DEFAULT NULL COMMENT '店员A',
  `clerk_b` varchar(255) DEFAULT NULL COMMENT '店员B',
  `radio` int DEFAULT NULL COMMENT '比例',
  `pk_begin_time` datetime DEFAULT NULL COMMENT '开始时间',
  `pk_end_time` datetime DEFAULT NULL COMMENT '结束时间',
  `punishment_time` datetime DEFAULT NULL COMMENT '惩罚公示时间',
  `status` varchar(255) DEFAULT NULL COMMENT '状态',
  PRIMARY KEY (`id`) USING BTREE,
  KEY `key_tenant_id` (`tenant_id`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci ROW_FORMAT=DYNAMIC COMMENT='店员pk';
/*!40101 SET character_set_client = @saved_cs_client */;

--
-- Table structure for table `play_clerk_ranking_info`
--

DROP TABLE IF EXISTS `play_clerk_ranking_info`;
/*!40101 SET @saved_cs_client     = @@character_set_client */;
/*!50503 SET character_set_client = utf8mb4 */;
CREATE TABLE `play_clerk_ranking_info` (
  `id` varchar(32) NOT NULL COMMENT 'UUID',
  `tenant_id` varchar(32) NOT NULL COMMENT '租户ID',
  `clerk_id` varchar(255) DEFAULT NULL COMMENT '店员ID',
  `historical_statistics` varchar(1) DEFAULT '0' COMMENT '是否为历史统计(1:是;0:不是)',
  `weekly_ranking` varchar(1) DEFAULT NULL COMMENT '日排名还是周排名（0:每日排名;1:每周排名）',
  `serial_number` int DEFAULT '0' COMMENT '统计序号',
  `ranking_index` int DEFAULT NULL COMMENT '排行序号',
  `start_count_date` date DEFAULT NULL COMMENT '开始统计时间',
  `end_count_date` date DEFAULT NULL COMMENT '结束统计时间',
  `order_number` varchar(255) DEFAULT '0' COMMENT '订单总数',
  `order_money` decimal(10,2) DEFAULT '0.00' COMMENT '订单金额',
  `order_continue_number` int DEFAULT '0' COMMENT '续单数',
  `order_continue_money` decimal(10,2) DEFAULT '0.00' COMMENT '续单金额',
  `previous_money` decimal(10,2) DEFAULT '0.00' COMMENT '距离前一名成绩',
  `orders_expired_number` int DEFAULT '0' COMMENT '超时未接单数',
  `custom_number` int DEFAULT NULL COMMENT '客户数量',
  `order_continue_proportion` decimal(10,2) DEFAULT NULL COMMENT '续单比例',
  `average_unit_price` decimal(10,2) DEFAULT NULL COMMENT '平均每单价格',
  `settlement_date` date DEFAULT NULL COMMENT '统计时间',
  `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 '逻辑删除 1已删除 0未删除',
  `version` int NOT NULL DEFAULT '1' COMMENT '数据版本',
  PRIMARY KEY (`id`) USING BTREE,
  KEY `key_tenant_id` (`tenant_id`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci ROW_FORMAT=DYNAMIC COMMENT='店员排行';
/*!40101 SET character_set_client = @saved_cs_client */;

--
-- Table structure for table `play_clerk_resource info`
--

DROP TABLE IF EXISTS `play_clerk_resource info`;
/*!40101 SET @saved_cs_client     = @@character_set_client */;
/*!50503 SET character_set_client = utf8mb4 */;
CREATE TABLE `play_clerk_resource info` (
  `id` varchar(32) NOT NULL COMMENT 'UUID',
  `tenant_id` varchar(32) NOT NULL COMMENT '租户ID',
  `play_user_id` varchar(32) DEFAULT NULL COMMENT '陪玩用户ID',
  `resource_type` tinyint(1) DEFAULT NULL COMMENT '资源分类\r\n0：图片\r\n1：录音\r\n2：视频',
  `resource_address` varchar(1000) DEFAULT NULL COMMENT '资源地址',
  `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 '逻辑删除 1已删除 0未删除',
  `version` int NOT NULL DEFAULT '1' COMMENT '数据版本',
  PRIMARY KEY (`id`) USING BTREE,
  KEY `key_tenant_id` (`tenant_id`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci ROW_FORMAT=DYNAMIC COMMENT='店员资源';
/*!40101 SET character_set_client = @saved_cs_client */;

--
-- Table structure for table `play_clerk_type_info`
--

DROP TABLE IF EXISTS `play_clerk_type_info`;
/*!40101 SET @saved_cs_client     = @@character_set_client */;
/*!50503 SET character_set_client = utf8mb4 */;
CREATE TABLE `play_clerk_type_info` (
  `id` varchar(32) NOT NULL COMMENT 'UUID',
  `tenant_id` varchar(32) NOT NULL COMMENT '租户ID',
  `type_name` varchar(255) DEFAULT NULL COMMENT '分类名称',
  `sort` int DEFAULT NULL COMMENT '排序',
  `home_displayed` varchar(1) DEFAULT NULL COMMENT '是否在首页显示(0:不显示;1显示)',
  `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 '逻辑删除 1已删除 0未删除',
  `version` int NOT NULL DEFAULT '1' COMMENT '数据版本',
  PRIMARY KEY (`id`) USING BTREE,
  KEY `key_tenant_id` (`tenant_id`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci ROW_FORMAT=DYNAMIC COMMENT='店员分类信息';
/*!40101 SET character_set_client = @saved_cs_client */;

--
-- Table structure for table `play_clerk_type_user_info`
--

DROP TABLE IF EXISTS `play_clerk_type_user_info`;
/*!40101 SET @saved_cs_client     = @@character_set_client */;
/*!50503 SET character_set_client = utf8mb4 */;
CREATE TABLE `play_clerk_type_user_info` (
  `id` varchar(32) NOT NULL COMMENT 'UUID',
  `tenant_id` varchar(32) NOT NULL COMMENT '租户ID',
  `type_id` varchar(32) DEFAULT NULL COMMENT '分组ID',
  `clerk_id` varchar(32) DEFAULT NULL COMMENT '店员ID',
  `sort` int DEFAULT NULL COMMENT '排序',
  `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 '逻辑删除 1已删除 0未删除',
  `version` int NOT NULL DEFAULT '1' COMMENT '数据版本',
  PRIMARY KEY (`id`) USING BTREE,
  KEY `key_tenant_id` (`tenant_id`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci ROW_FORMAT=DYNAMIC COMMENT='店员和分类关系';
/*!40101 SET character_set_client = @saved_cs_client */;

--
-- Table structure for table `play_clerk_user_info`
--

DROP TABLE IF EXISTS `play_clerk_user_info`;
/*!40101 SET @saved_cs_client     = @@character_set_client */;
/*!50503 SET character_set_client = utf8mb4 */;
CREATE TABLE `play_clerk_user_info` (
  `id` varchar(32) NOT NULL DEFAULT '0' COMMENT 'UUID',
  `tenant_id` varchar(32) NOT NULL COMMENT '租户ID',
  `sys_user_id` varchar(32) DEFAULT NULL COMMENT '陪玩用户ID',
  `phone` varchar(255) DEFAULT NULL COMMENT '电话号码',
  `wei_chat_code` varchar(255) DEFAULT NULL COMMENT '微信号码',
  `wei_chat_avatar` varchar(255) DEFAULT NULL,
  `openid` varchar(255) NOT NULL COMMENT '用户的标识，对当前公众号唯一',
  `nickname` varchar(255) DEFAULT NULL COMMENT '店员昵称',
  `type_id` varchar(32) DEFAULT NULL COMMENT '店员类别',
  `fixing_level` varchar(1) DEFAULT NULL COMMENT '是否固定等级(0:固定等级，1:不固定)',
  `group_id` varchar(32) DEFAULT NULL COMMENT '分组ID',
  `level_id` varchar(32) DEFAULT NULL COMMENT '店员等级',
  `sex` varchar(1) DEFAULT NULL COMMENT '店员性别（1：男：0：女）',
  `code` varchar(32) DEFAULT NULL COMMENT '身份证号',
  `album` text COMMENT '相册',
  `avatar` varchar(1000) DEFAULT NULL COMMENT '头像',
  `avatar_frame_id` varchar(32) DEFAULT NULL COMMENT '头像框',
  `audio` varchar(1000) DEFAULT NULL COMMENT '音频',
  `constellation` varchar(255) DEFAULT NULL COMMENT '星座',
  `label` text COMMENT '标签',
  `signature` text COMMENT '个性签名',
  `age` int DEFAULT NULL COMMENT '年龄',
  `country` varchar(64) DEFAULT NULL COMMENT '所在国家',
  `province` varchar(64) DEFAULT NULL COMMENT '所在省份',
  `city` varchar(64) DEFAULT NULL COMMENT '所在城市',
  `account_balance` decimal(10,2) DEFAULT '0.00' COMMENT '账户余额（单位分）',
  `remark` text COMMENT '备注',
  `onboarding_state` varchar(1) DEFAULT '1' COMMENT '在职状态（1：在职，1：离职）',
  `entry_time` datetime DEFAULT NULL COMMENT '入职时间',
  `resignation_time` datetime DEFAULT NULL COMMENT '离职时间',
  `working_hours` int DEFAULT NULL COMMENT '工作天数',
  `recommendation_state` varchar(1) DEFAULT '0' COMMENT '是否推荐状态（1：已推荐，0：未推荐）',
  `pin_to_top_state` varchar(1) DEFAULT '0' COMMENT '是否置顶状态（1：已置顶，0：未置顶）',
  `online_state` varchar(1) DEFAULT '0' COMMENT '在线状态【1：在线，0：离线】',
  `listing_state` varchar(1) DEFAULT '1' COMMENT '上架状态【1：上架，0：下架】',
  `display_state` varchar(1) DEFAULT '1' COMMENT '显示状态【1：显示，0：隐藏】',
  `real_state` varchar(1) DEFAULT '0' COMMENT '实名状态【1：已实名，0：未实名】',
  `mandatory_real_state` varchar(1) DEFAULT '0' COMMENT '是否必须实名【1：必须实名，0：非必须实名】',
  `random_order_state` varchar(1) DEFAULT '1' COMMENT '随机接单状态【1：允许，0：禁止】',
  `registration_time` datetime DEFAULT NULL COMMENT '注册时间',
  `last_login_time` datetime DEFAULT NULL COMMENT '上次登录时间',
  `clerk_state` varchar(1) DEFAULT '0' COMMENT '店员状态（0:不是陪玩，1:陪玩）',
  `token` text,
  `we_chat_code_image` varchar(1000) DEFAULT NULL COMMENT '微信二维码',
  `we_chat_pay_image` varchar(1000) DEFAULT NULL COMMENT '微信收款码图片',
  `alipay_image` varchar(1000) DEFAULT NULL COMMENT '支付宝收款码图片',
  `add_time` datetime DEFAULT NULL COMMENT '创建时间',
  `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` varchar(1) NOT NULL DEFAULT '0' COMMENT '逻辑删除 1已删除 0未删除',
  `version` int NOT NULL DEFAULT '1' COMMENT '数据版本',
  PRIMARY KEY (`id`) USING BTREE,
  KEY `key_tenant_id` (`tenant_id`) USING BTREE,
  KEY `key_openid` (`openid`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci ROW_FORMAT=DYNAMIC COMMENT='店员用户';
/*!40101 SET character_set_client = @saved_cs_client */;

--
-- Table structure for table `play_clerk_user_review_info`
--

DROP TABLE IF EXISTS `play_clerk_user_review_info`;
/*!40101 SET @saved_cs_client     = @@character_set_client */;
/*!50503 SET character_set_client = utf8mb4 */;
CREATE TABLE `play_clerk_user_review_info` (
  `id` varchar(32) NOT NULL COMMENT 'UUID',
  `tenant_id` varchar(32) NOT NULL COMMENT '租户ID',
  `clerk_id` varchar(32) DEFAULT NULL COMMENT '店员ID',
  `wei_chat_code` varchar(255) DEFAULT NULL COMMENT '微信号码',
  `phone` varchar(255) DEFAULT NULL COMMENT '手机号码',
  `nickname` varchar(255) DEFAULT NULL COMMENT '昵称',
  `sex` varchar(1) DEFAULT NULL COMMENT '性别[0:未知;1:男;2:女]',
  `age` int DEFAULT NULL COMMENT '年龄',
  `province` varchar(255) DEFAULT NULL COMMENT '所在省份',
  `city` varchar(255) DEFAULT NULL COMMENT '所在城市',
  `remark` text COMMENT '备注（相关经验）',
  `audio` varchar(255) DEFAULT NULL COMMENT '音频',
  `album` text COMMENT '照片',
  `add_time` datetime(1) DEFAULT NULL COMMENT '申请时间',
  `review_state` varchar(1) DEFAULT NULL COMMENT ' 审核状态（0：未审核:1：审核通过，2：审核不通过）',
  `review_time` datetime(1) DEFAULT NULL COMMENT '审核时间',
  `review_by` varchar(32) DEFAULT NULL COMMENT '审核人',
  `review_con` text COMMENT '审核内容',
  `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 '逻辑删除 1已删除 0未删除',
  `version` int NOT NULL DEFAULT '1' COMMENT '数据版本',
  PRIMARY KEY (`id`) USING BTREE,
  KEY `key_tenant_id` (`tenant_id`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci ROW_FORMAT=DYNAMIC COMMENT='店员资料审核';
/*!40101 SET character_set_client = @saved_cs_client */;

--
-- Table structure for table `play_clerk_wages_details_info`
--

DROP TABLE IF EXISTS `play_clerk_wages_details_info`;
/*!40101 SET @saved_cs_client     = @@character_set_client */;
/*!50503 SET character_set_client = utf8mb4 */;
CREATE TABLE `play_clerk_wages_details_info` (
  `id` varchar(32) NOT NULL COMMENT 'UUID',
  `tenant_id` varchar(32) NOT NULL COMMENT '租户ID',
  `wages_id` varchar(32) DEFAULT '0' COMMENT '工资统计ID',
  `clerk_id` varchar(255) DEFAULT NULL COMMENT '店员ID',
  `order_id` varchar(32) DEFAULT NULL COMMENT '订单ID',
  `order_no` varchar(32) DEFAULT NULL COMMENT '订单编号',
  `final_amount` decimal(10,2) DEFAULT NULL COMMENT '订单最终金额（支付金额）',
  `estimated_revenue` decimal(10,2) DEFAULT NULL COMMENT '工资金额',
  `end_order_time` datetime DEFAULT NULL COMMENT '订单完成时间',
  `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 '逻辑删除 1已删除 0未删除',
  `version` int NOT NULL DEFAULT '1' COMMENT '数据版本',
  PRIMARY KEY (`id`) USING BTREE,
  KEY `key_tenant_id` (`tenant_id`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci ROW_FORMAT=DYNAMIC COMMENT='店员工资明细信息';
/*!40101 SET character_set_client = @saved_cs_client */;

--
-- Table structure for table `play_clerk_wages_info`
--

DROP TABLE IF EXISTS `play_clerk_wages_info`;
/*!40101 SET @saved_cs_client     = @@character_set_client */;
/*!50503 SET character_set_client = utf8mb4 */;
CREATE TABLE `play_clerk_wages_info` (
  `id` varchar(32) NOT NULL COMMENT 'UUID',
  `tenant_id` varchar(32) NOT NULL COMMENT '租户ID',
  `clerk_id` varchar(255) DEFAULT NULL COMMENT '店员ID',
  `historical_statistics` varchar(1) DEFAULT '0' COMMENT '是否为历史统计(1:是;0:不是)',
  `serial_number` int DEFAULT '0' COMMENT '统计序号',
  `ranking_index` int DEFAULT NULL COMMENT '排行序号',
  `start_count_date` date DEFAULT NULL COMMENT '开始统计时间',
  `end_count_date` date DEFAULT NULL COMMENT '结束统计时间',
  `settlement_date` date DEFAULT NULL COMMENT '统计时间',
  `order_number` int DEFAULT '0' COMMENT '订单总数',
  `final_amount` decimal(10,2) DEFAULT '0.00' COMMENT '订单金额',
  `order_continue_number` int DEFAULT '0' COMMENT '续单数',
  `order_continue_proportion` float DEFAULT NULL COMMENT '续单比例',
  `order_continue_money` decimal(10,2) DEFAULT '0.00' COMMENT '续单金额',
  `orders_expired_number` int DEFAULT '0' COMMENT '超时未接单数',
  `estimated_revenue` decimal(10,2) DEFAULT NULL COMMENT '店员收入',
  `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 '逻辑删除 1已删除 0未删除',
  `version` int NOT NULL DEFAULT '1' COMMENT '数据版本',
  PRIMARY KEY (`id`) USING BTREE,
  KEY `key_tenant_id` (`tenant_id`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci ROW_FORMAT=DYNAMIC COMMENT='店员工资结算信息';
/*!40101 SET character_set_client = @saved_cs_client */;

--
-- Table structure for table `play_clerk_waiter_info`
--

DROP TABLE IF EXISTS `play_clerk_waiter_info`;
/*!40101 SET @saved_cs_client     = @@character_set_client */;
/*!50503 SET character_set_client = utf8mb4 */;
CREATE TABLE `play_clerk_waiter_info` (
  `id` varchar(32) NOT NULL COMMENT 'UUID',
  `tenant_id` varchar(32) NOT NULL COMMENT '租户ID',
  `sys_user_id` varchar(255) DEFAULT NULL COMMENT '账号ID',
  `waiter_name` varchar(255) DEFAULT NULL COMMENT '客服名称',
  `sort` int DEFAULT NULL COMMENT '排序',
  `add_time` datetime DEFAULT NULL COMMENT '添加时间',
  `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 '逻辑删除 1已删除 0未删除',
  `version` int NOT NULL DEFAULT '1' COMMENT '数据版本',
  PRIMARY KEY (`id`) USING BTREE,
  KEY `key_tenant_id` (`tenant_id`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci ROW_FORMAT=DYNAMIC COMMENT='客服信息';
/*!40101 SET character_set_client = @saved_cs_client */;

--
-- Table structure for table `play_commodity_and_level_info`
--

DROP TABLE IF EXISTS `play_commodity_and_level_info`;
/*!40101 SET @saved_cs_client     = @@character_set_client */;
/*!50503 SET character_set_client = utf8mb4 */;
CREATE TABLE `play_commodity_and_level_info` (
  `id` varchar(32) NOT NULL COMMENT 'UUID',
  `tenant_id` varchar(32) NOT NULL COMMENT '租户ID',
  `commodity_id` varchar(32) DEFAULT NULL COMMENT '商品ID',
  `level_id` varchar(255) DEFAULT NULL COMMENT '店员等级ID',
  `price` decimal(10,2) DEFAULT NULL COMMENT '服务单价',
  `sort` int DEFAULT NULL COMMENT '排序',
  `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 '逻辑删除 1已删除 0未删除',
  `version` int NOT NULL DEFAULT '1' COMMENT '数据版本',
  PRIMARY KEY (`id`) USING BTREE,
  KEY `key_tenant_id` (`tenant_id`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci ROW_FORMAT=DYNAMIC COMMENT='服务项目和店员等级数据';
/*!40101 SET character_set_client = @saved_cs_client */;

--
-- Table structure for table `play_commodity_info`
--

DROP TABLE IF EXISTS `play_commodity_info`;
/*!40101 SET @saved_cs_client     = @@character_set_client */;
/*!50503 SET character_set_client = utf8mb4 */;
CREATE TABLE `play_commodity_info` (
  `id` varchar(32) NOT NULL COMMENT 'UUID',
  `p_id` varchar(32) DEFAULT NULL COMMENT 'pid',
  `tenant_id` varchar(32) NOT NULL COMMENT '租户ID',
  `item_type` varchar(255) DEFAULT NULL COMMENT '项目类型',
  `item_name` varchar(255) DEFAULT NULL COMMENT '项目名称',
  `service_duration` varchar(255) DEFAULT NULL COMMENT '服务时长（文字描述信息,不参与订单计算）',
  `automatic_settlement_duration` int DEFAULT '-1' COMMENT '接单后自动结算等待时长（单位，秒,-1:不自动结算）',
  `price` decimal(10,2) DEFAULT NULL COMMENT '服务单价',
  `sort` int DEFAULT NULL COMMENT '排序',
  `enable_stace` varchar(1) DEFAULT '1' COMMENT '启用状态(0:停用,1:启用)',
  `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 '逻辑删除 1已删除 0未删除',
  `version` int NOT NULL DEFAULT '1' COMMENT '数据版本',
  PRIMARY KEY (`id`) USING BTREE,
  KEY `key_tenant_id` (`tenant_id`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci ROW_FORMAT=DYNAMIC COMMENT='服务项目和价格';
/*!40101 SET character_set_client = @saved_cs_client */;

--
-- Table structure for table `play_coupon_details`
--

DROP TABLE IF EXISTS `play_coupon_details`;
/*!40101 SET @saved_cs_client     = @@character_set_client */;
/*!50503 SET character_set_client = utf8mb4 */;
CREATE TABLE `play_coupon_details` (
  `id` varchar(32) NOT NULL COMMENT 'UUID',
  `tenant_id` varchar(32) NOT NULL COMMENT '租户ID',
  `custom_id` varchar(255) DEFAULT NULL COMMENT '顾客ID',
  `custom_nickname` varchar(255) DEFAULT NULL COMMENT '顾客昵称',
  `custom_level_id` varchar(255) DEFAULT NULL COMMENT '顾客等级ID',
  `coupon_id` varchar(255) DEFAULT NULL COMMENT '优惠券名称',
  `obtaining_channels` varchar(1) DEFAULT NULL COMMENT '获得渠道（1:自主领取;2:后台发放;3:退款返还;4:分享领取;5:抽奖获得）',
  `use_state` varchar(1) DEFAULT NULL COMMENT '使用状态（1:未使用;2:已使用;3:已回收）',
  `discount_type` varchar(1) DEFAULT NULL COMMENT '优惠类型（0:满减;1:折扣）',
  `discount_content` varchar(255) DEFAULT NULL COMMENT '优惠内容，文字展示',
  `discount_amount` decimal(10,2) DEFAULT NULL COMMENT '折扣值',
  `validity_period_type` varchar(1) DEFAULT NULL COMMENT '有效期类型（0:永久有效;1:临时有效）',
  `expiration_time` datetime DEFAULT NULL COMMENT '到期时间',
  `obtaining_time` datetime DEFAULT NULL COMMENT '获得时间',
  `use_time` datetime DEFAULT NULL COMMENT '使用时间',
  `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 '逻辑删除 1已删除 0未删除',
  `version` int NOT NULL DEFAULT '1' COMMENT '数据版本',
  PRIMARY KEY (`id`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci ROW_FORMAT=DYNAMIC COMMENT='优惠券详情';
/*!40101 SET character_set_client = @saved_cs_client */;

--
-- Table structure for table `play_coupon_info`
--

DROP TABLE IF EXISTS `play_coupon_info`;
/*!40101 SET @saved_cs_client     = @@character_set_client */;
/*!50503 SET character_set_client = utf8mb4 */;
CREATE TABLE `play_coupon_info` (
  `id` varchar(32) NOT NULL COMMENT 'UUID',
  `tenant_id` varchar(32) NOT NULL COMMENT '租户ID',
  `coupon_name` varchar(255) DEFAULT NULL COMMENT '优惠券名称',
  `validity_period_type` varchar(1) DEFAULT NULL COMMENT '有效期类型（0:永久有效;1:固定时间内有效;2:领取后几天内有效）',
  `productive_time` datetime DEFAULT NULL COMMENT '生效时间',
  `expiration_time` datetime DEFAULT NULL COMMENT '到期时间',
  `effective_day` int DEFAULT NULL COMMENT '生效时长,validityPeriodType=2时有效',
  `use_min_amount` decimal(10,2) DEFAULT NULL COMMENT '使用优惠券最近消费金额',
  `discount_type` varchar(1) DEFAULT NULL COMMENT '优惠类型（0:满减;1:折扣）',
  `discount_content` varchar(255) DEFAULT NULL COMMENT '优惠内容，文字展示',
  `discount_amount` decimal(10,2) DEFAULT NULL COMMENT '折扣值',
  `attribution_discounts` varchar(1) DEFAULT '1' COMMENT '优惠金额是否由店铺承担（0:不是;1:是）',
  `place_type` varchar(255) DEFAULT NULL COMMENT '下单类型（0：指定单，1：随机单。2：打赏单）',
  `clerk_type` varchar(1) DEFAULT '0' COMMENT '店员范围（0:所有店员）',
  `coupon_quantity` int DEFAULT '0' COMMENT '优惠券总数（-1表示不限制数量）',
  `issued_quantity` int DEFAULT '0' COMMENT '已发放数量',
  `remaining_quantity` int DEFAULT '0' COMMENT '剩余数量',
  `clerk_obtained_max_quantity` int DEFAULT '1' COMMENT '店员获得最大数量',
  `claim_condition_type` varchar(1) DEFAULT NULL COMMENT '领取条件类型（0:所有人可领取,1:指定条件领取）',
  `custom_level_check_type` varchar(1) DEFAULT NULL COMMENT '顾客等级选择状态(0:未选择,1:选择)',
  `custom_level` varchar(255) DEFAULT NULL COMMENT '顾客等级',
  `custom_follow_status_check_type` varchar(1) DEFAULT NULL COMMENT '用户关注公众号选择状态(0:未选择,1:选择)',
  `custom_follow_status` varchar(2) DEFAULT '-1' COMMENT '顾客关注公众号状态(0:未关注;1:已关注)',
  `custom_sex_check_type` varchar(1) DEFAULT NULL COMMENT ' 顾客性别选择状态(0:未选择,1:选择)',
  `custom_sex` varchar(255) DEFAULT NULL COMMENT '顾客性别（0:未知;1:男,2:女）',
  `new_user` varchar(1) DEFAULT NULL COMMENT '仅限新用户领取（0:不是;1:是）(没有下过单的用户，下单后再退单也不算)',
  `coupon_on_line_state` varchar(1) DEFAULT NULL COMMENT '优惠券上架状态（0;下架;1:上架）',
  `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 '逻辑删除 1已删除 0未删除',
  `version` int NOT NULL DEFAULT '1' COMMENT '数据版本',
  PRIMARY KEY (`id`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci ROW_FORMAT=DYNAMIC COMMENT='优惠券信息';
/*!40101 SET character_set_client = @saved_cs_client */;

--
-- Table structure for table `play_custom_amount details`
--

DROP TABLE IF EXISTS `play_custom_amount details`;
/*!40101 SET @saved_cs_client     = @@character_set_client */;
/*!50503 SET character_set_client = utf8mb4 */;
CREATE TABLE `play_custom_amount details` (
  `id` varchar(32) NOT NULL COMMENT 'UUID',
  `tenant_id` varchar(32) NOT NULL COMMENT '租户ID',
  `coston_id` varchar(32) DEFAULT NULL COMMENT '客户ID',
  `operate` varchar(1) DEFAULT NULL COMMENT '操作【0;1:2】\r\n0：充值\r\n1：消费\r\n2；退款',
  `trade_method` varchar(1) DEFAULT NULL COMMENT '交易方式\r\n0：微信支付\r\n1：其他支付',
  `
initial_amount` varchar(255) DEFAULT NULL COMMENT '交易前账户金额（单位分）',
  `trade_amount` varchar(255) DEFAULT NULL COMMENT '交易金额（单位分）',
  `account_balance` varchar(255) DEFAULT NULL COMMENT '交易后账户余额（单位分）',
  `order_id` varchar(255) DEFAULT NULL COMMENT '订单ID',
  `remark` varchar(1000) DEFAULT NULL COMMENT '备注',
  `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 '逻辑删除 1已删除 0未删除',
  `version` int NOT NULL DEFAULT '1' COMMENT '数据版本',
  PRIMARY KEY (`id`) USING BTREE,
  KEY `key_tenant_id` (`tenant_id`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci ROW_FORMAT=DYNAMIC COMMENT='顾客金额详细';
/*!40101 SET character_set_client = @saved_cs_client */;

--
-- Table structure for table `play_custom_article_info`
--

DROP TABLE IF EXISTS `play_custom_article_info`;
/*!40101 SET @saved_cs_client     = @@character_set_client */;
/*!50503 SET character_set_client = utf8mb4 */;
CREATE TABLE `play_custom_article_info` (
  `id` varchar(32) NOT NULL DEFAULT '0' COMMENT 'UUID',
  `tenant_id` varchar(32) NOT NULL COMMENT '租户ID',
  `article_id` varchar(32) DEFAULT NULL COMMENT '动态ID',
  `clerk_id` varchar(32) DEFAULT NULL COMMENT '店员ID',
  `custom_id` varchar(32) DEFAULT NULL COMMENT '顾客ID',
  `endorse_state` varchar(1) DEFAULT NULL COMMENT '赞同状态(1:赞同,0:未赞同)',
  `endorse_type` varchar(1) DEFAULT NULL COMMENT '赞同类型(1:点赞;0:收藏)',
  `endorse_time` datetime NOT NULL COMMENT '赞同时间',
  `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` varchar(1) NOT NULL DEFAULT '0' COMMENT '逻辑删除 1已删除 0未删除',
  `version` int NOT NULL DEFAULT '1' COMMENT '数据版本',
  PRIMARY KEY (`id`) USING BTREE,
  KEY `key_tenant_id` (`tenant_id`) USING BTREE,
  KEY `key_openid` (`endorse_time`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci ROW_FORMAT=DYNAMIC COMMENT='陪玩点赞动态信息';
/*!40101 SET character_set_client = @saved_cs_client */;

--
-- Table structure for table `play_custom_follow_info`
--

DROP TABLE IF EXISTS `play_custom_follow_info`;
/*!40101 SET @saved_cs_client     = @@character_set_client */;
/*!50503 SET character_set_client = utf8mb4 */;
CREATE TABLE `play_custom_follow_info` (
  `id` varchar(32) NOT NULL COMMENT 'UUID',
  `tenant_id` varchar(32) NOT NULL COMMENT '租户ID',
  `custom_id` varchar(32) DEFAULT NULL COMMENT '顾客ID',
  `clerk_id` varchar(32) DEFAULT NULL COMMENT '陪玩ID',
  `follow_time` datetime DEFAULT NULL COMMENT '关注时间',
  `unfollow_time` datetime(1) DEFAULT NULL COMMENT '取消关注时间',
  `follow_state` varchar(1) DEFAULT NULL COMMENT '关注状态[0:未关注，1:已关注]',
  `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 '逻辑删除 1已删除 0未删除',
  `version` int NOT NULL DEFAULT '1' COMMENT '数据版本',
  PRIMARY KEY (`id`) USING BTREE,
  KEY `key_tenant_id` (`tenant_id`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci ROW_FORMAT=DYNAMIC COMMENT='顾客关注陪玩信息';
/*!40101 SET character_set_client = @saved_cs_client */;

--
-- Table structure for table `play_custom_gift_info`
--

DROP TABLE IF EXISTS `play_custom_gift_info`;
/*!40101 SET @saved_cs_client     = @@character_set_client */;
/*!50503 SET character_set_client = utf8mb4 */;
CREATE TABLE `play_custom_gift_info` (
  `id` varchar(32) NOT NULL COMMENT 'UUID',
  `tenant_id` varchar(32) NOT NULL COMMENT '租户ID',
  `custom_id` varchar(32) DEFAULT NULL COMMENT '顾客ID',
  `giff_id` varchar(32) DEFAULT NULL COMMENT '礼物ID',
  `giff_number` int DEFAULT NULL COMMENT '礼物数量',
  `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 '逻辑删除 1已删除 0未删除',
  `version` int NOT NULL DEFAULT '1' COMMENT '数据版本',
  PRIMARY KEY (`id`) USING BTREE,
  KEY `key_tenant_id` (`tenant_id`) USING BTREE,
  KEY `key_openid` (`custom_id`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci ROW_FORMAT=DYNAMIC COMMENT='顾客和礼物关系';
/*!40101 SET character_set_client = @saved_cs_client */;

--
-- Table structure for table `play_custom_leave_msg`
--

DROP TABLE IF EXISTS `play_custom_leave_msg`;
/*!40101 SET @saved_cs_client     = @@character_set_client */;
/*!50503 SET character_set_client = utf8mb4 */;
CREATE TABLE `play_custom_leave_msg` (
  `id` varchar(32) NOT NULL COMMENT 'UUID',
  `tenant_id` varchar(32) NOT NULL COMMENT '租户ID',
  `custom_id` varchar(32) DEFAULT NULL COMMENT '顾客ID',
  `content` longtext COMMENT '留言内容',
  `images` text COMMENT '图片',
  `msg_time` datetime DEFAULT NULL COMMENT '留言时间',
  `remark` longtext COMMENT '备注',
  `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 '逻辑删除 1已删除 0未删除',
  `version` int NOT NULL DEFAULT '1' COMMENT '数据版本',
  PRIMARY KEY (`id`) USING BTREE,
  KEY `key_tenant_id` (`tenant_id`) USING BTREE,
  KEY `key_openid` (`custom_id`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci ROW_FORMAT=DYNAMIC COMMENT='顾客留言';
/*!40101 SET character_set_client = @saved_cs_client */;

--
-- Table structure for table `play_custom_level_info`
--

DROP TABLE IF EXISTS `play_custom_level_info`;
/*!40101 SET @saved_cs_client     = @@character_set_client */;
/*!50503 SET character_set_client = utf8mb4 */;
CREATE TABLE `play_custom_level_info` (
  `id` varchar(32) NOT NULL COMMENT 'UUID',
  `tenant_id` varchar(32) NOT NULL COMMENT '租户ID',
  `name` varchar(255) DEFAULT NULL COMMENT '等级名称',
  `level` tinyint(1) DEFAULT NULL COMMENT '等级数字（排序字段）',
  `consumption_amount` varchar(255) DEFAULT NULL COMMENT '上一级消费金额',
  `discount` tinyint(1) DEFAULT NULL COMMENT '满减比例',
  `avatar_frame_address` varchar(1000) DEFAULT NULL COMMENT '头像框地址',
  `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 '逻辑删除 1已删除 0未删除',
  `version` int NOT NULL DEFAULT '1' COMMENT '数据版本',
  PRIMARY KEY (`id`) USING BTREE,
  KEY `key_tenant_id` (`tenant_id`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci ROW_FORMAT=DYNAMIC COMMENT='顾客等级';
/*!40101 SET character_set_client = @saved_cs_client */;

--
-- Table structure for table `play_custom_user_info`
--

DROP TABLE IF EXISTS `play_custom_user_info`;
/*!40101 SET @saved_cs_client     = @@character_set_client */;
/*!50503 SET character_set_client = utf8mb4 */;
CREATE TABLE `play_custom_user_info` (
  `id` varchar(32) NOT NULL COMMENT 'UUID',
  `tenant_id` varchar(32) NOT NULL COMMENT '租户ID',
  `openid` varchar(255) DEFAULT NULL COMMENT '用户的标识，对当前公众号唯一',
  `unionid` varchar(255) DEFAULT NULL COMMENT ' 用户的标识，对当前公众号唯一',
  `nickname` varchar(255) DEFAULT NULL COMMENT '顾客昵称',
  `sex` tinyint(1) DEFAULT NULL COMMENT '顾客性别（0:位置;1:男,2:女）',
  `avatar` varchar(1000) DEFAULT NULL COMMENT '头像',
  `phone` varchar(255) DEFAULT NULL COMMENT '电话号码',
  `wei_chat_code` varchar(255) DEFAULT NULL COMMENT '微信号码',
  `level_id` varchar(32) DEFAULT NULL COMMENT '等级',
  `country` varchar(64) DEFAULT NULL COMMENT '所在国家',
  `province` varchar(64) DEFAULT NULL COMMENT '所在省份',
  `city` varchar(64) DEFAULT NULL COMMENT '所在城市',
  `accumulated_recharge_amount` decimal(10,2) DEFAULT '0.00' COMMENT '累计充值金额',
  `accumulated_consumption_amount` decimal(10,2) DEFAULT NULL COMMENT '累计消费金额',
  `account_balance` decimal(10,2) DEFAULT '0.00' COMMENT '账户余额（单位分）',
  `account_state` varchar(1) DEFAULT '0' COMMENT '余额状态[0:不存在余额，1:存在余额]',
  `subscribe_state` varchar(1) DEFAULT '0' COMMENT '关注状态[0:未关注，1:已关注]',
  `blacklist_state` varchar(1) DEFAULT '0' COMMENT '黑名单状态[0:非黑名单，1:黑名单]',
  `violation_state` varchar(1) DEFAULT '0' COMMENT '违规状态[0:未违规,1:违规]',
  `purchase_state` varchar(1) DEFAULT '0' COMMENT '是否下单状态[0:未未下单，1:下单过]',
  `mobile_phone_state` varchar(1) DEFAULT '0' COMMENT '绑定手机状态[0:未绑定,1:绑定]',
  `real_state` varchar(1) DEFAULT '0' COMMENT '实名状态【1：已实名，0：未实名】',
  `mandatory_real_state` varchar(1) DEFAULT '0' COMMENT '是否必须实名【2:跟随店铺设置，1：必须实名，0：非必须实名】',
  `registration_time` datetime DEFAULT NULL COMMENT '注册时间',
  `last_login_time` datetime DEFAULT NULL COMMENT '上次登录时间',
  `first_purchase_time` datetime DEFAULT NULL COMMENT '首次下单时间',
  `last_purchase_time` datetime DEFAULT NULL COMMENT '最后一次下单时间',
  `hide_level_state` varchar(1) DEFAULT '0' COMMENT '隐藏等级[0:不隐藏;1:隐藏]',
  `hide_ranking_state` varchar(1) DEFAULT '0' COMMENT '隐藏排名[0:不隐藏;1:隐藏]',
  `remark` text COMMENT '备注',
  `token` text,
  `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 '逻辑删除 1已删除 0未删除',
  `version` int NOT NULL DEFAULT '1' COMMENT '数据版本',
  PRIMARY KEY (`id`) USING BTREE,
  KEY `key_tenant_id` (`tenant_id`) USING BTREE,
  KEY `key_openid` (`openid`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci ROW_FORMAT=DYNAMIC COMMENT='顾客';
/*!40101 SET character_set_client = @saved_cs_client */;

--
-- Table structure for table `play_gift_info`
--

DROP TABLE IF EXISTS `play_gift_info`;
/*!40101 SET @saved_cs_client     = @@character_set_client */;
/*!50503 SET character_set_client = utf8mb4 */;
CREATE TABLE `play_gift_info` (
  `id` varchar(32) NOT NULL COMMENT 'UUID',
  `tenant_id` varchar(32) NOT NULL COMMENT '租户ID',
  `history` varchar(1) DEFAULT '0' COMMENT '是否是历史礼物(0:不是,1:是)',
  `name` varchar(255) DEFAULT NULL COMMENT '礼物名称',
  `type` varchar(255) DEFAULT NULL COMMENT '礼物类型（0：盲盒,1:普通礼物）',
  `url` varchar(1000) DEFAULT NULL COMMENT '礼物图片地址',
  `price` decimal(10,2) DEFAULT '0.00' COMMENT '单价',
  `unit` varchar(255) DEFAULT NULL COMMENT '价格单位',
  `state` varchar(2) DEFAULT '0' COMMENT '状态（0:正常,1:下架）',
  `listing_time` datetime DEFAULT NULL COMMENT '上架时间',
  `delinting_time` datetime DEFAULT NULL COMMENT '下架时间',
  `remark` text COMMENT '备注',
  `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 '逻辑删除 1已删除 0未删除',
  `version` int NOT NULL DEFAULT '1' COMMENT '数据版本',
  PRIMARY KEY (`id`) USING BTREE,
  KEY `key_tenant_id` (`tenant_id`) USING BTREE,
  KEY `key_openid` (`name`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci ROW_FORMAT=DYNAMIC COMMENT='礼物';
/*!40101 SET character_set_client = @saved_cs_client */;

--
-- Table structure for table `play_notice_info`
--

DROP TABLE IF EXISTS `play_notice_info`;
/*!40101 SET @saved_cs_client     = @@character_set_client */;
/*!50503 SET character_set_client = utf8mb4 */;
CREATE TABLE `play_notice_info` (
  `id` varchar(32) NOT NULL COMMENT 'UUID',
  `tenant_id` varchar(32) NOT NULL COMMENT '租户ID',
  `notice_type` varchar(1) DEFAULT NULL COMMENT '公告类型【0:公告,1:消息】',
  `title` text COMMENT '标题',
  `content` text COMMENT '内容',
  `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 '逻辑删除 1已删除 0未删除',
  `version` int NOT NULL DEFAULT '1' COMMENT '数据版本',
  PRIMARY KEY (`id`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci ROW_FORMAT=DYNAMIC COMMENT='公告';
/*!40101 SET character_set_client = @saved_cs_client */;

--
-- Table structure for table `play_order_complaint_info`
--

DROP TABLE IF EXISTS `play_order_complaint_info`;
/*!40101 SET @saved_cs_client     = @@character_set_client */;
/*!50503 SET character_set_client = utf8mb4 */;
CREATE TABLE `play_order_complaint_info` (
  `id` varchar(255) NOT NULL COMMENT 'uuid',
  `tenant_id` varchar(32) DEFAULT NULL COMMENT '租户ID',
  `order_id` varchar(32) DEFAULT NULL COMMENT '订单ID',
  `custom_id` varchar(32) DEFAULT NULL COMMENT '顾客ID',
  `clerk_id` varchar(32) DEFAULT NULL COMMENT '店员ID',
  `commodity_id` varchar(32) DEFAULT NULL COMMENT '商品ID',
  `wx_chat_code` varchar(255) DEFAULT NULL COMMENT '投诉人微信号码',
  `complaint_con` text COMMENT '投诉内容',
  `complaint_time` datetime DEFAULT NULL COMMENT '投诉时间',
  `images` text COMMENT '图片列表',
  `hidden` varchar(1) DEFAULT '0' COMMENT '数据是否隐藏（0:未隐藏，1:隐藏）',
  `updated_time` datetime DEFAULT NULL COMMENT '修改时间',
  `updated_by` varchar(32) DEFAULT NULL COMMENT '修改人的id',
  `created_time` datetime DEFAULT NULL COMMENT '创建时间',
  `created_by` varchar(32) DEFAULT NULL COMMENT '创建人的id',
  `deleted` tinyint(1) NOT NULL DEFAULT '0' COMMENT '逻辑删除 1已删除 0未删除',
  `version` int NOT NULL DEFAULT '1' COMMENT '数据版本',
  PRIMARY KEY (`id`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci ROW_FORMAT=DYNAMIC COMMENT='订单投诉信息';
/*!40101 SET character_set_client = @saved_cs_client */;

--
-- Table structure for table `play_order_continue_info`
--

DROP TABLE IF EXISTS `play_order_continue_info`;
/*!40101 SET @saved_cs_client     = @@character_set_client */;
/*!50503 SET character_set_client = utf8mb4 */;
CREATE TABLE `play_order_continue_info` (
  `id` varchar(255) NOT NULL COMMENT 'uuid',
  `tenant_id` varchar(32) DEFAULT NULL COMMENT '租户ID',
  `order_id` varchar(32) DEFAULT NULL COMMENT '订单ID',
  `order_no` varchar(32) DEFAULT NULL COMMENT '订单号码',
  `custom_id` varchar(32) DEFAULT NULL COMMENT '顾客ID',
  `clerk_id` varchar(32) DEFAULT NULL COMMENT '店员ID',
  `place_type` varchar(1) DEFAULT '0' COMMENT '下单类型（0：指定单，1：随机单。2：打赏单）',
  `order_money` decimal(10,2) DEFAULT NULL COMMENT '下单金额',
  `final_amount` decimal(10,2) DEFAULT NULL COMMENT '支付金额',
  `continue_msg` text COMMENT '申请消息',
  `images` text COMMENT '截图',
  `reviewed_required` varchar(1) DEFAULT NULL COMMENT '是否需要审核，0:不需要，1:需要',
  `reviewed_state` varchar(1) DEFAULT NULL COMMENT '审核状态（0:未审核，1:已通过，2:不通过）',
  `reviewed_by` varchar(32) DEFAULT NULL COMMENT '审核人',
  `reviewed_time` datetime DEFAULT NULL COMMENT '审核时间',
  `reviewed_remark` varchar(255) DEFAULT NULL COMMENT '审核内容',
  `continue_time` datetime DEFAULT NULL COMMENT '申请时间',
  `updated_time` datetime DEFAULT NULL COMMENT '修改时间',
  `updated_by` varchar(32) DEFAULT NULL COMMENT '修改人的id',
  `created_time` datetime DEFAULT NULL COMMENT '创建时间',
  `created_by` varchar(32) DEFAULT NULL COMMENT '创建人的id',
  `deleted` tinyint(1) NOT NULL DEFAULT '0' COMMENT '逻辑删除 1已删除 0未删除',
  `version` int NOT NULL DEFAULT '1' COMMENT '数据版本',
  PRIMARY KEY (`id`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci ROW_FORMAT=DYNAMIC COMMENT='订单续单信息';
/*!40101 SET character_set_client = @saved_cs_client */;

--
-- Table structure for table `play_order_demand_info`
--

DROP TABLE IF EXISTS `play_order_demand_info`;
/*!40101 SET @saved_cs_client     = @@character_set_client */;
/*!50503 SET character_set_client = utf8mb4 */;
CREATE TABLE `play_order_demand_info` (
  `id` varchar(255) NOT NULL COMMENT 'uuid',
  `tenant_id` varchar(32) DEFAULT NULL COMMENT '租户ID',
  `order_id` varchar(2) DEFAULT NULL COMMENT '订单ID',
  `sex` varchar(1) DEFAULT NULL COMMENT '店员性别（0:位置;1:男,2:女）',
  `level_id` varchar(32) DEFAULT NULL COMMENT '店员等级ID',
  `commodity_id` varchar(32) DEFAULT NULL COMMENT '服务项目类型\r\n',
  `commodity_number` int DEFAULT NULL COMMENT '商品数量',
  `lables` text COMMENT '标签',
  `updated_time` datetime DEFAULT NULL COMMENT '修改时间',
  `updated_by` varchar(32) DEFAULT NULL COMMENT '修改人的id',
  `created_time` datetime DEFAULT NULL COMMENT '创建时间',
  `created_by` varchar(32) DEFAULT NULL COMMENT '创建人的id',
  `deleted` tinyint(1) NOT NULL DEFAULT '0' COMMENT '逻辑删除 1已删除 0未删除',
  `version` int NOT NULL DEFAULT '1' COMMENT '数据版本',
  PRIMARY KEY (`id`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci ROW_FORMAT=DYNAMIC COMMENT='订单需求（随机单使用）';
/*!40101 SET character_set_client = @saved_cs_client */;

--
-- Table structure for table `play_order_evaluate_info`
--

DROP TABLE IF EXISTS `play_order_evaluate_info`;
/*!40101 SET @saved_cs_client     = @@character_set_client */;
/*!50503 SET character_set_client = utf8mb4 */;
CREATE TABLE `play_order_evaluate_info` (
  `id` varchar(255) NOT NULL COMMENT 'uuid',
  `tenant_id` varchar(32) DEFAULT NULL COMMENT '租户ID',
  `order_id` varchar(32) DEFAULT NULL COMMENT '订单ID',
  `custom_id` varchar(32) DEFAULT NULL COMMENT '顾客ID',
  `clerk_id` varchar(32) DEFAULT NULL COMMENT '店员ID',
  `commodity_id` varchar(32) DEFAULT NULL COMMENT '商品ID',
  `anonymous` varchar(1) DEFAULT '1' COMMENT '匿名评价(0:匿名，1:非匿名)',
  `evaluate_type` varchar(1) DEFAULT '0' COMMENT '评价类型(0:好评，1差评)',
  `evaluate_level` bigint DEFAULT NULL COMMENT '评价等级【1-5星，最低1星，最高5星】',
  `evaluate_con` text COMMENT '评价内容',
  `evaluate_time` datetime DEFAULT NULL COMMENT '评价时间',
  `hidden` varchar(1) DEFAULT '0' COMMENT '数据是否隐藏（0:未隐藏，1:隐藏）',
  `updated_time` datetime DEFAULT NULL COMMENT '修改时间',
  `updated_by` varchar(32) DEFAULT NULL COMMENT '修改人的id',
  `created_time` datetime DEFAULT NULL COMMENT '创建时间',
  `created_by` varchar(32) DEFAULT NULL COMMENT '创建人的id',
  `deleted` tinyint(1) NOT NULL DEFAULT '0' COMMENT '逻辑删除 1已删除 0未删除',
  `version` int NOT NULL DEFAULT '1' COMMENT '数据版本',
  PRIMARY KEY (`id`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci ROW_FORMAT=DYNAMIC COMMENT='订单评价信息';
/*!40101 SET character_set_client = @saved_cs_client */;

--
-- Table structure for table `play_order_info`
--

DROP TABLE IF EXISTS `play_order_info`;
/*!40101 SET @saved_cs_client     = @@character_set_client */;
/*!50503 SET character_set_client = utf8mb4 */;
CREATE TABLE `play_order_info` (
  `id` varchar(255) NOT NULL COMMENT 'uuid',
  `tenant_id` varchar(32) DEFAULT NULL COMMENT '租户ID',
  `order_no` varchar(20) DEFAULT NULL COMMENT '订单编号',
  `backend_entry` varchar(1) DEFAULT '0' COMMENT '订单是否是客服录入订单[0:不是,1:是]',
  `order_status` varchar(2) DEFAULT NULL COMMENT '订单状态【0:1:2:3:4】\r\n0：已下单（待接单）\r\n1：已接单（待开始）\r\n2：已开始（服务中）\r\n3：已完成\r\n4：已取消',
  `order_type` varchar(2) DEFAULT NULL COMMENT '订单类型【-1:退款订单;0:充值订单；1:提现订单；2:普通订单】',
  `place_type` varchar(2) DEFAULT NULL COMMENT '下单类型（0：指定单，1：随机单。2：打赏单）',
  `reward_type` varchar(1) DEFAULT NULL COMMENT '打赏类型（0:余额;1:礼物）',
  `first_order` varchar(2) DEFAULT '1' COMMENT '是否是首单【0：不是。1：是】',
  `refund_type` varchar(2) DEFAULT '0' COMMENT '退款类型【0：未退款，1：已退款】',
  `refund_amount` decimal(10,2) DEFAULT '0.00' COMMENT '退款金额',
  `refund_reason` varchar(255) DEFAULT NULL COMMENT '退款原因',
  `order_money` decimal(10,2) DEFAULT '0.00' COMMENT '下单金额',
  `discount_amount` decimal(10,2) DEFAULT '0.00' COMMENT '优惠金额',
  `final_amount` decimal(10,2) DEFAULT '0.00' COMMENT '最终金额（下单金额 - 退款金额）。单位分',
  `estimated_revenue` decimal(10,2) DEFAULT '0.00' COMMENT '店员预计收入',
  `estimated_revenue_ratio` int DEFAULT NULL COMMENT '店员提成比例',
  `labels` text COMMENT '订单要求',
  `sex` varchar(2) DEFAULT NULL COMMENT '随机单要求-店员性别（0:未知;1:男;2:女）',
  `level_id` varchar(32) DEFAULT NULL COMMENT '随机单要求-店员等级ID',
  `exclude_history` varchar(1) DEFAULT NULL COMMENT '随机单要求-是否排除下单过的成员（0:不排除;1:排除）',
  `commodity_id` varchar(32) DEFAULT NULL COMMENT '商品ID',
  `commodity_type` varchar(1) DEFAULT NULL COMMENT '商品类型[0:礼物，1：服务]',
  `commodity_price` decimal(10,2) DEFAULT NULL COMMENT '商品单价',
  `commodity_name` varchar(255) DEFAULT NULL COMMENT '商品名称',
  `service_duration` varchar(255) DEFAULT NULL COMMENT '商品属性（服务时长）',
  `commodity_number` int DEFAULT NULL COMMENT '商品数量',
  `coupon_ids` text COMMENT '优惠券列表',
  `use_coupon` varchar(1) DEFAULT '0' COMMENT '是否使用优惠券[0:未使用,1:已使用]',
  `pay_method` varchar(2) DEFAULT '0' COMMENT '支付方式，0：余额支付,1:微信支付,2:支付宝支付',
  `pay_state` varchar(2) DEFAULT NULL COMMENT '支付状态,[-1:支付失败;0:未支付;1:支付中,2:支付完成]',
  `pay_msg` text COMMENT '支付备注',
  `purchaser_by` varchar(32) DEFAULT '0' COMMENT '下单人',
  `purchaser_time` datetime DEFAULT NULL COMMENT '下单时间',
  `wei_chat_code` varchar(255) DEFAULT NULL COMMENT '微信号码',
  `review_required` varchar(2) DEFAULT NULL COMMENT '是否需要审核，0:不需要，1:需要',
  `review_state` varchar(2) DEFAULT NULL COMMENT '审核状态（0:未审核，1:已通过，2:不通过）',
  `reviewed_by` varchar(32) DEFAULT NULL COMMENT '审核人',
  `reviewed_time` datetime DEFAULT NULL COMMENT '审核时间',
  `reviewed_remark` varchar(255) DEFAULT NULL COMMENT '审核内容',
  `remark` longtext COMMENT '订单备注',
  `backend_remark` text COMMENT '后台备注',
  `accept_by` varchar(255) DEFAULT NULL COMMENT '接单人',
  `accept_time` datetime DEFAULT NULL COMMENT '接单时间',
  `group_id` varchar(32) DEFAULT NULL COMMENT '订单分组ID（和接单人同一个分组,接单时设置）',
  `order_start_time` datetime DEFAULT NULL COMMENT '开始时间',
  `order_end_time` datetime DEFAULT NULL COMMENT '完成时间',
  `order_cancel_time` datetime DEFAULT NULL COMMENT '取消时间',
  `order_settlement_state` varchar(1) DEFAULT '0' COMMENT '订单结算状态(0:未结算;1:已结算)',
  `order_settlement_time` datetime DEFAULT NULL COMMENT '订单结算时间',
  `orders_expired_state` varchar(1) DEFAULT '0' COMMENT '订单超时状态(0:未超时;1:已超时)',
  `updated_time` datetime DEFAULT NULL COMMENT '修改时间',
  `updated_by` varchar(32) DEFAULT NULL COMMENT '修改人的id',
  `created_time` datetime DEFAULT NULL COMMENT '创建时间',
  `created_by` varchar(32) DEFAULT NULL COMMENT '创建人的id',
  `deleted` tinyint(1) NOT NULL DEFAULT '0' COMMENT '逻辑删除 1已删除 0未删除',
  `version` int NOT NULL DEFAULT '1' COMMENT '数据版本',
  `profit_sharing_amount` decimal(20,2) DEFAULT NULL COMMENT '分账金额',
  PRIMARY KEY (`id`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci ROW_FORMAT=DYNAMIC COMMENT='订单信息';
/*!40101 SET character_set_client = @saved_cs_client */;

--
-- Table structure for table `play_order_log_info`
--

DROP TABLE IF EXISTS `play_order_log_info`;
/*!40101 SET @saved_cs_client     = @@character_set_client */;
/*!50503 SET character_set_client = utf8mb4 */;
CREATE TABLE `play_order_log_info` (
  `id` varchar(255) NOT NULL COMMENT 'uuid',
  `tenant_id` varchar(32) DEFAULT NULL COMMENT '租户ID',
  `order_id` varchar(1) DEFAULT NULL COMMENT '订单ID',
  `oper_type` varchar(1) DEFAULT NULL COMMENT '操作类型\r\n0:下单\r\n1:审核\r\n2:接单\r\n3:服务\r\n4:完成\r\n5:取消',
  `oper_time` datetime(1) DEFAULT NULL COMMENT '操作时间',
  `remark` varchar(255) DEFAULT NULL COMMENT '备注',
  `updated_time` datetime DEFAULT NULL COMMENT '修改时间',
  `updated_by` varchar(32) DEFAULT NULL COMMENT '修改人的id',
  `created_time` datetime DEFAULT NULL COMMENT '创建时间',
  `created_by` varchar(32) DEFAULT NULL COMMENT '创建人的id',
  `deleted` tinyint(1) NOT NULL DEFAULT '0' COMMENT '逻辑删除 1已删除 0未删除',
  `version` int NOT NULL DEFAULT '1' COMMENT '数据版本',
  PRIMARY KEY (`id`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci ROW_FORMAT=DYNAMIC COMMENT='订单日志';
/*!40101 SET character_set_client = @saved_cs_client */;

--
-- Table structure for table `play_order_random_info`
--

DROP TABLE IF EXISTS `play_order_random_info`;
/*!40101 SET @saved_cs_client     = @@character_set_client */;
/*!50503 SET character_set_client = utf8mb4 */;
CREATE TABLE `play_order_random_info` (
  `id` varchar(32) NOT NULL COMMENT 'UUID',
  `tenant_id` varchar(32) NOT NULL COMMENT '租户ID',
  `sex` varchar(100) DEFAULT NULL COMMENT '店员性别（0:未知;1:男;2:女）',
  `level_id` varchar(100) DEFAULT NULL COMMENT '店员等级ID',
  `labels` text COMMENT '标签列表',
  `commodity_id` varchar(32) DEFAULT NULL COMMENT '商品ID',
  `commodity_price` decimal(10,2) DEFAULT NULL COMMENT '商品单价',
  `commodity_name` varchar(255) DEFAULT NULL COMMENT '商品名称',
  `commodity_number` int DEFAULT NULL COMMENT '商品数量',
  `service_duration` varchar(255) DEFAULT NULL COMMENT '服务时长',
  `wei_chat_code` varchar(100) DEFAULT NULL COMMENT '微信号',
  `exclude_history` varchar(100) DEFAULT NULL COMMENT '是否排除下单过的成员（0:不排除;1:排除）',
  `remark` text COMMENT '备注',
  `purchaser_by` varchar(32) DEFAULT NULL COMMENT '下单人',
  `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 '逻辑删除 1已删除 0未删除',
  `version` int NOT NULL DEFAULT '1' COMMENT '数据版本',
  PRIMARY KEY (`id`) USING BTREE,
  KEY `key_tenant_id` (`tenant_id`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci ROW_FORMAT=DYNAMIC COMMENT='随机单列表';
/*!40101 SET character_set_client = @saved_cs_client */;

--
-- Table structure for table `play_order_refund_info`
--

DROP TABLE IF EXISTS `play_order_refund_info`;
/*!40101 SET @saved_cs_client     = @@character_set_client */;
/*!50503 SET character_set_client = utf8mb4 */;
CREATE TABLE `play_order_refund_info` (
  `id` varchar(255) NOT NULL COMMENT 'uuid',
  `tenant_id` varchar(32) DEFAULT NULL COMMENT '租户ID',
  `order_id` varchar(32) DEFAULT NULL COMMENT '订单ID',
  `custom_id` varchar(32) DEFAULT NULL COMMENT '顾客ID',
  `clerk_id` varchar(32) DEFAULT NULL COMMENT '店员ID',
  `pay_method` varchar(1) DEFAULT '0' COMMENT '支付方式，[0:余额支付;1:微信支付,2:支付宝支付]',
  `use_coupon` varchar(1) DEFAULT NULL COMMENT '是否使用优惠券[0:未使用,1:已使用]',
  `refund_type` varchar(1) DEFAULT '0' COMMENT '退款类型【0:部分退款;1:全部退款】',
  `refund_amount` decimal(10,2) DEFAULT NULL COMMENT '退款金额',
  `refund_reason` text COMMENT '退款原因',
  `refund_by_type` varchar(1) DEFAULT NULL COMMENT '退款人类型[0:顾客;1:店员;2:组长]',
  `refund_by_id` varchar(32) DEFAULT NULL COMMENT '退款人ID',
  `images` text COMMENT '截图',
  `refund_state` varchar(2) DEFAULT '0' COMMENT '退款状态[-1:异常;0:处理中;1:成功;2:关闭]',
  `reviewed_required` varchar(1) DEFAULT NULL COMMENT '是否需要审核，0:不需要，1:需要',
  `reviewed_state` varchar(1) DEFAULT NULL COMMENT '审核状态（0:未审核，1:已通过，2:不通过）',
  `reviewed_by` varchar(32) DEFAULT NULL COMMENT '审核人',
  `reviewed_time` datetime DEFAULT NULL COMMENT '审核时间',
  `reviewed_remark` varchar(255) DEFAULT NULL COMMENT '审核内容',
  `updated_time` datetime DEFAULT NULL COMMENT '修改时间',
  `updated_by` varchar(32) DEFAULT NULL COMMENT '修改人的id',
  `created_time` datetime DEFAULT NULL COMMENT '创建时间',
  `created_by` varchar(32) DEFAULT NULL COMMENT '创建人的id',
  `deleted` tinyint(1) NOT NULL DEFAULT '0' COMMENT '逻辑删除 1已删除 0未删除',
  `version` int NOT NULL DEFAULT '1' COMMENT '数据版本',
  PRIMARY KEY (`id`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci ROW_FORMAT=DYNAMIC COMMENT='订单退款信息';
/*!40101 SET character_set_client = @saved_cs_client */;

--
-- Table structure for table `play_personnel_admin_info`
--

DROP TABLE IF EXISTS `play_personnel_admin_info`;
/*!40101 SET @saved_cs_client     = @@character_set_client */;
/*!50503 SET character_set_client = utf8mb4 */;
CREATE TABLE `play_personnel_admin_info` (
  `id` varchar(32) NOT NULL COMMENT 'UUID',
  `tenant_id` varchar(32) NOT NULL COMMENT '租户ID',
  `sys_user_id` varchar(255) DEFAULT NULL COMMENT '账号ID',
  `sys_user_code` varchar(255) DEFAULT NULL COMMENT '用户账号',
  `admin_name` varchar(255) DEFAULT NULL COMMENT '分组名称',
  `leader_name` varchar(255) DEFAULT NULL COMMENT '组长名称',
  `add_time` datetime DEFAULT NULL COMMENT '添加时间',
  `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 '逻辑删除 1已删除 0未删除',
  `version` int NOT NULL DEFAULT '1' COMMENT '数据版本',
  PRIMARY KEY (`id`) USING BTREE,
  KEY `key_tenant_id` (`tenant_id`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci ROW_FORMAT=DYNAMIC COMMENT='管理员管理';
/*!40101 SET character_set_client = @saved_cs_client */;

--
-- Table structure for table `play_personnel_group_info`
--

DROP TABLE IF EXISTS `play_personnel_group_info`;
/*!40101 SET @saved_cs_client     = @@character_set_client */;
/*!50503 SET character_set_client = utf8mb4 */;
CREATE TABLE `play_personnel_group_info` (
  `id` varchar(32) NOT NULL COMMENT 'UUID',
  `tenant_id` varchar(32) NOT NULL COMMENT '租户ID',
  `sys_user_id` varchar(255) DEFAULT NULL COMMENT '账号ID',
  `sys_user_code` varchar(255) DEFAULT NULL COMMENT '用户账号',
  `group_name` varchar(255) DEFAULT NULL COMMENT '分组名称',
  `leader_name` varchar(255) DEFAULT NULL COMMENT '组长名称',
  `add_time` datetime DEFAULT NULL COMMENT '添加时间',
  `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 '逻辑删除 1已删除 0未删除',
  `version` int NOT NULL DEFAULT '1' COMMENT '数据版本',
  PRIMARY KEY (`id`) USING BTREE,
  KEY `key_tenant_id` (`tenant_id`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci ROW_FORMAT=DYNAMIC COMMENT='分组管理';
/*!40101 SET character_set_client = @saved_cs_client */;

--
-- Table structure for table `play_personnel_waiter_info`
--

DROP TABLE IF EXISTS `play_personnel_waiter_info`;
/*!40101 SET @saved_cs_client     = @@character_set_client */;
/*!50503 SET character_set_client = utf8mb4 */;
CREATE TABLE `play_personnel_waiter_info` (
  `id` varchar(32) NOT NULL COMMENT 'UUID',
  `tenant_id` varchar(32) NOT NULL COMMENT '租户ID',
  `sys_user_id` varchar(255) DEFAULT NULL COMMENT '账号ID',
  `sys_user_code` varchar(255) DEFAULT NULL COMMENT '用户账号',
  `waiter_name` varchar(255) DEFAULT NULL COMMENT '客服名称',
  `add_time` datetime DEFAULT NULL COMMENT '添加时间',
  `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 '逻辑删除 1已删除 0未删除',
  `version` int NOT NULL DEFAULT '1' COMMENT '数据版本',
  PRIMARY KEY (`id`) USING BTREE,
  KEY `key_tenant_id` (`tenant_id`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci ROW_FORMAT=DYNAMIC COMMENT='客服管理';
/*!40101 SET character_set_client = @saved_cs_client */;

--
-- Table structure for table `play_shop_article_info`
--

DROP TABLE IF EXISTS `play_shop_article_info`;
/*!40101 SET @saved_cs_client     = @@character_set_client */;
/*!50503 SET character_set_client = utf8mb4 */;
CREATE TABLE `play_shop_article_info` (
  `id` varchar(32) NOT NULL COMMENT 'UUID',
  `tenant_id` varchar(32) NOT NULL COMMENT '租户ID',
  `article_type` varchar(255) DEFAULT NULL COMMENT '文章类型',
  `article_title` varchar(255) DEFAULT '0' COMMENT '文章标标题',
  `article_content` text COMMENT '文章内容',
  `visits_number` int DEFAULT '0' COMMENT '浏览量',
  `sort` int DEFAULT '0' COMMENT '排序',
  `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 '逻辑删除 1已删除 0未删除',
  `version` int NOT NULL DEFAULT '1' COMMENT '数据版本',
  PRIMARY KEY (`id`) USING BTREE,
  KEY `key_tenant_id` (`tenant_id`) USING BTREE,
  KEY `key_openid` (`article_type`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci ROW_FORMAT=DYNAMIC COMMENT='店铺文章信息';
/*!40101 SET character_set_client = @saved_cs_client */;

--
-- Table structure for table `play_shop_carousel_info`
--

DROP TABLE IF EXISTS `play_shop_carousel_info`;
/*!40101 SET @saved_cs_client     = @@character_set_client */;
/*!50503 SET character_set_client = utf8mb4 */;
CREATE TABLE `play_shop_carousel_info` (
  `id` varchar(32) NOT NULL COMMENT 'UUID',
  `tenant_id` varchar(32) NOT NULL COMMENT '租户ID',
  `carousel_index` varchar(1) DEFAULT NULL COMMENT '轮播图位置(0:首页)',
  `enable_state` varchar(1) DEFAULT '0' COMMENT '启用状态（0:未启用,1:已启用）',
  `carousel_url` text COMMENT '图片URL',
  `navigation_type` varchar(1) DEFAULT '0' COMMENT '导航类型(0:无)',
  `sort` int DEFAULT '0' COMMENT '排序',
  `add_time` datetime DEFAULT NULL COMMENT '创建时间',
  `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 '逻辑删除 1已删除 0未删除',
  `version` int NOT NULL DEFAULT '1' COMMENT '数据版本',
  PRIMARY KEY (`id`) USING BTREE,
  KEY `key_tenant_id` (`tenant_id`) USING BTREE,
  KEY `key_openid` (`carousel_index`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci ROW_FORMAT=DYNAMIC COMMENT='店铺首页轮播';
/*!40101 SET character_set_client = @saved_cs_client */;

--
-- Table structure for table `sys_administrative_area_dict_info`
--

DROP TABLE IF EXISTS `sys_administrative_area_dict_info`;
/*!40101 SET @saved_cs_client     = @@character_set_client */;
/*!50503 SET character_set_client = utf8mb4 */;
CREATE TABLE `sys_administrative_area_dict_info` (
  `id` varchar(255) NOT NULL COMMENT 'UUID',
  `code` varchar(255) DEFAULT NULL COMMENT '编码',
  `name` varchar(255) DEFAULT NULL COMMENT '名称',
  `level` varchar(2) DEFAULT NULL COMMENT '区域等级',
  `p_code` varchar(32) DEFAULT NULL COMMENT '上级单位编码',
  `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 '逻辑删除 1已删除 0未删除',
  `version` int NOT NULL DEFAULT '1' COMMENT '数据版本',
  `tenant_id` varchar(32) DEFAULT NULL,
  PRIMARY KEY (`id`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci ROW_FORMAT=DYNAMIC COMMENT='地区信息';
/*!40101 SET character_set_client = @saved_cs_client */;

--
-- Table structure for table `sys_dept`
--

DROP TABLE IF EXISTS `sys_dept`;
/*!40101 SET @saved_cs_client     = @@character_set_client */;
/*!50503 SET character_set_client = utf8mb4 */;
CREATE TABLE `sys_dept` (
  `dept_id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键ID',
  `parent_id` bigint NOT NULL DEFAULT '0' COMMENT '父部门id',
  `ancestors` varchar(50) NOT NULL COMMENT '祖级列表',
  `dept_name` varchar(30) DEFAULT NULL COMMENT '部门名称',
  `sort` int DEFAULT NULL COMMENT '显示顺序',
  `leader` varchar(20) DEFAULT NULL COMMENT '负责人',
  `phone` varchar(11) DEFAULT NULL COMMENT '联系电话',
  `email` varchar(30) DEFAULT NULL COMMENT '邮箱',
  `status` tinyint(1) DEFAULT '0' COMMENT '部门状态（0正常 1停用）',
  `dept_level` int DEFAULT NULL COMMENT '部门级别',
  `tenant_id` varchar(32) DEFAULT NULL COMMENT '租户ID',
  `updated_time` datetime DEFAULT NULL COMMENT '修改时间',
  `updated_by` varchar(32) DEFAULT NULL COMMENT '修改人的id',
  `created_time` datetime DEFAULT NULL COMMENT '创建时间',
  `created_by` varchar(32) DEFAULT NULL COMMENT '创建人的id',
  `deleted` tinyint(1) NOT NULL DEFAULT '0' COMMENT '逻辑删除 1已删除 0未删除',
  `version` int NOT NULL DEFAULT '1' COMMENT '数据版本',
  PRIMARY KEY (`dept_id`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=67 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci ROW_FORMAT=DYNAMIC COMMENT='部门表';
/*!40101 SET character_set_client = @saved_cs_client */;

--
-- Table structure for table `sys_dict`
--

DROP TABLE IF EXISTS `sys_dict`;
/*!40101 SET @saved_cs_client     = @@character_set_client */;
/*!50503 SET character_set_client = utf8mb4 */;
CREATE TABLE `sys_dict` (
  `dict_id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键ID',
  `dict_type` varchar(30) NOT NULL COMMENT '字典类型',
  `dict_name` varchar(30) DEFAULT NULL COMMENT '字典名称',
  `status` tinyint(1) DEFAULT '0' COMMENT '状态（0正常 1停用）',
  `remark` varchar(100) DEFAULT NULL COMMENT '备注',
  `updated_time` datetime DEFAULT NULL COMMENT '修改时间',
  `updated_by` varchar(32) DEFAULT NULL COMMENT '修改人的id',
  `created_time` datetime DEFAULT NULL COMMENT '创建时间',
  `created_by` varchar(32) DEFAULT NULL COMMENT '创建人的id',
  `deleted` tinyint(1) NOT NULL DEFAULT '0' COMMENT '逻辑删除 1已删除 0未删除',
  `version` int NOT NULL DEFAULT '1' COMMENT '数据版本',
  PRIMARY KEY (`dict_id`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=14 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci ROW_FORMAT=DYNAMIC COMMENT='字典表';
/*!40101 SET character_set_client = @saved_cs_client */;

--
-- Table structure for table `sys_dict_data`
--

DROP TABLE IF EXISTS `sys_dict_data`;
/*!40101 SET @saved_cs_client     = @@character_set_client */;
/*!50503 SET character_set_client = utf8mb4 */;
CREATE TABLE `sys_dict_data` (
  `dict_data_id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键ID',
  `dict_type` varchar(30) NOT NULL COMMENT '字典类型',
  `dict_label` varchar(30) DEFAULT NULL COMMENT '字典名称',
  `dict_value` varchar(100) DEFAULT NULL COMMENT '字典值',
  `sort` int DEFAULT NULL COMMENT '显示顺序',
  `is_default` char(1) DEFAULT 'N' COMMENT '是否默认（Y是 N否）',
  `list_class` varchar(100) DEFAULT NULL COMMENT '表格回显样式',
  `status` tinyint(1) DEFAULT '0' COMMENT '状态（0正常 1停用）',
  `remark` varchar(100) DEFAULT NULL COMMENT '备注',
  `updated_time` datetime DEFAULT NULL COMMENT '修改时间',
  `updated_by` varchar(32) DEFAULT NULL COMMENT '修改人的id',
  `created_time` datetime DEFAULT NULL COMMENT '创建时间',
  `created_by` varchar(32) DEFAULT NULL COMMENT '创建人的id',
  `deleted` tinyint(1) NOT NULL DEFAULT '0' COMMENT '逻辑删除 1已删除 0未删除',
  `version` int NOT NULL DEFAULT '1' COMMENT '数据版本',
  PRIMARY KEY (`dict_data_id`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=13 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci ROW_FORMAT=DYNAMIC COMMENT='字典数据表';
/*!40101 SET character_set_client = @saved_cs_client */;

--
-- Table structure for table `sys_login_log`
--

DROP TABLE IF EXISTS `sys_login_log`;
/*!40101 SET @saved_cs_client     = @@character_set_client */;
/*!50503 SET character_set_client = utf8mb4 */;
CREATE TABLE `sys_login_log` (
  `login_id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键ID',
  `user_code` varchar(50) NOT NULL DEFAULT '0' COMMENT '用户账号',
  `ipaddr` varchar(128) NOT NULL COMMENT '登录IP地址',
  `login_location` varchar(255) DEFAULT NULL COMMENT '登录地点',
  `browser` varchar(50) DEFAULT NULL COMMENT '浏览器类型',
  `os` varchar(50) DEFAULT NULL COMMENT '操作系统',
  `msg` varchar(255) DEFAULT NULL COMMENT '提示消息',
  `status` tinyint(1) DEFAULT '0' COMMENT '登录状态（0成功 1失败）',
  `login_time` datetime DEFAULT NULL COMMENT '访问时间',
  `tenant_id` varchar(32) DEFAULT NULL COMMENT '租户ID',
  `updated_time` datetime DEFAULT NULL COMMENT '修改时间',
  `updated_by` varchar(32) DEFAULT NULL COMMENT '修改人的id',
  `created_time` datetime DEFAULT NULL COMMENT '创建时间',
  `created_by` varchar(32) DEFAULT NULL COMMENT '创建人的id',
  `deleted` tinyint(1) NOT NULL DEFAULT '0' COMMENT '逻辑删除 1已删除 0未删除',
  `version` int NOT NULL DEFAULT '1' COMMENT '数据版本',
  PRIMARY KEY (`login_id`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=913 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci ROW_FORMAT=DYNAMIC COMMENT='系统访问日志表';
/*!40101 SET character_set_client = @saved_cs_client */;

--
-- Table structure for table `sys_menu`
--

DROP TABLE IF EXISTS `sys_menu`;
/*!40101 SET @saved_cs_client     = @@character_set_client */;
/*!50503 SET character_set_client = utf8mb4 */;
CREATE TABLE `sys_menu` (
  `menu_id` bigint NOT NULL AUTO_INCREMENT COMMENT '菜单ID',
  `menu_name` varchar(64) DEFAULT NULL COMMENT '菜单名称',
  `menu_code` varchar(20) DEFAULT NULL COMMENT '前端名称',
  `icon` varchar(100) DEFAULT '#' COMMENT '菜单图标',
  `permission` varchar(100) DEFAULT NULL COMMENT '授权标识',
  `menu_level` bigint DEFAULT NULL COMMENT '菜单级别',
  `sort` int DEFAULT NULL COMMENT '排序',
  `parent_id` bigint DEFAULT NULL COMMENT '父ID',
  `menu_type` tinyint unsigned NOT NULL DEFAULT '1' COMMENT '菜单类型;1:有界面可访问菜单,2:无界面可访问菜单,0:只作为菜单',
  `status` tinyint unsigned NOT NULL DEFAULT '0' COMMENT '鐘舵€?1:鏄剧ず,0:涓嶆樉绀?',
  `remark` varchar(255) DEFAULT NULL COMMENT '备注',
  `path` varchar(200) DEFAULT NULL COMMENT '路由地址',
  `component` varchar(200) DEFAULT NULL COMMENT '组件路径',
  `router_query` varchar(200) DEFAULT NULL COMMENT '路由参数',
  `is_frame` int DEFAULT '0' COMMENT '是否为外链（0否 1是）',
  `visible` int DEFAULT '1' COMMENT '菜单是否显示;0:隐藏 1:显示',
  `updated_time` datetime DEFAULT NULL COMMENT '修改时间',
  `updated_by` varchar(32) DEFAULT NULL COMMENT '修改人的id',
  `created_time` datetime DEFAULT NULL COMMENT '创建时间',
  `created_by` varchar(32) DEFAULT NULL COMMENT '创建人的id',
  `deleted` tinyint(1) NOT NULL DEFAULT '0' COMMENT '逻辑删除 1已删除 0未删除',
  `version` int NOT NULL DEFAULT '1' COMMENT '数据版本',
  `perms` varchar(255) DEFAULT NULL,
  PRIMARY KEY (`menu_id`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=2105 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci ROW_FORMAT=DYNAMIC COMMENT='菜单表';
/*!40101 SET character_set_client = @saved_cs_client */;

--
-- Table structure for table `sys_operation_log`
--

DROP TABLE IF EXISTS `sys_operation_log`;
/*!40101 SET @saved_cs_client     = @@character_set_client */;
/*!50503 SET character_set_client = utf8mb4 */;
CREATE TABLE `sys_operation_log` (
  `oper_id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键ID',
  `title` varchar(30) NOT NULL COMMENT '模块标题',
  `business_type` int NOT NULL DEFAULT '0' COMMENT '业务类型（0其它 1新增 2修改 3删除）',
  `method` varchar(100) DEFAULT NULL COMMENT '方法名称',
  `request_method` varchar(10) DEFAULT NULL COMMENT '请求方式',
  `operator_type` int DEFAULT '0' COMMENT '操作类别（0其它 1后台用户 2手机端用户）',
  `oper_name` varchar(50) DEFAULT NULL COMMENT '操作人员',
  `oper_url` varchar(255) DEFAULT '0' COMMENT '请求URL',
  `oper_ip` varchar(128) DEFAULT NULL COMMENT '主机地址',
  `oper_location` varchar(255) DEFAULT NULL COMMENT '操作地点',
  `oper_param` varchar(2000) DEFAULT NULL COMMENT '请求参数',
  `json_result` varchar(2000) DEFAULT NULL COMMENT '返回参数',
  `status` int DEFAULT '0' COMMENT '操作状态（0正常 1异常）',
  `error_msg` varchar(2000) DEFAULT NULL COMMENT '错误消息',
  `oper_time` datetime DEFAULT NULL COMMENT '操作时间',
  `dept_name` varchar(50) DEFAULT NULL COMMENT '部门名称',
  `dept_id` bigint DEFAULT NULL COMMENT '部门id',
  `tenant_id` varchar(32) DEFAULT NULL COMMENT '租户ID',
  `updated_time` datetime DEFAULT NULL COMMENT '修改时间',
  `updated_by` varchar(32) DEFAULT NULL COMMENT '修改人的id',
  `created_time` datetime DEFAULT NULL COMMENT '创建时间',
  `created_by` varchar(32) DEFAULT NULL COMMENT '创建人的id',
  `deleted` tinyint(1) NOT NULL DEFAULT '0' COMMENT '逻辑删除 1已删除 0未删除',
  `version` int NOT NULL DEFAULT '1' COMMENT '数据版本',
  PRIMARY KEY (`oper_id`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=1332 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci ROW_FORMAT=DYNAMIC COMMENT='操作日志表';
/*!40101 SET character_set_client = @saved_cs_client */;

--
-- Table structure for table `sys_role`
--

DROP TABLE IF EXISTS `sys_role`;
/*!40101 SET @saved_cs_client     = @@character_set_client */;
/*!50503 SET character_set_client = utf8mb4 */;
CREATE TABLE `sys_role` (
  `role_id` bigint NOT NULL AUTO_INCREMENT COMMENT '角色ID',
  `role_name` varchar(64) NOT NULL COMMENT '角色名称',
  `description` varchar(100) DEFAULT NULL COMMENT '角色描述',
  `role_key` varchar(100) DEFAULT NULL COMMENT '角色权限字符串',
  `data_scope` tinyint DEFAULT '1' COMMENT '数据范围（1：全部数据权限 2：自定数据权限 3：本部门数据权限 4：本部门及以下数据权限）',
  `menu_check_strictly` tinyint(1) DEFAULT '1' COMMENT '菜单树选择项是否关联显示（ 0：父子不互相关联显示 1：父子互相关联显示）',
  `dept_check_strictly` tinyint(1) DEFAULT '1' COMMENT '部门树选择项是否关联显示（ 0：父子不互相关联显示 1：父子互相关联显示）',
  `status` tinyint unsigned NOT NULL DEFAULT '0' COMMENT '状态;0:正常;1:禁用',
  `tenant_id` varchar(32) DEFAULT NULL COMMENT '租户ID',
  `updated_time` datetime DEFAULT NULL COMMENT '修改时间',
  `updated_by` varchar(32) DEFAULT NULL COMMENT '修改人的id',
  `created_time` datetime DEFAULT NULL COMMENT '创建时间',
  `created_by` varchar(32) DEFAULT NULL COMMENT '创建人的id',
  `deleted` tinyint(1) NOT NULL DEFAULT '0' COMMENT '逻辑删除 1已删除 0未删除',
  `version` int NOT NULL DEFAULT '1' COMMENT '数据版本',
  PRIMARY KEY (`role_id`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=146 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci ROW_FORMAT=DYNAMIC COMMENT='角色表';
/*!40101 SET character_set_client = @saved_cs_client */;

--
-- Table structure for table `sys_role_dept`
--

DROP TABLE IF EXISTS `sys_role_dept`;
/*!40101 SET @saved_cs_client     = @@character_set_client */;
/*!50503 SET character_set_client = utf8mb4 */;
CREATE TABLE `sys_role_dept` (
  `role_dept_id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键ID',
  `dept_id` bigint NOT NULL COMMENT '部门ID',
  `role_id` bigint NOT NULL COMMENT '角色ID',
  `tenant_id` varchar(32) DEFAULT NULL COMMENT '租户ID',
  `updated_time` datetime DEFAULT NULL COMMENT '修改时间',
  `updated_by` varchar(32) DEFAULT NULL COMMENT '修改人的id',
  `created_time` datetime DEFAULT NULL COMMENT '创建时间',
  `created_by` varchar(32) DEFAULT NULL COMMENT '创建人的id',
  `deleted` tinyint(1) NOT NULL DEFAULT '0' COMMENT '逻辑删除 1已删除 0未删除',
  `version` int NOT NULL DEFAULT '1' COMMENT '数据版本',
  PRIMARY KEY (`role_dept_id`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci ROW_FORMAT=DYNAMIC COMMENT='角色部门关联表';
/*!40101 SET character_set_client = @saved_cs_client */;

--
-- Table structure for table `sys_role_menu`
--

DROP TABLE IF EXISTS `sys_role_menu`;
/*!40101 SET @saved_cs_client     = @@character_set_client */;
/*!50503 SET character_set_client = utf8mb4 */;
CREATE TABLE `sys_role_menu` (
  `role_menu_id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键ID',
  `menu_id` bigint NOT NULL COMMENT '菜单ID',
  `role_id` bigint NOT NULL COMMENT '角色ID',
  `tenant_id` varchar(32) DEFAULT NULL COMMENT '租户id',
  `updated_time` datetime DEFAULT NULL COMMENT '修改时间',
  `updated_by` varchar(32) DEFAULT NULL COMMENT '修改人的id',
  `created_time` datetime DEFAULT NULL COMMENT '创建时间',
  `created_by` varchar(32) DEFAULT NULL COMMENT '创建人的id',
  `deleted` tinyint(1) NOT NULL DEFAULT '0' COMMENT '逻辑删除 1已删除 0未删除',
  `version` int NOT NULL DEFAULT '1' COMMENT '数据版本',
  PRIMARY KEY (`role_menu_id`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=5237 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci ROW_FORMAT=DYNAMIC COMMENT='菜单角色关联表';
/*!40101 SET character_set_client = @saved_cs_client */;

--
-- Table structure for table `sys_tenant`
--

DROP TABLE IF EXISTS `sys_tenant`;
/*!40101 SET @saved_cs_client     = @@character_set_client */;
/*!50503 SET character_set_client = utf8mb4 */;
CREATE TABLE `sys_tenant` (
  `tenant_id` varchar(32) NOT NULL COMMENT '租户ID',
  `tenant_type` tinyint unsigned NOT NULL DEFAULT '0' COMMENT '租户类型;0:公司;1:部门;2:个人;3:其他',
  `tenant_status` tinyint unsigned NOT NULL DEFAULT '0' COMMENT '用户状态;0:正常,1:停用',
  `tenant_code` varchar(32) DEFAULT NULL COMMENT '编码',
  `tenant_name` varchar(64) DEFAULT NULL COMMENT '名称',
  `tenant_logo` varchar(128) DEFAULT NULL COMMENT 'logo图',
  `sort_order` int DEFAULT NULL COMMENT '显示顺序',
  `home_url` varchar(64) DEFAULT NULL COMMENT '官方地址',
  `phone` varchar(20) DEFAULT NULL COMMENT '电话',
  `fax` varchar(15) DEFAULT NULL COMMENT '传真',
  `email` varchar(20) DEFAULT NULL COMMENT '邮箱',
  `address` varchar(128) DEFAULT NULL COMMENT '地址',
  `user_name` varchar(20) DEFAULT NULL COMMENT '管理员账号',
  `user_pwd` varchar(64) DEFAULT NULL COMMENT '管理员密码',
  `package_id` varchar(32) DEFAULT NULL COMMENT '套餐id',
  `tenant_time` datetime DEFAULT NULL COMMENT '租赁到期时间',
  `remarks` varchar(255) DEFAULT NULL COMMENT '备注',
  `tenant_key` varchar(255) DEFAULT NULL COMMENT '租户权限标识',
  `app_id` varchar(2000) DEFAULT NULL COMMENT '微信公众号ID',
  `secret` varchar(2000) DEFAULT NULL COMMENT '微信公众号的app secret',
  `mch_id` varchar(2000) DEFAULT NULL COMMENT '商户ID',
  `mch_key` varchar(2000) DEFAULT NULL COMMENT '商户秘钥',
  `certificate_path` varchar(2000) DEFAULT NULL COMMENT '商户秘钥',
  `updated_time` datetime DEFAULT NULL COMMENT '修改时间',
  `updated_by` varchar(32) DEFAULT NULL COMMENT '修改人的id',
  `created_time` datetime DEFAULT NULL COMMENT '创建时间',
  `created_by` varchar(32) DEFAULT NULL COMMENT '创建人的id',
  `deleted` tinyint(1) NOT NULL DEFAULT '0' COMMENT '逻辑删除 1已删除 0未删除',
  `version` int NOT NULL DEFAULT '1' COMMENT '数据版本',
  `czcgtz_template_id` varchar(100) DEFAULT NULL COMMENT '充值成功通知',
  `xdcgtz_template_id` varchar(100) DEFAULT NULL COMMENT '下单成功通知',
  `sdxddtz_template_id` varchar(100) DEFAULT NULL COMMENT '收到新订单通知',
  `rzsqshtx_template_id` varchar(100) DEFAULT NULL COMMENT '认证申请审核提醒',
  `xdtx_template_id` varchar(100) DEFAULT NULL,
  `rzsq_admin_template_id` varchar(100) DEFAULT NULL,
  `dingdantousuchuli_template_id` varchar(100) DEFAULT NULL COMMENT '订单投诉处理id',
  `jiedanchenggongtongzhi_template_id` varchar(100) DEFAULT NULL COMMENT '接单成功通知id',
  `dingdanzhifuchenggongtongzhi_template_id` varchar(100) DEFAULT NULL COMMENT '订单支付成功通知',
  `quxiaodingdantongzhi_template_id` varchar(100) DEFAULT NULL COMMENT '取消订单通知',
  `dingdanwanchengtongzhi_template_id` varchar(100) DEFAULT NULL COMMENT '订单完成通知',
  `chongzhichenggong_template_id` varchar(100) DEFAULT NULL COMMENT '充值成功通知',
  `xindingdanshoulitongzhi_template_id` varchar(100) DEFAULT NULL COMMENT '新订单受理通知',
  `profitsharing_rate` int DEFAULT '0' COMMENT '分账比例，数字，最小0，最大30',
  PRIMARY KEY (`tenant_id`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci ROW_FORMAT=DYNAMIC COMMENT='租户表';
/*!40101 SET character_set_client = @saved_cs_client */;

--
-- Table structure for table `sys_tenant_package`
--

DROP TABLE IF EXISTS `sys_tenant_package`;
/*!40101 SET @saved_cs_client     = @@character_set_client */;
/*!50503 SET character_set_client = utf8mb4 */;
CREATE TABLE `sys_tenant_package` (
  `package_id` varchar(32) NOT NULL COMMENT '套餐id',
  `package_name` varchar(64) DEFAULT NULL COMMENT '名称',
  `menu_ids` varchar(1000) DEFAULT NULL COMMENT '关联菜单id',
  `status` tinyint unsigned NOT NULL DEFAULT '0' COMMENT '状态;0:正常,1:停用',
  `remarks` varchar(255) DEFAULT NULL COMMENT '备注',
  `updated_time` datetime DEFAULT NULL COMMENT '修改时间',
  `updated_by` varchar(32) DEFAULT NULL COMMENT '修改人的id',
  `created_time` datetime DEFAULT NULL COMMENT '创建时间',
  `created_by` varchar(32) DEFAULT NULL COMMENT '创建人的id',
  `deleted` tinyint(1) NOT NULL DEFAULT '0' COMMENT '逻辑删除 1已删除 0未删除',
  `version` int NOT NULL DEFAULT '1' COMMENT '数据版本',
  PRIMARY KEY (`package_id`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci ROW_FORMAT=DYNAMIC COMMENT='租户套餐表';
/*!40101 SET character_set_client = @saved_cs_client */;

--
-- Table structure for table `sys_tenant_recharge_info`
--

DROP TABLE IF EXISTS `sys_tenant_recharge_info`;
/*!40101 SET @saved_cs_client     = @@character_set_client */;
/*!50503 SET character_set_client = utf8mb4 */;
CREATE TABLE `sys_tenant_recharge_info` (
  `id` varchar(32) NOT NULL COMMENT 'UUID',
  `tenant_id` varchar(32) NOT NULL COMMENT '租户ID',
  `recharge_amount` decimal(10,2) NOT NULL DEFAULT '0.00' COMMENT '充值金额',
  `recharge_user` varchar(255) DEFAULT NULL COMMENT '充值用户',
  `recharge_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '充值时间',
  `pay_method` varchar(255) DEFAULT NULL COMMENT '支付方式，1:微信支付,2:支付宝支付;3:银行卡支付',
  `pay_account` varchar(255) DEFAULT NULL COMMENT '支付账号',
  `remark` varchar(255) DEFAULT NULL COMMENT '备注',
  `updated_time` datetime DEFAULT NULL COMMENT '修改时间',
  `updated_by` varchar(32) DEFAULT NULL COMMENT '修改人的id',
  `created_time` datetime DEFAULT NULL COMMENT '创建时间',
  `created_by` varchar(32) DEFAULT NULL COMMENT '创建人的id',
  `deleted` tinyint(1) NOT NULL DEFAULT '0' COMMENT '逻辑删除 1已删除 0未删除',
  `version` int NOT NULL DEFAULT '1' COMMENT '数据版本',
  PRIMARY KEY (`tenant_id`,`id`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci ROW_FORMAT=DYNAMIC COMMENT='租户充值信息';
/*!40101 SET character_set_client = @saved_cs_client */;

--
-- Table structure for table `sys_user`
--

DROP TABLE IF EXISTS `sys_user`;
/*!40101 SET @saved_cs_client     = @@character_set_client */;
/*!50503 SET character_set_client = utf8mb4 */;
CREATE TABLE `sys_user` (
  `user_id` varchar(32) NOT NULL COMMENT '用户ID',
  `user_type` tinyint unsigned NOT NULL DEFAULT '1' COMMENT '用户类型;1:admin;2:会员',
  `sex` tinyint NOT NULL DEFAULT '0' COMMENT '性别;0:保密,1:男,2:女',
  `birthday` datetime DEFAULT NULL COMMENT '生日',
  `last_login_time` datetime DEFAULT NULL COMMENT '最后登录时间',
  `last_login_ip` varchar(15) NOT NULL DEFAULT '' COMMENT '最后登录ip',
  `user_code` varchar(60) DEFAULT NULL COMMENT '用户名',
  `pass_word` varchar(64) NOT NULL DEFAULT '' COMMENT '登录密码;cmf_password加密',
  `real_name` varchar(20) DEFAULT NULL COMMENT '姓名',
  `user_nickname` varchar(50) DEFAULT NULL COMMENT '用户昵称',
  `user_email` varchar(100) NOT NULL DEFAULT '' COMMENT '用户登录邮箱',
  `status` tinyint DEFAULT '0' COMMENT '状态 0:正常；1:停用 ',
  `user_url` varchar(100) NOT NULL DEFAULT '' COMMENT '用户个人网址',
  `avatar` varchar(255) NOT NULL DEFAULT '' COMMENT '用户头像',
  `mobile` varchar(20) DEFAULT NULL,
  `dept_id` bigint DEFAULT NULL COMMENT '部门id',
  `add_time` datetime DEFAULT NULL COMMENT '新增时间',
  `tenant_id` varchar(32) DEFAULT NULL COMMENT '租户ID',
  `updated_time` datetime DEFAULT NULL COMMENT '修改时间',
  `updated_by` varchar(32) DEFAULT NULL COMMENT '修改人的id',
  `created_time` datetime DEFAULT NULL COMMENT '创建时间',
  `created_by` varchar(32) DEFAULT NULL COMMENT '创建人的id',
  `deleted` tinyint(1) NOT NULL DEFAULT '0' COMMENT '逻辑删除 1已删除 0未删除',
  `version` int NOT NULL DEFAULT '1' COMMENT '数据版本',
  `super_admin` tinyint(1) DEFAULT '0',
  `clerk_id` varchar(100) DEFAULT NULL,
  PRIMARY KEY (`user_id`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci ROW_FORMAT=DYNAMIC COMMENT='用户表';
/*!40101 SET character_set_client = @saved_cs_client */;

--
-- Table structure for table `sys_user_role`
--

DROP TABLE IF EXISTS `sys_user_role`;
/*!40101 SET @saved_cs_client     = @@character_set_client */;
/*!50503 SET character_set_client = utf8mb4 */;
CREATE TABLE `sys_user_role` (
  `user_role_id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键ID',
  `user_id` varchar(32) NOT NULL COMMENT '用户ID',
  `role_id` bigint NOT NULL COMMENT '角色ID',
  `tenant_id` varchar(32) DEFAULT NULL COMMENT '租户id',
  `updated_time` datetime DEFAULT NULL COMMENT '修改时间',
  `updated_by` varchar(32) DEFAULT NULL COMMENT '修改人的id',
  `created_time` datetime DEFAULT NULL COMMENT '创建时间',
  `created_by` varchar(32) DEFAULT NULL COMMENT '创建人的id',
  `deleted` tinyint(1) NOT NULL DEFAULT '0' COMMENT '逻辑删除 1已删除 0未删除',
  `version` int NOT NULL DEFAULT '1' COMMENT '数据版本',
  PRIMARY KEY (`user_role_id`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=100 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci ROW_FORMAT=DYNAMIC COMMENT='用户角色关联表';
/*!40101 SET character_set_client = @saved_cs_client */;
/*!40103 SET TIME_ZONE=@OLD_TIME_ZONE */;

/*!40101 SET SQL_MODE=@OLD_SQL_MODE */;
/*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */;
/*!40014 SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS */;
/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */;
/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */;
/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */;
/*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */;

-- Dump completed on 2025-04-24 23:27:24


--- File: play-admin/src/main/resources/ip2region/ip2region.db ---
[Binary file: content not included]

--- File: play-admin/src/main/resources/logback-spring.xml ---
<configuration scan="true" scanPeriod="10 seconds">

    <contextName>logback</contextName>
    <!-- name的值是变量的名称，value的值时变量定义的值。通过定义的值会被插入到logger上下文中。定义变量后，可以使“${}”来使用变量。 -->
    <property name="log.path" value="./log"/>
    <property name="version" value="1.0"/>

    <!-- 彩色日志 -->
    <!-- 彩色日志依赖的渲染类 -->
    <conversionRule conversionWord="clr" converterClass="org.springframework.boot.logging.logback.ColorConverter"/>
    <conversionRule conversionWord="wex"
                    converterClass="org.springframework.boot.logging.logback.WhitespaceThrowableProxyConverter"/>
    <conversionRule conversionWord="wEx"
                    converterClass="org.springframework.boot.logging.logback.ExtendedWhitespaceThrowableProxyConverter"/>
    <!-- 彩色日志格式 -->
    <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}}"/>

    <!--输出到控制台-->
    <appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
        <!--此日志appender是为开发使用，只配置最底级别，控制台输出的日志级别是大于或等于此级别的日志信息-->
        <filter class="ch.qos.logback.classic.filter.ThresholdFilter">
            <level>info</level>
        </filter>
        <encoder>
            <Pattern>${CONSOLE_LOG_PATTERN}</Pattern>
            <!-- 设置字符集 -->
            <charset>UTF-8</charset>
        </encoder>
    </appender>

    <!--  detail info 文件保存  -->
    <appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <file>${log.path}/detail.log</file>
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <fileNamePattern>${log.path}/detail/detail-%d{yyyy-MM-dd}.%i.log</fileNamePattern>
            <timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
                <maxFileSize>100MB</maxFileSize>
            </timeBasedFileNamingAndTriggeringPolicy>
            <maxHistory>7</maxHistory>
        </rollingPolicy>
        <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
            <pattern>%d{MM/dd/yyyy HH:mm:ss} %-5level [%thread]%logger{16} - %msg%n</pattern>
            <charset>UTF-8</charset> <!-- 设置字符集 -->
        </encoder>
    </appender>

    <!-- 时间滚动输出 level为 ERROR 日志 -->
    <appender name="ERROR_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <!-- 正在记录的日志文件的路径及文件名 -->
        <file>${log.path}/error.log</file>
        <!--日志文件输出格式-->
        <encoder>
            <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern>
            <charset>UTF-8</charset> <!-- 此处设置字符集 -->
        </encoder>
        <!-- 日志记录器的滚动策略，按日期，按大小记录 -->
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <fileNamePattern>${log.path}/error/error-%d{yyyy-MM-dd}.%i.log</fileNamePattern>
            <timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
                <maxFileSize>100MB</maxFileSize>
            </timeBasedFileNamingAndTriggeringPolicy>
            <!--日志文件保留天数-->
            <maxHistory>10</maxHistory>
        </rollingPolicy>
        <!-- 此日志文件只记录ERROR级别的 -->
        <filter class="ch.qos.logback.classic.filter.LevelFilter">
            <level>ERROR</level>
            <onMatch>ACCEPT</onMatch>
            <onMismatch>DENY</onMismatch>
        </filter>
    </appender>

    <root level="info">
        <appender-ref ref="CONSOLE"/>
        <appender-ref ref="FILE"/>
        <appender-ref ref="ERROR_FILE"/>
    </root>

    <!-- Set all application loggers to DEBUG -->
    <logger name="com.starry" level="debug"/>
    <logger name="com.ctrod" level="info"/>
    
    <!-- Spring framework debug logging for requests -->
    <logger name="org.springframework.web.servlet.DispatcherServlet" level="debug"/>
    <logger name="org.springframework.security" level="debug"/>
    
    <!-- Database query logging -->
    <logger name="org.hibernate.SQL" level="debug"/>
    <logger name="org.hibernate.type.descriptor.sql.BasicBinder" level="trace"/>
    <logger name="com.baomidou.mybatisplus" level="debug"/>

</configuration>


--- File: play-admin/src/main/resources/mapper/commodity/CommodityInfoMapper.xml ---
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.starry.admin.modules.commodity.mapper.CommodityInfoMapper">
</mapper>

--- File: play-admin/src/main/resources/mapper/coupon/CouponInfoMapper.xml ---
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.starry.admin.modules.coupon.mapper.CouponInfoMapper">
</mapper>

--- File: play-admin/src/main/resources/mapper/platform/SysTenantMapper.xml ---
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.starry.admin.modules.system.mapper.SysTenantMapper">
    <resultMap type="com.starry.admin.modules.system.module.entity.SysTenantEntity" id="SysTenantResult">
        <result property="tenantId" column="tenant_id"/>
        <result property="tenantType" column="tenant_type"/>
        <result property="tenantStatus" column="tenant_status"/>
        <result property="tenantCode" column="tenant_code"/>
        <result property="tenantName" column="tenant_name"/>
        <result property="tenantLogo" column="tenant_logo"/>
        <result property="sortOrder" column="sort_order"/>
        <result property="homeUrl" column="home_url"/>
        <result property="phone" column="phone"/>
        <result property="fax" column="fax"/>
        <result property="email" column="email"/>
        <result property="address" column="address"/>
        <result property="userName" column="user_name"/>
        <result property="userPwd" column="user_pwd"/>
        <result property="packageId" column="package_id"/>
        <result property="tenantTime" column="tenant_time"/>
        <result property="remarks" column="remarks"/>
        <result property="updatedTime" column="updated_time"/>
        <result property="updatedBy" column="updated_by"/>
        <result property="createdTime" column="created_time"/>
        <result property="createdBy" column="created_by"/>
        <result property="deleted" column="deleted"/>
        <result property="version" column="version"/>
    </resultMap>

    <sql id="selectSysTenantVo">
        select tenant_id,
               tenant_type,
               tenant_status,
               tenant_code,
               tenant_name,
               tenant_logo,
               sort_order,
               home_url,
               phone,
               fax,
               email,
               address,
               user_name,
               user_pwd,
               package_id,
               tenant_time,
               remarks,
               updated_time,
               updated_by,
               created_time,
               created_by,
               deleted,
               version
        from sys_tenant
    </sql>

    <select id="selectSysTenantList" resultType="com.starry.admin.modules.system.vo.TenantResultVo">
        <include refid="selectSysTenantVo"/>
        <where>
            deleted = 0
        </where>
        <if test="vo.tenantName != null and vo.tenantName != ''">
            AND tenant_name like concat('%', #{vo.tenantName}, '%')
        </if>
        <if test="vo.tenantStatus != null and vo.tenantStatus != ''">
            AND tenant_status = #{vo.tenantStatus}
        </if>
        <if test="vo.beginTime != null and vo.beginTime != ''">
            <!-- 开始时间检索 -->
            AND date_format(tenant_time,'%y%m%d') &gt;= date_format(#{vo.beginTime},'%y%m%d')
        </if>
        <if test="vo.endTime != null and vo.endTime != ''">
            <!-- 结束时间检索 -->
            AND date_format(tenant_time,'%y%m%d') &lt;= date_format(#{vo.endTime},'%y%m%d')
        </if>
    </select>

    <select id="selectSysTenantByTenantId" parameterType="String" resultMap="SysTenantResult">
        <include refid="selectSysTenantVo"/>
        where tenant_id = #{tenantId}
    </select>

    <delete id="deleteTenantByIds" parameterType="String">
        delete from sys_tenant where tenant_id in
        <foreach item="id" collection="array" open="(" separator="," close=")">
            #{id}
        </foreach>
    </delete>
</mapper>

--- File: play-admin/src/main/resources/mapper/platform/SysTenantPackageMapper.xml ---
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.starry.admin.modules.system.mapper.SysTenantPackageMapper">
    <resultMap type="com.starry.admin.modules.system.module.entity.SysTenantPackageEntity" id="SysTenantPackageResult">
        <result property="packageId" column="package_id"/>
        <result property="packageName" column="package_name"/>
        <result property="menuIds" column="menu_ids"/>
        <result property="status" column="status"/>
        <result property="remarks" column="remarks"/>
        <result property="updatedTime" column="updated_time"/>
        <result property="updatedBy" column="updated_by"/>
        <result property="createdTime" column="created_time"/>
        <result property="createdBy" column="created_by"/>
        <result property="deleted" column="deleted"/>
        <result property="version" column="version"/>
    </resultMap>

    <sql id="selectSysTenantPackageVo">
        select package_id,
               package_name,
               menu_ids,
               status,
               remarks,
               updated_time,
               updated_by,
               created_time,
               created_by,
               deleted
        from sys_tenant_package
    </sql>

    <select id="selectSysTenantPackageList" resultMap="SysTenantPackageResult">
        <include refid="selectSysTenantPackageVo"/>
        <where>
            deleted = 0
        </where>
        <if test="vo.packageName != null and vo.packageName != ''">
            AND package_name like concat('%', #{vo.packageName}, '%')
        </if>
        <if test="vo.status != null and vo.status != ''">
            AND status = #{vo.status}
        </if>
    </select>

    <select id="selectSysTenantPackageByPackageId" parameterType="String" resultMap="SysTenantPackageResult">
        <include refid="selectSysTenantPackageVo"/>
        where package_id = #{packageId}
    </select>

    <select id="getSimpleList" resultType="com.starry.admin.modules.system.vo.SimplePackage">
        select package_id as id, package_name as name
        from sys_tenant_package
        where deleted = 0
          and status = '0'
    </select>
</mapper>

--- File: play-admin/src/main/resources/mapper/play/PlayResourcesInfoMapper.xml ---
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.starry.admin.modules.play.mapper.PlayResourcesInfoMapper">
</mapper>

--- File: play-admin/src/main/resources/mapper/play/PlayUserInfoMapper.xml ---
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.starry.admin.modules.play.mapper.PlayUserInfoMapper">
</mapper>

--- File: play-admin/src/main/resources/mapper/system/SysDeptMapper.xml ---
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.starry.admin.modules.system.mapper.SysDeptMapper">
    <!-- 通用查询映射结果 -->
    <resultMap id="BaseResultMap" type="com.starry.admin.modules.system.module.entity.SysDeptEntity">
        <id column="dept_id" property="deptId"/>
        <result column="parent_id" property="parentId"/>
        <result column="ancestors" property="ancestors"/>
        <result column="dept_name" property="deptName"/>
        <result column="sort" property="sort"/>
        <result column="leader" property="leader"/>
        <result column="phone" property="phone"/>
        <result column="email" property="email"/>
        <result column="status" property="status"/>
        <result column="dept_level" property="deptLevel"/>
        <result column="updated_time" property="updatedTime"/>
        <result column="updated_by" property="updatedBy"/>
        <result column="created_time" property="createdTime"/>
        <result column="created_by" property="createdBy"/>
        <result column="deleted" property="deleted"/>
        <result column="version" property="version"/>
    </resultMap>

    <sql id="selectDeptVo">
        select d.dept_id,
               d.parent_id,
               d.ancestors,
               d.dept_name,
               d.sort,
               d.leader,
               d.phone,
               d.email,
               d.status,
               d.dept_level,
               d.created_by,
               d.created_time
        from sys_dept d
    </sql>

    <select id="selectDeptList" resultMap="BaseResultMap">
        <include refid="selectDeptVo"/>
        where d.deleted = 0
        <if test="dept.deptId != null and dept.deptId != 0">
            AND d.dept_id = #{dept.deptId}
        </if>
        <if test="dept.parentId != null and dept.parentId != 0">
            AND d.parent_id = #{dept.parentId}
        </if>
        <if test="dept.deptName != null and dept.deptName != ''">
            AND d.dept_name like concat('%', #{dept.deptName}, '%')
        </if>
        <if test="dept.status != null and dept.status != ''">
            AND d.status = #{dept.status}
        </if>
        <!-- 数据范围过滤 -->
        ${dept.params.dataScope}
        order by d.parent_id, d.sort
    </select>

    <select id="selectChildrenDeptById" resultMap="BaseResultMap">
        select * from sys_dept where deleted = 0 and find_in_set(#{deptId}, ancestors)
    </select>

    <delete id="deleteDeptByTenantId">
        delete from sys_dept where tenant_id in
        <foreach item="id" collection="array" open="(" separator="," close=")">
            #{id}
        </foreach>
    </delete>

    <select id="selectDeptListByRoleId" resultType="Long">
        select d.dept_id
        from sys_dept d
        left join sys_role_dept rd on d.dept_id = rd.dept_id
        where rd.role_id = #{roleId}
        <if test="deptCheckStrictly">
            and d.dept_id not in (select d.parent_id from sys_dept d inner join sys_role_dept rd on d.dept_id =
            rd.dept_id and rd.role_id = #{roleId})
        </if>
        order by d.parent_id, d.sort
    </select>
</mapper>


--- File: play-admin/src/main/resources/mapper/system/SysDictDataMapper.xml ---
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.starry.admin.modules.system.mapper.SysDictDataMapper">
    <resultMap type="com.starry.admin.modules.system.module.entity.SysDictDataEntity" id="XlDictDataResult">
        <result property="dictDataId" column="dict_data_id"/>
        <result property="dictType" column="dict_type"/>
        <result property="dictLabel" column="dict_label"/>
        <result property="dictValue" column="dict_value"/>
        <result property="sort" column="sort"/>
        <result property="isDefault" column="is_default"/>
        <result property="listClass" column="list_class"/>
        <result property="status" column="status"/>
        <result property="remark" column="remark"/>
        <result property="updatedTime" column="updated_time"/>
        <result property="updatedBy" column="updated_by"/>
        <result property="createdTime" column="created_time"/>
        <result property="createdBy" column="created_by"/>
        <result property="deleted" column="deleted"/>
        <result property="version" column="version"/>
    </resultMap>

    <sql id="selectXlDictDataVo">
        select dict_data_id,
               dict_type,
               dict_label,
               dict_value,
               sort,
               is_default,
               list_class,
               status,
               remark,
               updated_time,
               updated_by,
               created_time,
               created_by,
               deleted
        from sys_dict_data
    </sql>

    <select id="selectXlDictDataList" resultMap="XlDictDataResult"
            parameterType="com.starry.admin.modules.system.module.entity.SysDictDataEntity">
        <include refid="selectXlDictDataVo"/>
        <where>
            <if test="vo.dictType != null and vo.dictType != ''">
                AND dict_type = #{vo.dictType}
            </if>
            <if test="vo.dictLabel != null and vo.dictLabel != ''">
                AND dict_label like concat('%', #{vo.dictLabel}, '%')
            </if>
            <if test="vo.status != null and vo.status != ''">
                AND status = #{vo.status}
            </if>
        </where>
    </select>

    <select id="selectXlDictDataByDictDataId" parameterType="Long" resultMap="XlDictDataResult">
        <include refid="selectXlDictDataVo"/>
        where dict_data_id = #{dictDataId}
    </select>

    <select id="selectDictDataByType" resultMap="XlDictDataResult">
        <include refid="selectXlDictDataVo"/>
        where deleted = 0 and status = 0 and dict_type = #{dictType} order by sort asc
    </select>
</mapper>

--- File: play-admin/src/main/resources/mapper/system/SysDictMapper.xml ---
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.starry.admin.modules.system.mapper.SysDictMapper">
    <resultMap type="com.starry.admin.modules.system.module.entity.SysDictEntity" id="XlDictResult">
        <result property="dictId" column="dict_id"/>
        <result property="dictType" column="dict_type"/>
        <result property="dictName" column="dict_name"/>
        <result property="status" column="status"/>
        <result property="remark" column="remark"/>
        <result property="updatedTime" column="updated_time"/>
        <result property="updatedBy" column="updated_by"/>
        <result property="createdTime" column="created_time"/>
        <result property="createdBy" column="created_by"/>
        <result property="deleted" column="deleted"/>
        <result property="version" column="version"/>
    </resultMap>

    <sql id="selectXlDictVo">
        select dict_id,
               dict_type,
               dict_name,
               status,
               remark,
               updated_time,
               updated_by,
               created_time,
               created_by,
               deleted
        from sys_dict
    </sql>

    <select id="selectXlDictList" resultMap="XlDictResult">
        <include refid="selectXlDictVo"/>
        where deleted = 0
        <if test="sysDictVo.dictName != null and sysDictVo.dictName != ''">
            AND dict_name like concat('%', #{sysDictVo.dictName}, '%')
        </if>
        <if test="sysDictVo.dictType != null and sysDictVo.dictType != ''">
            AND dict_type = #{sysDictVo.dictType}
        </if>
        <if test="sysDictVo.status != null and sysDictVo.status != ''">
            AND status = #{sysDictVo.status}
        </if>
        <if test="sysDictVo.beginTime != null and sysDictVo.beginTime != ''">
            <!-- 开始时间检索 -->
            AND date_format(created_time, '%y%m%d') &gt;= date_format(#{sysDictVo.beginTime}, '%y%m%d')
        </if>
        <if test="sysDictVo.endTime != null and sysDictVo.endTime != ''">
            <!-- 结束时间检索 -->
            AND date_format(created_time, '%y%m%d') &lt;= date_format(#{sysDictVo.endTime}, '%y%m%d')
        </if>
    </select>

    <select id="selectXlDictByDictId" parameterType="Long" resultMap="XlDictResult">
        <include refid="selectXlDictVo"/>
        where dict_id = #{dictId}
    </select>
</mapper>

--- File: play-admin/src/main/resources/mapper/system/SysLoginLogMapper.xml ---
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.starry.admin.modules.system.mapper.SysLoginLogMapper">
    <resultMap type="com.starry.admin.modules.system.module.entity.SysLoginLogEntity" id="XlLoginLogResult">
        <result property="loginId" column="login_id"/>
        <result property="userCode" column="user_code"/>
        <result property="ipaddr" column="ipaddr"/>
        <result property="loginLocation" column="login_location"/>
        <result property="browser" column="browser"/>
        <result property="os" column="os"/>
        <result property="msg" column="msg"/>
        <result property="status" column="status"/>
        <result property="loginTime" column="login_time"/>
        <result property="updatedTime" column="updated_time"/>
        <result property="updatedBy" column="updated_by"/>
        <result property="createdTime" column="created_time"/>
        <result property="createdBy" column="created_by"/>
        <result property="deleted" column="deleted"/>
        <result property="version" column="version"/>
    </resultMap>

    <sql id="selectXlLoginLogVo">
        select login_id,
               user_code,
               ipaddr,
               login_location,
               browser,
               os,
               msg,
               status,
               login_time,
               updated_time,
               updated_by,
               created_time,
               created_by,
               deleted
        from sys_login_log
    </sql>

    <select id="selectXlLoginLogList" resultMap="XlLoginLogResult">
        <include refid="selectXlLoginLogVo"/>
        <where>
            deleted = 0
        </where>
        <if test="vo.ipaddr != null and vo.ipaddr != ''">
            AND ipaddr = #{vo.ipaddr}
        </if>
        <if test="vo.beginTime != null and vo.beginTime != ''">
            <!-- 开始时间检索 -->
            AND date_format(login_time,'%y%m%d') &gt;= date_format(#{vo.beginTime},'%y%m%d')
        </if>
        <if test="vo.endTime != null and vo.endTime != ''">
            <!-- 结束时间检索 -->
            AND date_format(login_time,'%y%m%d') &lt;= date_format(#{vo.endTime},'%y%m%d')
        </if>
        order by login_id desc
    </select>

    <select id="selectXlLoginLogByLoginId" parameterType="Long" resultMap="XlLoginLogResult">
        <include refid="selectXlLoginLogVo"/>
        where login_id = #{loginId}
    </select>

    <update id="cleanLoginlog">
        truncate table sys_login_log
    </update>
</mapper>

--- File: play-admin/src/main/resources/mapper/system/SysMenuMapper.xml ---
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.starry.admin.modules.system.mapper.SysMenuMapper">
    <!-- 通用查询映射结果 -->
    <resultMap id="BaseResultMap" type="com.starry.admin.modules.system.module.entity.SysMenuEntity">
        <id column="menu_id" property="menuId"/>
        <result column="menu_name" property="menuName"/>
        <result column="menu_code" property="menuCode"/>
        <result column="icon" property="icon"/>
        <result column="menu_level" property="menuLevel"/>
        <result column="permission" property="permission"/>
        <result column="sort" property="sort"/>
        <result column="parent_id" property="parentId"/>
        <result column="menu_type" property="menuType"/>
        <result column="status" property="status"/>
        <result column="remark" property="remark"/>
        <result column="path" property="path"/>
        <result column="component" property="component"/>
        <result column="router_query" property="routerQuery"/>
        <result column="is_frame" property="isFrame"/>
        <result column="visible" property="visible"/>
        <result column="updated_time" property="updatedTime"/>
        <result column="updated_by" property="updatedBy"/>
        <result column="created_time" property="createdTime"/>
        <result column="created_by" property="createdBy"/>
        <result column="deleted" property="deleted"/>
        <result column="version" property="version"/>
    </resultMap>

    <select id="selectMenuList" resultType="com.starry.admin.modules.system.module.entity.SysMenuEntity">
        select distinct m.menu_id, m.parent_id, m.menu_name, m.path, m.component, m.router_query,m.permission,
        m.visible, m.status, m.menu_code, m.is_frame, m.menu_type, m.icon, m.sort, m.created_time
        from sys_menu m
        <where>
            <if test="menuName != null and menuName != ''">
                AND m.menu_name like concat('%', #{menuName}, '%')
            </if>
            <if test="visible != null and visible != ''">
                AND m.visible = #{visible}
            </if>
            <if test="status != null and status != ''">
                AND m.status = #{status}
            </if>
        </where>
        order by m.parent_id, m.sort
    </select>

    <select id="selectMenuListByUserId" resultType="com.starry.admin.modules.system.module.entity.SysMenuEntity">
        select distinct m.menu_id, m.parent_id, m.menu_name, m.path, m.component, m.router_query,m.permission,
        m.visible, m.status, m.menu_code, m.is_frame, m.menu_type, m.icon, m.sort, m.created_time
        from sys_menu m
        left join sys_role_menu rm on m.menu_id = rm.menu_id
        left join sys_user_role ur on rm.role_id = ur.role_id
        left join sys_role ro on ur.role_id = ro.role_id
        where ur.user_id = #{userId}
        <if test="menu.menuName != null and menu.menuName != ''">
            AND m.menu_name like concat('%', #{menu.menuName}, '%')
        </if>
        <if test="menu.visible != null and menu.visible != ''">
            AND m.visible = #{menu.visible}
        </if>
        <if test="menu.status != null and menu.status != ''">
            AND m.status = #{menu.status}
        </if>
        order by m.parent_id, m.sort
    </select>

    <select id="selectMenuListByRoleId" resultType="Long">
        select m.menu_id
        from sys_menu m
        left join sys_role_menu rm on m.menu_id = rm.menu_id
        where m.deleted = 0
        and rm.deleted = 0
        and rm.role_id = #{roleId}
        <if test="menuCheckStrictly">
            and m.menu_id not in (select m.parent_id from sys_menu m inner join sys_role_menu rm on m.menu_id =
            rm.menu_id and rm.role_id = #{roleId})
        </if>
        order by m.parent_id, m.sort
    </select>

    <select id="selectMenuPermsByUserId" resultType="String">
        select distinct m.permission
        from sys_menu m
        left join sys_role_menu rm on m.menu_id = rm.menu_id
        left join sys_user_role ur on rm.role_id = ur.role_id
        left join sys_role r on r.role_id = ur.role_id
        where m.deleted = 0 and rm.deleted = 0 and ur.deleted = 0 and r.deleted = 0
        and m.status = 0 and r.status = 0 and ur.user_id = #{userId}
    </select>

    <select id="selectMenuTreeAll" resultMap="BaseResultMap">
        select distinct m.menu_id,
                        m.parent_id,
                        m.menu_name,
                        m.path,
                        m.component,
                        m.router_query,
                        m.visible,
                        m.status,
                        ifnull(m.permission, '') as permission,
                        m.is_frame,
                        m.menu_type,
                        m.icon,
                        m.sort
        from sys_menu m
        where m.deleted = 0
          and m.menu_type in (0, 1)
          and m.status = 0
        order by m.parent_id, m.sort
    </select>

    <select id="selectMenuTreeByUserId" resultMap="BaseResultMap">
        select distinct m.menu_id, m.parent_id, m.menu_name, m.path, m.component, m.router_query, m.visible, m.status,
        ifnull(m.permission,'') as permission, m.is_frame, m.menu_type, m.icon, m.sort
        from sys_menu m
        left join sys_role_menu rm on m.menu_id = rm.menu_id
        left join sys_user_role ur on rm.role_id = ur.role_id
        left join sys_role r on ur.role_id = r.role_id
        left join sys_user u on ur.user_id = u.user_id
        where m.deleted = 0 and rm.deleted = 0 and ur.deleted = 0 and r.deleted = 0 and r.deleted = 0 and u.deleted = 0
        and u.user_id = #{userId} and m.menu_type in (0, 1) and m.status = 0 AND r.status = 0
        order by m.parent_id, m.sort
    </select>

    <select id="selectSimpleMenuList" resultType="com.starry.admin.modules.system.vo.SimpleMenu">
        select menu_id as id, menu_name as name, parent_id as parentId, menu_type as type
        from sys_menu
        where status = 0
    </select>
</mapper>


--- File: play-admin/src/main/resources/mapper/system/SysOperationLogMapper.xml ---
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.starry.admin.modules.system.mapper.SysOperationLogMapper">
    <resultMap type="com.starry.admin.modules.system.module.entity.SysOperationLogEntity" id="SysOperLogResult">
        <result property="operId" column="oper_id"/>
        <result property="title" column="title"/>
        <result property="businessType" column="business_type"/>
        <result property="method" column="method"/>
        <result property="requestMethod" column="request_method"/>
        <result property="operatorType" column="operator_type"/>
        <result property="operName" column="oper_name"/>
        <result property="operUrl" column="oper_url"/>
        <result property="operIp" column="oper_ip"/>
        <result property="operLocation" column="oper_location"/>
        <result property="operParam" column="oper_param"/>
        <result property="jsonResult" column="json_result"/>
        <result property="status" column="status"/>
        <result property="errorMsg" column="error_msg"/>
        <result property="operTime" column="oper_time"/>
        <result property="deptName" column="dept_name"/>
        <result property="deptId" column="dept_id"/>
        <result property="updatedTime" column="updated_time"/>
        <result property="updatedBy" column="updated_by"/>
        <result property="createdTime" column="created_time"/>
        <result property="createdBy" column="created_by"/>
        <result property="deleted" column="deleted"/>
    </resultMap>

    <sql id="selectXlOperLogVo">
        select oper_id,
               title,
               business_type,
               method,
               request_method,
               operator_type,
               oper_name,
               oper_url,
               oper_ip,
               oper_location,
               oper_param,
               json_result,
               status,
               error_msg,
               oper_time,
               dept_name,
               dept_id,
               updated_time,
               updated_by,
               created_time,
               created_by,
               deleted
        from sys_operation_log
    </sql>

    <select id="selectXlOperLogList" resultMap="SysOperLogResult">
        <include refid="selectXlOperLogVo"/>
        <where>
            deleted = 0
        </where>
        <if test="vo.title != null and vo.title != ''">
            AND title like concat('%', #{vo.title}, '%')
        </if>
        <if test="vo.businessType != null">
            AND business_type = #{vo.businessType}
        </if>
        <if test="vo.businessTypes != null and vo.businessTypes.length > 0">
            AND business_type in
            <foreach collection="vo.businessTypes" item="businessType" open="(" separator="," close=")">
                #{businessType}
            </foreach>
        </if>
        <if test="vo.operName != null and vo.operName != ''">
            AND oper_name like concat('%', #{vo.operName}, '%')
        </if>
        <if test="vo.beginTime != null and vo.beginTime != ''">
            and date_format(oper_time,'%y%m%d') &gt;= date_format(#{vo.beginTime},'%y%m%d')
        </if>
        <if test="vo.endTime != null and vo.endTime != ''">
            and date_format(oper_time,'%y%m%d') &lt;= date_format(#{vo.endTime},'%y%m%d')
        </if>
    </select>

    <select id="selectXlOperLogByOperId" parameterType="Long" resultMap="SysOperLogResult">
    </select>
</mapper>

--- File: play-admin/src/main/resources/mapper/system/SysRoleDeptMapper.xml ---
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.starry.admin.modules.system.mapper.SysRoleDeptMapper">
    <!-- 通用查询映射结果 -->
    <resultMap id="BaseResultMap" type="com.starry.admin.modules.system.module.entity.SysRoleDeptEntity">
        <id column="role_dept_id" property="roleDeptId"/>
        <result column="role_id" property="roleId"/>
        <result column="dept_id" property="deptId"/>
        <result column="tenant_id" property="tenantId"/>
        <result column="updated_time" property="updatedTime"/>
        <result column="updated_by" property="updatedBy"/>
        <result column="created_time" property="createdTime"/>
        <result column="created_by" property="createdBy"/>
        <result column="deleted" property="deleted"/>
        <result column="version" property="version"/>
    </resultMap>

    <delete id="deleteRoleDeptByTenantId">
        delete from sys_role_dept where tenant_id in
        <foreach item="id" collection="array" open="(" separator="," close=")">
            #{id}
        </foreach>
    </delete>

    <delete id="deleteRoleDeptByRoleId" parameterType="Long">
        delete from sys_role_dept where role_id = #{roleId}
    </delete>

    <insert id="batchRoleDept">
        insert into sys_role_dept(role_id, dept_id) values
        <foreach item="item" index="index" collection="list" separator=",">
            (#{item.roleId},#{item.deptId})
        </foreach>
    </insert>

    <delete id="deleteRoleDept" parameterType="Long">
        delete from sys_role_dept where role_id in
        <foreach collection="array" item="roleId" open="(" separator="," close=")">
            #{roleId}
        </foreach>
    </delete>
</mapper>


--- File: play-admin/src/main/resources/mapper/system/SysRoleMapper.xml ---
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.starry.admin.modules.system.mapper.SysRoleMapper">
    <!-- 通用查询映射结果 -->
    <resultMap id="BaseResultMap" type="com.starry.admin.modules.system.module.entity.SysRoleEntity">
        <id column="role_id" property="roleId"/>
        <result column="role_name" property="roleName"/>
        <result column="description" property="description"/>
        <result column="data_scope" property="dataScope"/>
        <result column="menu_check_strictly" property="menuCheckStrictly"/>
        <result column="dept_check_strictly" property="deptCheckStrictly"/>
        <result column="status" property="status"/>
        <result column="tenant_id" property="tenantId"/>
        <result column="updated_time" property="updatedTime"/>
        <result column="updated_by" property="updatedBy"/>
        <result column="create_time" property="createdTime"/>
        <result column="created_by" property="createdBy"/>
        <result column="deleted" property="deleted"/>
        <result column="version" property="version"/>
    </resultMap>

    <sql id="selectRoleVo">
        select distinct r.role_id,
                        r.role_name,
                        r.role_key,
                        r.data_scope,
                        r.menu_check_strictly,
                        r.dept_check_strictly,
                        r.status,
                        r.created_time,
                        r.description
        from sys_role r
                 left join sys_user_role ur on ur.role_id = r.role_id and ur.deleted = 0
                 left join sys_user u on u.user_id = ur.user_id and u.deleted = 0
    </sql>

    <select id="selectRoleById" parameterType="Long" resultMap="BaseResultMap">
        <include refid="selectRoleVo"/>
        where r.deleted = 0
        and r.role_id = #{roleId}
    </select>

    <select id="checkRoleNameUnique" parameterType="String" resultMap="BaseResultMap">
        <include refid="selectRoleVo"/>
        where r.role_name=#{roleName} and r.deleted = 0 limit 1
    </select>

    <select id="checkRoleKeyUnique" parameterType="String" resultMap="BaseResultMap">
        <include refid="selectRoleVo"/>
        where r.role_key=#{roleKey} and r.deleted = 0 limit 1
    </select>

    <select id="selectRolePermissionByUserId" resultMap="BaseResultMap">
        <include refid="selectRoleVo"/>
        where r.deleted = 0
        and ur.user_id = #{userId}
    </select>

    <select id="selectRoleList" resultMap="BaseResultMap">
        <include refid="selectRoleVo"/>
        where r.deleted = 0
    </select>

    <select id="queryAdminRole" parameterType="String" resultMap="BaseResultMap">
        select distinct r.role_id, r.role_name, r.role_key, r.data_scope, r.menu_check_strictly, r.dept_check_strictly,
        r.status, r.description,tenant_id
        from sys_role r
        where r.tenant_id = #{tenantId} and r.role_key = 'admin' order by role_id desc
    </select>

    <delete id="deleteRoleByTenantId">
        delete from sys_role where tenant_id in
        <foreach item="id" collection="array" open="(" separator="," close=")">
            #{id}
        </foreach>
    </delete>

    <select id="countUserRoleByRoleId" resultType="Integer">
        select count(1) from sys_user_role where role_id=#{roleId}
    </select>
</mapper>


--- File: play-admin/src/main/resources/mapper/system/SysRoleMenuMapper.xml ---
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.starry.admin.modules.system.mapper.SysRoleMenuMapper">
    <!-- 通用查询映射结果 -->
    <resultMap id="BaseResultMap" type="com.starry.admin.modules.system.module.entity.SysRoleMenuEntity">
        <id column="role_menu_id" property="roleMenuId"/>
        <result column="role_id" property="roleId"/>
        <result column="menu_id" property="menuId"/>
        <result column="tenant_id" property="tenantId"/>
        <result column="updated_time" property="updatedTime"/>
        <result column="updated_by" property="updatedBy"/>
        <result column="created_time" property="createdTime"/>
        <result column="created_by" property="createdBy"/>
        <result column="deleted" property="deleted"/>
        <result column="version" property="version"/>
    </resultMap>

    <delete id="deleteRoleMenuByTenantId" parameterType="String">
        delete from sys_role_menu where tenant_id = #{tenantId}
    </delete>

    <delete id="deleteRoleMenuByTenantIds">
        delete from sys_role_menu where tenant_id in
        <foreach item="id" collection="array" open="(" separator="," close=")">
            #{id}
        </foreach>
    </delete>

    <delete id="deleteRoleMenu" parameterType="Long">
        delete from sys_role_menu where role_id in
        <foreach collection="array" item="roleId" open="(" separator="," close=")">
            #{roleId}
        </foreach>
    </delete>
</mapper>


--- File: play-admin/src/main/resources/mapper/system/SysUserMapper.xml ---
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.starry.admin.modules.system.mapper.SysUserMapper">
    <!-- 通用查询映射结果 -->
    <resultMap id="BaseResultMap" type="com.starry.admin.modules.system.module.entity.SysUserEntity">
        <id column="user_id" property="userId"/>
        <result column="user_code" property="userCode"/>
        <result column="pass_word" property="passWord"/>
        <result column="real_name" property="realName"/>
        <result column="user_nickname" property="userNickname"/>
        <result column="user_email" property="userEmail"/>
        <result column="status" property="status"/>
        <result column="sex" property="sex"/>
        <result column="mobile" property="mobile"/>
        <result column="dept_id" property="deptId"/>
        <result column="tenant_id" property="tenantId"/>
        <result column="updated_time" property="updatedTime"/>
        <result column="updated_by" property="updatedBy"/>
        <result column="super_admin" property="superAdmin"/>
        <result column="created_time" property="createdTime"/>
        <result column="created_by" property="createdBy"/>
        <result column="clerk_id" property="clerkId"/>
        <result column="deleted" property="deleted"/>
        <result column="version" property="version"/>

        <association property="dept" javaType="com.starry.admin.modules.system.module.entity.SysDeptEntity">
            <id column="dept_id" property="deptId"/>
            <result column="dept_name" property="deptName"/>
            <result column="leader" property="leader"/>
        </association>

        <collection property="roles" javaType="java.util.List"
                    ofType="com.starry.admin.modules.system.module.entity.SysRoleEntity">
            <id column="role_id" property="roleId"/>
            <result column="role_name" property="roleName"/>
            <result column="role_key" property="roleKey"/>
            <result column="data_scope" property="dataScope"/>
        </collection>
    </resultMap>

    <sql id="selectUserVo">
        select u.user_id,
               u.real_name,
               u.user_nickname,
               u.user_email,
               u.avatar,
               u.user_code,
               u.pass_word,
               u.sex,
               u.mobile,
               u.status,
               u.created_time,
               u.dept_id,
               u.tenant_id,
               u.super_admin,
               d.dept_name,
               r.role_id,
               r.role_name,
               r.role_key,
               r.data_scope,
               r.status as role_status
        from sys_user u
                 left join sys_user_role ur on u.user_id = ur.user_id and ur.deleted = 0
                 left join sys_role r on r.role_id = ur.role_id and r.deleted = 0
                 left join sys_dept d on u.dept_id = d.dept_id and d.deleted = 0
    </sql>

    <select id="selectUserById" resultMap="BaseResultMap">
        <include refid="selectUserVo"/>
        where u.deleted = 0
        and u.user_id = #{userId}
    </select>

    <select id="selectUserList" resultMap="BaseResultMap">
        select u.user_id,
        u.real_name,
        u.user_nickname,
        u.user_email,
        u.avatar,
        u.user_code,
        u.pass_word,
        u.sex,
        u.mobile,
        u.status,
        u.dept_id,
        u.created_time,
        d.dept_name,
        u.super_admin,
        u.clerk_id,
        d.leader
        from sys_user u
        left join sys_dept d on u.dept_id = d.dept_id and d.deleted = 0
        where u.deleted = 0
        <if test="user.userId != null and user.userId != 0">
            AND u.user_id = #{user.userId}
        </if>
        <if test="user.userCode != null and user.userCode != ''">
            AND u.user_code like concat('%', #{user.userCode}, '%')
        </if>
        <if test="user.realName != null and user.realName != ''">
            AND u.real_name like concat('%', #{user.realName}, '%')
        </if>
    </select>

    <select id="selectUserByUserName" resultMap="BaseResultMap">
        <include refid="selectUserVo"/>
        where u.user_code = #{userName} and u.deleted = 0
    </select>

    <select id="selectUserByUserNameAndTenantId" resultMap="BaseResultMap">
        <include refid="selectUserVo"/>
        where u.user_code = #{userName} and u.deleted = 0 and u.tenant_id = #{tenantId}
    </select>

    <select id="checkUserNameUnique" parameterType="String" resultType="int">
        select count(1) from sys_user where user_code = #{userName} and deleted = 0 limit 1
    </select>

    <delete id="deleteUserByTenantId">
        delete from sys_user where tenant_id in
        <foreach item="id" collection="array" open="(" separator="," close=")">
            #{id}
        </foreach>
    </delete>

    <select id="selectAllocatedList" resultType="com.starry.admin.modules.system.vo.RoleUserResultVo">
        select distinct u.user_id, u.dept_id, u.user_code, u.real_name, u.user_email, u.mobile, u.status, u.created_time
        from sys_user u
        left join sys_dept d on u.dept_id = d.dept_id
        left join sys_user_role ur on u.user_id = ur.user_id
        left join sys_role r on r.role_id = ur.role_id
        where u.deleted = 0 and r.role_id = #{query.roleId}
        <if test="query.userCode != null and query.userCode != ''">
            AND u.user_code like concat('%', #{query.userCode}, '%')
        </if>
        <if test="query.mobile != null and query.mobile != ''">
            AND u.mobile like concat('%', #{query.mobile}, '%')
        </if>
        <!-- 数据范围过滤 -->
        ${query.dataScope}
    </select>

    <select id="selectUnallocatedList" resultType="com.starry.admin.modules.system.vo.RoleUserResultVo">
        select distinct u.user_id, u.dept_id, u.user_code, u.real_name, u.user_email, u.mobile, u.status, u.created_time
        from sys_user u
        left join sys_dept d on u.dept_id = d.dept_id
        left join sys_user_role ur on u.user_id = ur.user_id
        left join sys_role r on r.role_id = ur.role_id
        where u.deleted = 0 and (r.role_id != #{query.roleId} or r.role_id IS NULL)
        and u.user_id not in (select u.user_id from sys_user u inner join sys_user_role ur on u.user_id = ur.user_id and
        ur.role_id = #{query.roleId})
        <if test="query.userCode != null and query.userCode != ''">
            AND u.user_code like concat('%', #{query.userCode}, '%')
        </if>
        <if test="query.mobile != null and query.mobile != ''">
            AND u.mobile like concat('%', #{query.mobile}, '%')
        </if>
        <!-- 数据范围过滤 -->
        ${query.params.dataScope}
    </select>
</mapper>


--- File: play-admin/src/main/resources/mapper/system/SysUserRoleMapper.xml ---
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.starry.admin.modules.system.mapper.SysUserRoleMapper">
    <!-- 通用查询映射结果 -->
    <resultMap id="BaseResultMap" type="com.starry.admin.modules.system.module.entity.SysUserRoleEntity">
        <id column="user_role_id" property="userRoleId"/>
        <result column="role_id" property="roleId"/>
        <result column="user_id" property="userId"/>
        <result column="tenant_id" property="tenantId"/>
        <result column="updated_time" property="updatedTime"/>
        <result column="updated_by" property="updatedBy"/>
        <result column="created_time" property="createdTime"/>
        <result column="created_by" property="createdBy"/>
        <result column="deleted" property="deleted"/>
        <result column="version" property="version"/>
    </resultMap>

    <delete id="deleteUserRoleByTenantId">
        delete from sys_user_role where tenant_id in
        <foreach item="id" collection="array" open="(" separator="," close=")">
            #{id}
        </foreach>
    </delete>

    <delete id="deleteUserRoleInfo">
        delete from sys_user_role where user_id=#{userId} and role_id=#{roleId}
    </delete>

    <delete id="deleteUserRoleInfos">
        delete from sys_user_role where role_id=#{roleId} and user_id in
        <foreach collection="userIds" item="userId" open="(" separator="," close=")">
            #{userId}
        </foreach>
    </delete>
</mapper>


--- File: play-admin/src/main/resources/mapper/weichat/WxMsgMapper.xml ---
<?xml version="1.0" encoding="UTF-8"?>
<!--
  ~ Copyright (C) 2018-2019
  ~ All rights reserved, Designed By admin
  -->
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">

<mapper namespace="com.starry.weichat.mapper.WxMsgMapper">
    <select id="listWxMsgMapGroup" resultType="com.baomidou.mybatisplus.core.metadata.IPage">
        select a.*,
        b.count_msg
        from
        wx_msg as a
        right join
        (select `wx_user_id`,
        max(`create_time`) as maxtime,
        count(`wx_user_id`) as count_msg
        from wx_msg
        <where>
            <if test="query.type != null and query.type != ''">
                AND `type` = #{query.type}
            </if>
            <if test="query.readFlag != null and query.readFlag != ''">
                AND `read_flag` = #{query.readFlag}
            </if>
            <if test="query.notInRepType != null and query.notInRepType != ''">
                AND `rep_type` NOT IN (#{query.notInRepType})
            </if>
        </where>
        group by `wx_user_id`) as b
        on a.`wx_user_id` = b.`wx_user_id`
        and a.`create_time` = b.maxtime
        order by a.`create_time` desc ;
    </select>
</mapper>


--- File: play-admin/src/main/resources/oss.properties ---
aliyun.endpoint=oss-cn-hangzhou.aliyuncs.com
aliyun.accessKeyId=LTAI5t9ojt6uovFZokYZfJx5
aliyun.accessKeySecret=xfCyNM5BQlAkoaa5NbcpoeipmCKZgf
aliyun.bucketName=live-cloud-cvoon

--- File: play-admin/src/main/resources/play.properties ---
#微信公众号或者小程序等的appid
wx.play.appId:xxxxxxxxxxxxx
#微信支付商户号
wx.play.mchId:xxxxxxxxxxxxx
# 证书
wx.play.apiclient_key.pem:xxxxxxxxx
# 证书
wx.play.apiclient_cert.pem:xxxxxxxxx
# apiV3key
wx.play.apiV3key:xxxxxxxxxxxxxxxxxx
#支付回调通知地址
wx.play.returnUrl:http://xxxxxxxxxx/wxPay/callBack
#    退款回调地址
wx.play.refundUrl:http://xxxxxxxxxx/wxPay/refund


--- File: play-admin/src/main/resources/spy.properties ---
#3.2.1以上使用modulelist=com.baomidou.mybatisplus.extension.p6spy.MybatisPlusLogFactory,com.p6spy.engine.outage.P6OutageFactory
# 自定义日志打印
logMessageFormat=com.baomidou.mybatisplus.extension.p6spy.P6SpyLogger
#日志输出到控制台
appender=com.baomidou.mybatisplus.extension.p6spy.StdoutLogger
# 使用日志系统记录 sql
#appender=com.p6spy.engine.spy.appender.Slf4JLogger
# 设置 p6spy driver 代理
deregisterdrivers=true
# 取消JDBC URL前缀
useprefix=true
# 配置记录 Log 例外,可去掉的结果集error,info,batch,debug,statement,commit,rollback,result,resultset.
excludecategories=info,debug,result,commit,resultset
# 日期格式
dateformat=yyyy-MM-dd HH:mm:ss
# 实际驱动可多个
#driverlist=org.h2.Driver
# 是否开启慢SQL记录
outagedetection=true
# 慢SQL记录标准 2 秒
outagedetectioninterval=2

--- File: play-common/pom.xml ---
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xmlns="http://maven.apache.org/POM/4.0.0"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>com.starry</groupId>
        <artifactId>play-with</artifactId>
        <version>1.0</version>
    </parent>

    <artifactId>play-common</artifactId>

    <properties>
        <maven.compiler.source>11</maven.compiler.source>
        <maven.compiler.target>11</maven.compiler.target>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>

    <dependencies>
        <!--引入springboot-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <!--SpringSecurity依赖配置-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
        </dependency>

        <!--简化对象封装工具-->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.30</version>
            <scope>provided</scope>
        </dependency>
        <!-- Add lombok annotation processor -->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok-mapstruct-binding</artifactId>
            <version>0.2.0</version>
        </dependency>
        <!--mybatis-plus-->
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
        </dependency>
        <dependency>
            <groupId>com.github.yulichang</groupId>
            <artifactId>mybatis-plus-join-boot-starter</artifactId>
        </dependency>
        <dependency>
            <groupId>javax.persistence</groupId>
            <artifactId>javax.persistence-api</artifactId>
        </dependency>
        <!-- druid连接池 -->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid-spring-boot-starter</artifactId>
        </dependency>
        <!--swagger-->
        <dependency>
            <groupId>com.github.xiaoymin</groupId>
            <artifactId>knife4j-spring-boot-starter</artifactId>
        </dependency>
        <!--常用工具类 -->
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-lang3</artifactId>
        </dependency>
        <!-- io常用工具类 -->
        <dependency>
            <groupId>commons-io</groupId>
            <artifactId>commons-io</artifactId>
        </dependency>
        <!-- 获取系统信息 -->
        <dependency>
            <groupId>com.github.oshi</groupId>
            <artifactId>oshi-core</artifactId>
        </dependency>
        <!-- hutool 工具类库 -->
        <dependency>
            <groupId>cn.hutool</groupId>
            <artifactId>hutool-all</artifactId>
        </dependency>
        <!-- 自定义验证注解 -->
        <dependency>
            <groupId>javax.validation</groupId>
            <artifactId>validation-api</artifactId>
        </dependency>
        <!-- redis 缓存操作 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>
        <!-- 阿里JSON解析器 -->
        <dependency>
            <groupId>com.alibaba.fastjson2</groupId>
            <artifactId>fastjson2</artifactId>
        </dependency>
        <dependency>
            <groupId>com.github.gavlyukovskiy</groupId>
            <artifactId>p6spy-spring-boot-starter</artifactId>
        </dependency>


        <dependency>
            <groupId>com.google.code.gson</groupId>
            <artifactId>gson</artifactId>
        </dependency>


        <dependency>
            <groupId>org.lionsoul</groupId>
            <artifactId>ip2region</artifactId>
        </dependency>

        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>transmittable-thread-local</artifactId>
        </dependency>
    </dependencies>

</project>

--- File: play-common/src/main/java/com/starry/common/annotation/DataScope.java ---
package com.starry.common.annotation;

import java.lang.annotation.*;

/**
 * 数据权限过滤注解
 *
 * @author admin
 */
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface DataScope {
    /**
     * 部门表的别名
     */
    String deptAlias() default "";

    /**
     * 用户表的别名
     */
    String userAlias() default "";

    /**
     * 权限字符（用于多个角色匹配符合要求的权限）默认根据权限注解@ss获取，多个权限用逗号分隔开来
     */
    String permission() default "";
}


--- File: play-common/src/main/java/com/starry/common/annotation/EnumValue.java ---
package com.starry.common.annotation;

import static java.lang.annotation.ElementType.*;
import static java.lang.annotation.RetentionPolicy.RUNTIME;

import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import javax.validation.Constraint;
import javax.validation.Payload;

/**
 * 枚举参数校验注解
 *
 * @author admin
 */
@Target({METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER})
@Retention(RUNTIME)
@Documented
@Constraint(validatedBy = {EnumValueValidator.class})
public @interface EnumValue {

    // 默认错误消息
    String message() default "必须为指定值";

    String[] strValues() default {};

    int[] intValues() default {};

    // 分组
    Class<?>[] groups() default {};

    // 负载
    Class<? extends Payload>[] payload() default {};

    // 指定多个时使用
    @Target({FIELD, METHOD, PARAMETER, ANNOTATION_TYPE})
    @Retention(RUNTIME)
    @Documented
    @interface List {
        EnumValue[] value();
    }
}


--- File: play-common/src/main/java/com/starry/common/annotation/EnumValueValidator.java ---
package com.starry.common.annotation;

import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;

/**
 * 枚举校验注解处理类
 */
public class EnumValueValidator implements ConstraintValidator<EnumValue, Object> {

    private String[] strValues;
    private int[] intValues;

    @Override
    public void initialize(EnumValue constraintAnnotation) {
        strValues = constraintAnnotation.strValues();
        intValues = constraintAnnotation.intValues();
    }

    @Override
    public boolean isValid(Object value, ConstraintValidatorContext context) {
        if (value instanceof String) {
            for (String s : strValues) {
                if (s.equals(value)) {
                    return true;
                }
            }
        } else if (value instanceof Integer) {
            for (int s : intValues) {
                if (s == (Integer) value) {
                    return true;
                }
            }
        }
        return false;
    }
}


--- File: play-common/src/main/java/com/starry/common/annotation/Log.java ---
package com.starry.common.annotation;

import com.starry.common.enums.BusinessType;
import java.lang.annotation.*;

/**
 * @author admin 自定义操作日志记录注解
 * @since 2022/7/22
 */
@Target({ElementType.PARAMETER, ElementType.METHOD}) // 注解放置的目标位置,PARAMETER: 可用在参数上 METHOD：可用在方法级别上
@Retention(RetentionPolicy.RUNTIME) // 指明修饰的注解的生存周期 RUNTIME：运行级别保留
@Documented
public @interface Log {

    /**
     * 模块
     */
    String title() default "";

    /**
     * 功能
     */
    BusinessType businessType() default BusinessType.OTHER;

    /**
     * 是否保存请求的参数
     */
    boolean isSaveRequestData() default true;

    /**
     * 是否保存响应的参数
     */
    boolean isSaveResponseData() default true;
}


--- File: play-common/src/main/java/com/starry/common/config/FastJson2JsonRedisSerializer.java ---
package com.starry.common.config;

import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONReader;
import com.alibaba.fastjson2.JSONWriter;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.data.redis.serializer.SerializationException;

/**
 * Redis使用FastJson序列化
 *
 * @author ruoyi
 */
public class FastJson2JsonRedisSerializer<T> implements RedisSerializer<T> {
    public static final Charset DEFAULT_CHARSET = StandardCharsets.UTF_8;

    private final Class<T> clazz;

    public FastJson2JsonRedisSerializer(Class<T> clazz) {
        super();
        this.clazz = clazz;
    }

    @Override
    public byte[] serialize(T t) throws SerializationException {
        if (t == null) {
            return new byte[0];
        }
        return JSON.toJSONString(t, JSONWriter.Feature.WriteClassName).getBytes(DEFAULT_CHARSET);
    }

    @Override
    public T deserialize(byte[] bytes) throws SerializationException {
        if (bytes == null || bytes.length <= 0) {
            return null;
        }
        String str = new String(bytes, DEFAULT_CHARSET);

        return JSON.parseObject(str, clazz, JSONReader.Feature.SupportAutoType);
    }
}


--- File: play-common/src/main/java/com/starry/common/config/RedisConfig.java ---
package com.starry.common.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.StringRedisSerializer;

/**
 * @author admin redis 配置
 * @since 2022/10/18
 */

@Configuration
public class RedisConfig {

    @Bean
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory connectionFactory) {
        RedisTemplate<String, Object> template = new RedisTemplate<>();
        template.setConnectionFactory(connectionFactory);
        FastJson2JsonRedisSerializer<Object> serializer = new FastJson2JsonRedisSerializer<>(Object.class);
        // 使用StringRedisSerializer来序列化和反序列化redis的key值
        template.setKeySerializer(new StringRedisSerializer());
        template.setValueSerializer(serializer);
        // Hash的key也采用StringRedisSerializer的序列化方式
        template.setHashKeySerializer(new StringRedisSerializer());
        template.setHashValueSerializer(serializer);

        template.afterPropertiesSet();
        return template;
    }
}


--- File: play-common/src/main/java/com/starry/common/config/Swagger2Config.java ---
package com.starry.common.config;

import java.util.ArrayList;
import java.util.List;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import springfox.documentation.builders.ApiInfoBuilder;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.service.ApiKey;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger2.annotations.EnableSwagger2WebMvc;

/**
 * @author admin swagger配置
 * @since 2021/9/1
 */
@Configuration
@EnableSwagger2WebMvc
public class Swagger2Config {

    @Bean(value = "defaultApi2")
    public Docket defaultApi2() {
        Docket docket = new Docket(DocumentationType.SWAGGER_2)
                .apiInfo(new ApiInfoBuilder().title("接口文档").description("# 接口文档")
                        .termsOfServiceUrl("http://www.xx.com/").contact("277769738@qq.com").version("1.0").build())
                // 分组名称
                .groupName("2.X版本").select()
                // 这里指定Controller扫描包路径
                .apis(RequestHandlerSelectors.basePackage("com.starry.admin.modules")).paths(PathSelectors.any())
                .build();
        return docket;
    }

    private List<ApiKey> securitySchemes() {
        // 设置请求头信息
        List<ApiKey> result = new ArrayList<>();
        ApiKey apiKey = new ApiKey("Authorization", "Authorization", "header");
        result.add(apiKey);
        return result;
    }
}


--- File: play-common/src/main/java/com/starry/common/config/ThreadPoolConfig.java ---
package com.starry.common.config;

import com.starry.common.utils.ThreadsUtils;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.ThreadPoolExecutor;
import org.apache.commons.lang3.concurrent.BasicThreadFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;

/**
 * @author admin 线程池配置
 * @since 2022/7/25
 */
@Configuration
public class ThreadPoolConfig {

    /**
     * 核心线程池大小
     **/
    private final int corePoolSize = 50;
    /**
     * 最大可创建的线程数
     **/
    private final int maxPoolSize = 200;
    /**
     * 队列最大长度
     **/
    private final int queueCapacity = 1000;
    /**
     * 线程池维护线程所允许的空闲时间
     **/
    private final int keepAliveSeconds = 300;

    @Bean(name = "threadPoolTaskExecutor")
    public ThreadPoolTaskExecutor threadPoolTaskExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setMaxPoolSize(maxPoolSize);
        executor.setCorePoolSize(corePoolSize);
        executor.setQueueCapacity(queueCapacity);
        executor.setKeepAliveSeconds(keepAliveSeconds);
        // 线程池对拒绝任务(无线程可用)的处理策略
        executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
        return executor;
    }

    /**
     * 执行周期性或定时任务
     */
    @Bean(name = "scheduledExecutorService")
    protected ScheduledExecutorService scheduledExecutorService() {
        return new ScheduledThreadPoolExecutor(corePoolSize,
                new BasicThreadFactory.Builder().namingPattern("schedule-pool-%d").daemon(true).build(),
                new ThreadPoolExecutor.CallerRunsPolicy()) {
            @Override
            protected void afterExecute(Runnable r, Throwable t) {
                super.afterExecute(r, t);
                ThreadsUtils.printException(r, t);
            }
        };
    }

}


--- File: play-common/src/main/java/com/starry/common/config/typehandler/ArrayLongTypeHandler.java ---
package com.starry.common.config.typehandler;

import cn.hutool.json.JSONUtil;
import java.sql.CallableStatement;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import org.apache.ibatis.type.BaseTypeHandler;
import org.apache.ibatis.type.JdbcType;
import org.apache.ibatis.type.MappedJdbcTypes;
import org.apache.ibatis.type.MappedTypes;

/**
 * 存储到数据库, 将LONG数组转换成字符串; 从数据库获取数据, 将字符串转为LONG数组.
 */
@MappedTypes({Long[].class})
@MappedJdbcTypes({JdbcType.VARCHAR})
public class ArrayLongTypeHandler extends BaseTypeHandler<Long[]> {

    private static final Long[] l = new Long[]{};

    @Override
    public void setNonNullParameter(PreparedStatement ps, int i, Long[] parameter, JdbcType jdbcType)
            throws SQLException {
        ps.setString(i, JSONUtil.toJsonStr(parameter));
    }

    @Override
    public Long[] getNullableResult(ResultSet rs, String columnName) throws SQLException {
        return JSONUtil.parseArray(rs.getString(columnName)).toArray(l);
    }

    @Override
    public Long[] getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
        return JSONUtil.parseArray(rs.getString(columnIndex)).toArray(l);
    }

    @Override
    public Long[] getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
        return JSONUtil.parseArray(cs.getString(columnIndex)).toArray(l);
    }

}


--- File: play-common/src/main/java/com/starry/common/config/typehandler/ArrayStringTypeHandler.java ---
package com.starry.common.config.typehandler;

import cn.hutool.json.JSONUtil;
import java.sql.CallableStatement;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import org.apache.ibatis.type.BaseTypeHandler;
import org.apache.ibatis.type.JdbcType;
import org.apache.ibatis.type.MappedJdbcTypes;
import org.apache.ibatis.type.MappedTypes;

/**
 * 存储到数据库, 将String数组转换成字符串; 从数据库获取数据, 将字符串转为LONG数组.
 */
@MappedTypes({String[].class})
@MappedJdbcTypes({JdbcType.VARCHAR})
public class ArrayStringTypeHandler extends BaseTypeHandler<String[]> {

    private static final String[] l = new String[]{};

    @Override
    public void setNonNullParameter(PreparedStatement ps, int i, String[] parameter, JdbcType jdbcType)
            throws SQLException {
        ps.setString(i, JSONUtil.toJsonStr(parameter));
    }

    @Override
    public String[] getNullableResult(ResultSet rs, String columnName) throws SQLException {
        return JSONUtil.parseArray(rs.getString(columnName)).toArray(l);
    }

    @Override
    public String[] getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
        return JSONUtil.parseArray(rs.getString(columnIndex)).toArray(l);
    }

    @Override
    public String[] getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
        return JSONUtil.parseArray(cs.getString(columnIndex)).toArray(l);
    }

}


--- File: play-common/src/main/java/com/starry/common/config/typehandler/JsonTypeHandler.java ---
package com.starry.common.config.typehandler;

import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import java.sql.CallableStatement;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import org.apache.ibatis.type.BaseTypeHandler;
import org.apache.ibatis.type.JdbcType;
import org.apache.ibatis.type.MappedJdbcTypes;
import org.apache.ibatis.type.MappedTypes;

/**
 * 存储到数据库, 将JSON对象转换成字符串; 从数据库获取数据, 将字符串转为JSON对象.
 */
@MappedTypes({JSONObject.class})
@MappedJdbcTypes({JdbcType.VARCHAR})
public class JsonTypeHandler extends BaseTypeHandler<JSONObject> {

    @Override
    public void setNonNullParameter(PreparedStatement ps, int i, JSONObject parameter, JdbcType jdbcType)
            throws SQLException {

        ps.setString(i, JSONUtil.toJsonStr(parameter));
    }

    @Override
    public JSONObject getNullableResult(ResultSet rs, String columnName) throws SQLException {

        return JSONUtil.parseObj(rs.getString(columnName)).toBean(JSONObject.class);
    }

    @Override
    public JSONObject getNullableResult(ResultSet rs, int columnIndex) throws SQLException {

        return JSONUtil.parseObj(rs.getString(columnIndex)).toBean(JSONObject.class);
    }

    @Override
    public JSONObject getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {

        return JSONUtil.parseObj(cs.getString(columnIndex)).toBean(JSONObject.class);
    }

}


--- File: play-common/src/main/java/com/starry/common/constant/CacheConstants.java ---
package com.starry.common.constant;

/**
 * @author 缓存常量
 * @since 2022/8/26
 */
public class CacheConstants {

    /**
     * 字典管理 cache key
     */
    public static final String SYS_DICT_KEY = "sys_dict:";

    /**
     * 登录用户 redis key
     */
    public static final String LOGIN_TOKEN_KEY = "login_tokens:";

    /**
     * 验证码 redis key
     */
    public static final String CAPTCHA_CODE_KEY = "captcha_codes:";
}


--- File: play-common/src/main/java/com/starry/common/constant/Constants.java ---
package com.starry.common.constant;

/**
 * @author admin 通用常量信息
 */
public class Constants {

    /**
     * UTF-8 字符集
     */
    public static final String UTF8 = "UTF-8";

    /**
     * GBK 字符集
     */
    public static final String GBK = "GBK";

    /**
     * http请求
     */
    public static final String HTTP = "http://";

    /**
     * https请求
     */
    public static final String HTTPS = "https://";

    /**
     * 通用成功标识
     */
    public static final String SUCCESS = "0";

    /**
     * 通用失败标识
     */
    public static final String FAIL = "1";

    /**
     * 登录成功
     */
    public static final String LOGIN_SUCCESS = "Success";

    /**
     * 注销
     */
    public static final String LOGOUT = "Logout";

    /**
     * 登录失败
     */
    public static final String LOGIN_FAIL = "Error";

    /**
     * 令牌前缀
     */
    public static final String TOKEN_PREFIX = "Bearer ";

    public static final String CLERK_USER_LOGIN_TOKEN = "clerkusertoken";
    public static final String CUSTOM_USER_LOGIN_TOKEN = "customusertoken";

    /**
     * 令牌前缀
     */
    public static final String LOGIN_USER_KEY = "login_user_key";
    public static final String LOGIN_USER_KEY_MINI = "login_user_key_mini";
    public static final String LOGIN_USER_KEY_COSER = "login_user_key_coser";

}


--- File: play-common/src/main/java/com/starry/common/constant/HttpStatus.java ---
package com.starry.common.constant;

/**
 * 返回状态码
 *
 * @author ruoyi
 */
@SuppressWarnings("")
public class HttpStatus {
    /**
     * 操作成功
     */
    public static final int SUCCESS = 200;

    /**
     * 对象创建成功
     */
    public static final int CREATED = 201;

    /**
     * 请求已经被接受
     */
    public static final int ACCEPTED = 202;

    /**
     * 操作已经执行成功，但是没有返回数据
     */
    public static final int NO_CONTENT = 204;

    /**
     * 资源已被移除
     */
    public static final int MOVED_PERM = 301;

    /**
     * 重定向
     */
    public static final int SEE_OTHER = 303;

    /**
     * 资源没有被修改
     */
    public static final int NOT_MODIFIED = 304;

    /**
     * 参数列表错误（缺少，格式不匹配）
     */
    public static final int BAD_REQUEST = 400;

    /**
     * 未授权
     */
    public static final int UNAUTHORIZED = 401;

    /**
     * 访问受限，授权过期
     */
    public static final int FORBIDDEN = 403;

    /**
     * 资源，服务未找到
     */
    public static final int NOT_FOUND = 404;

    /**
     * 不允许的http方法
     */
    public static final int BAD_METHOD = 405;

    /**
     * 资源冲突，或者资源被锁
     */
    public static final int CONFLICT = 409;

    /**
     * 不支持的数据，媒体类型
     */
    public static final int UNSUPPORTED_TYPE = 415;

    /**
     * 系统内部错误
     */
    public static final int ERROR = 500;

    /**
     * 接口未实现
     */
    public static final int NOT_IMPLEMENTED = 501;
}


--- File: play-common/src/main/java/com/starry/common/constant/SecurityConstants.java ---
package com.starry.common.constant;

/**
 * @author admin 权限相关常量
 */
public class SecurityConstants {
    /**
     * 租户ID字段
     */
    public static final String DETAILS_TENANT_ID = "tenant_id";

    /**
     * 部门ID字段
     */
    public static final String DETAILS_DEPT_ID = "dept_id";

    /**
     * 用户ID字段
     */
    public static final String DETAILS_USER_ID = "user_id";

    /**
     * 用户名字段
     */
    public static final String DETAILS_USERNAME = "username";
    /**
     * 用户标识
     */
    public static final String USER_KEY = "user_key";
    /**
     * 登录用户
     */
    public static final String LOGIN_USER = "login_user";
    /**
     * 角色权限
     */
    public static final String ROLE_PERMISSION = "role_permission";
}


--- File: play-common/src/main/java/com/starry/common/constant/UserConstants.java ---
package com.starry.common.constant;

/**
 * @author 用户常量信息
 * @since 2022/7/4
 */
public class UserConstants {

    /**
     * 校验返回结果码 0：唯一 1：不唯一
     */
    public final static String UNIQUE = "0";
    public final static String NOT_UNIQUE = "1";

    /**
     * 是否菜单外链（否）
     */
    public static final String NO_FRAME = "0";

    /**
     * 是否菜单外链（是）
     */
    public static final String YES_FRAME = "1";

    /**
     * 菜单类型（目录）
     */
    public static final String TYPE_DIR = "0";

    /**
     * 菜单类型（菜单）
     */
    public static final String TYPE_MENU = "1";

    /**
     * 菜单类型（按钮）
     */
    public static final String TYPE_BUTTON = "2";

    /**
     * Layout组件标识
     */
    public final static String LAYOUT = "Layout";

    /**
     * InnerLink组件标识
     */
    public final static String INNER_LINK = "InnerLink";

    /**
     * ParentView组件标识
     */
    public final static String PARENT_VIEW = "ParentView";

    /**
     * 部门正常状态
     */
    public static final String DEPT_NORMAL = "0";

    /**
     * 部门停用状态
     */
    public static final String DEPT_DISABLE = "1";

    /**
     * 用户名长度限制
     */
    public static final int USERNAME_MIN_LENGTH = 2;

    public static final int USERNAME_MAX_LENGTH = 20;

    /**
     * 密码长度限制
     */
    public static final int PASSWORD_MIN_LENGTH = 5;

    public static final int PASSWORD_MAX_LENGTH = 20;
}


--- File: play-common/src/main/java/com/starry/common/context/CustomSecurityContextHolder.java ---
package com.starry.common.context;

import cn.hutool.core.convert.Convert;
import com.alibaba.ttl.TransmittableThreadLocal;
import com.starry.common.constant.SecurityConstants;
import com.starry.common.utils.StringUtils;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
 * @author admin 获取当前线程变量中的 租户id 部门id 用户id、用户名称、Token等信息
 * @since 2023/3/6
 */
public class CustomSecurityContextHolder {

    private static final TransmittableThreadLocal<Map<String, Object>> THREAD_LOCAL = new TransmittableThreadLocal<>();

    public static void set(String key, Object value) {
        Map<String, Object> map = getLocalMap();
        map.put(key, value == null ? StringUtils.EMPTY : value);
    }

    public static String get(String key) {
        Map<String, Object> map = getLocalMap();
        return Convert.toStr(map.getOrDefault(key, StringUtils.EMPTY));
    }

    public static <T> T get(String key, Class<T> clazz) {
        Map<String, Object> map = getLocalMap();
        return StringUtils.cast(map.getOrDefault(key, null));
    }

    public static Map<String, Object> getLocalMap() {
        Map<String, Object> map = THREAD_LOCAL.get();
        if (map == null) {
            map = new ConcurrentHashMap<>();
            THREAD_LOCAL.set(map);
        }
        return map;
    }

    public static void setLocalMap(Map<String, Object> threadLocalMap) {
        THREAD_LOCAL.set(threadLocalMap);
    }

    public static String getTenantId() {
        return Convert.toStr(get(SecurityConstants.DETAILS_TENANT_ID), "9999");
    }

    public static void setTenantId(String tenantId) {
        set(SecurityConstants.DETAILS_TENANT_ID, tenantId);
    }

    public static Long getDeptId() {
        return Convert.toLong(get(SecurityConstants.DETAILS_DEPT_ID));
    }

    public static String getUserId() {
        return Convert.toStr(get(SecurityConstants.DETAILS_USER_ID));
    }

    public static void setDeptId(String deptId) {
        set(SecurityConstants.DETAILS_DEPT_ID, deptId);
    }

    public static String getPermission() {
        return get(SecurityConstants.ROLE_PERMISSION);
    }

    public static void setPermission(String permissions) {
        set(SecurityConstants.ROLE_PERMISSION, permissions);
    }

    public static void remove() {
        THREAD_LOCAL.remove();
    }
}


--- File: play-common/src/main/java/com/starry/common/controller/CaptchaController.java ---
package com.starry.common.controller;

import com.starry.common.domain.Captcha;
import com.starry.common.redis.CaptchaService;
import com.starry.common.result.R;
import io.swagger.annotations.ApiOperation;
import javax.annotation.Resource;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 * @author admin 验证码前端控制器
 * @since 2022/7/7
 */
@RestController
@RequestMapping("/captcha")
public class CaptchaController {

    @Resource
    private CaptchaService captchaService;

    @ApiOperation(value = "生成验证码拼图")
    @PostMapping("get-captcha")
    public R getCaptcha(@RequestBody Captcha captcha) {
        return R.ok(captchaService.getCaptcha(captcha));
    }

}


--- File: play-common/src/main/java/com/starry/common/domain/BaseEntity.java ---
package com.starry.common.domain;

import com.baomidou.mybatisplus.annotation.FieldFill;
import com.baomidou.mybatisplus.annotation.TableField;
import com.fasterxml.jackson.annotation.JsonFormat;
import io.swagger.annotations.ApiModelProperty;
import java.io.Serializable;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import javax.persistence.MappedSuperclass;
import javax.persistence.Transient;
import lombok.Getter;
import lombok.Setter;
import org.springframework.format.annotation.DateTimeFormat;

/**
 * @author admin 基类
 * @since 2022/7/14
 */
@Getter
@Setter
@MappedSuperclass
public class BaseEntity<T> implements Serializable {

    private static final long serialVersionUID = 1L;

    @TableField(fill = FieldFill.INSERT)
    @ApiModelProperty(value = "逻辑删除 1已删除 0未删除")
    private Boolean deleted;

    @TableField(fill = FieldFill.INSERT)
    @ApiModelProperty(value = "创建人")
    private String createdBy;

    @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
    @TableField(fill = FieldFill.INSERT)
    @ApiModelProperty(value = "创建时间")
    private Date createdTime;

    @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
    @TableField(fill = FieldFill.UPDATE)
    @ApiModelProperty(value = "更新时间")
    private Date updatedTime;

    @TableField(fill = FieldFill.UPDATE)
    @ApiModelProperty(value = "更新人")
    private String updatedBy;

    /**
     * 搜索值
     */
    @TableField(exist = false)
    private String searchValue;

    /**
     * 开始日期
     */
    @TableField(exist = false)
    private String beginTime;

    /**
     * 结束日期
     */
    @TableField(exist = false)
    private String endTime;

    /**
     * 请求参数
     */
    @Transient
    @TableField(exist = false)
    private Map<String, Object> params = new HashMap<>();

}


--- File: play-common/src/main/java/com/starry/common/domain/BasePageEntity.java ---
package com.starry.common.domain;

import com.baomidou.mybatisplus.annotation.FieldFill;
import com.baomidou.mybatisplus.annotation.TableField;
import io.swagger.annotations.ApiModelProperty;
import java.io.Serializable;
import java.util.Date;
import lombok.Data;
import lombok.experimental.Accessors;
import org.springframework.format.annotation.DateTimeFormat;

/**
 * @author admin 分页基类
 * @since 2021/9/2
 */
@Data
@Accessors(chain = true)
public class BasePageEntity implements Serializable {

    @ApiModelProperty(value = "当前页码", example = "1", notes = "默认为1")
    private int currentPage = 1;

    @ApiModelProperty(value = "每页记录数", example = "10", notes = "默认为10")
    private int pageSize = 10;

    @ApiModelProperty(value = "页码", example = "1", notes = "与currentPage相同，默认为1")
    private int pageNum = 1;

    @TableField(fill = FieldFill.INSERT)
    @ApiModelProperty(value = "是否已删除", example = "false", hidden = true)
    private Boolean deleted = Boolean.FALSE;

    @TableField(fill = FieldFill.INSERT)
    @ApiModelProperty(value = "创建人", hidden = true)
    private String createdBy;

    @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    @TableField(fill = FieldFill.INSERT)
    @ApiModelProperty(value = "创建时间", hidden = true)
    private Date createdTime;

    @TableField(fill = FieldFill.UPDATE)
    @ApiModelProperty(value = "更新时间", hidden = true)
    private Date updatedTime;

    @TableField(fill = FieldFill.UPDATE)
    @ApiModelProperty(value = "更新人", hidden = true)
    private String updatedBy;

    /**
     * 开始日期
     */
    @TableField(exist = false)
    @ApiModelProperty(value = "开始日期", example = "2024-01-01 00:00:00", notes = "格式：yyyy-MM-dd HH:mm:ss")
    private String beginTime;

    /**
     * 结束日期
     */
    @TableField(exist = false)
    @ApiModelProperty(value = "结束日期", example = "2024-12-31 23:59:59", notes = "格式：yyyy-MM-dd HH:mm:ss")
    private String endTime;

}


--- File: play-common/src/main/java/com/starry/common/domain/Captcha.java ---
package com.starry.common.domain;

import lombok.Data;

/**
 * @author admin 验证码拼图类
 */
@Data
public class Captcha {

    /**
     * 随机字符串
     **/
    private String nonceStr;
    /**
     * 验证值
     **/
    private String value;
    /**
     * 生成的画布的base64
     **/
    private String canvasSrc;
    /**
     * 画布宽度
     **/
    private Integer canvasWidth;
    /**
     * 画布高度
     **/
    private Integer canvasHeight;
    /**
     * 生成的阻塞块的base64
     **/
    private String blockSrc;
    /**
     * 阻塞块宽度
     **/
    private Integer blockWidth;
    /**
     * 阻塞块高度
     **/
    private Integer blockHeight;
    /**
     * 阻塞块凸凹半径
     **/
    private Integer blockRadius;
    /**
     * 阻塞块的横轴坐标
     **/
    private Integer blockX;
    /**
     * 阻塞块的纵轴坐标
     **/
    private Integer blockY;
    /**
     * 图片获取位置
     **/
    private Integer place;
}


--- File: play-common/src/main/java/com/starry/common/enums/BusinessType.java ---
package com.starry.common.enums;

/**
 * @author admin 业务操作类型
 * @since 2022/7/22
 */
public enum BusinessType {
    /**
     * 其它
     */
    OTHER,

    /**
     * 新增
     */
    INSERT,

    /**
     * 修改
     */
    UPDATE,

    /**
     * 删除
     */
    DELETE,
    /**
     * 导出
     */
    EXPORT,

    /**
     * 导入
     */
    IMPORT,
    /**
     * 生成代码
     */
    GENCODE,
    /**
     * 清空数据
     */
    CLEAN,
    /**
     * 强退
     */
    FORCE,
    /**
     * 授权
     */
    GRANT,
}


--- File: play-common/src/main/java/com/starry/common/filter/RepeatableFilter.java ---
package com.starry.common.filter;

import com.starry.common.utils.StringUtils;
import java.io.IOException;
import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import org.springframework.http.MediaType;

/**
 * Repeatable 过滤器
 *
 * @author ruoyi
 */
public class RepeatableFilter implements Filter {
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {

    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
            throws IOException, ServletException {
        ServletRequest requestWrapper = null;
        if (request instanceof HttpServletRequest
                && StringUtils.equalsAnyIgnoreCase(request.getContentType(), MediaType.APPLICATION_JSON_VALUE)) {
            requestWrapper = new RepeatedlyRequestWrapper((HttpServletRequest) request, response);
        }
        if (null == requestWrapper) {
            chain.doFilter(request, response);
        } else {
            chain.doFilter(requestWrapper, response);
        }
    }

    @Override
    public void destroy() {

    }
}


--- File: play-common/src/main/java/com/starry/common/filter/RepeatedlyRequestWrapper.java ---
package com.starry.common.filter;

import com.starry.common.utils.http.HttpHelper;
import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import javax.servlet.ReadListener;
import javax.servlet.ServletInputStream;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;

/**
 * 构建可重复读取inputStream的request
 *
 * @author ruoyi
 */
public class RepeatedlyRequestWrapper extends HttpServletRequestWrapper {
    private final byte[] body;

    public RepeatedlyRequestWrapper(HttpServletRequest request, ServletResponse response) throws IOException {
        super(request);
        request.setCharacterEncoding("UTF-8");
        response.setCharacterEncoding("UTF-8");

        body = HttpHelper.getBodyString(request).getBytes("UTF-8");
    }

    @Override
    public BufferedReader getReader() throws IOException {
        return new BufferedReader(new InputStreamReader(getInputStream()));
    }

    @Override
    public ServletInputStream getInputStream() throws IOException {

        final ByteArrayInputStream bais = new ByteArrayInputStream(body);

        return new ServletInputStream() {

            @Override
            public int read() throws IOException {
                return bais.read();
            }

            @Override
            public boolean isFinished() {
                return false;
            }

            @Override
            public boolean isReady() {
                return false;
            }

            @Override
            public void setReadListener(ReadListener readListener) {

            }
        };
    }
}


--- File: play-common/src/main/java/com/starry/common/filter/XssFilter.java ---
package com.starry.common.filter;

import cn.hutool.core.util.StrUtil;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/**
 * 防止XSS攻击的过滤器
 *
 * @author ruoyi
 */
public class XssFilter implements Filter {
    /**
     * 排除链接
     */
    public List<String> excludes = new ArrayList<>();

    /**
     * xss过滤开关
     */
    public boolean enabled = false;

    @Override
    public void init(FilterConfig filterConfig) {
        String tempExcludes = filterConfig.getInitParameter("excludes");
        String tempEnabled = filterConfig.getInitParameter("enabled");
        if (StrUtil.isNotBlank(tempExcludes)) {
            String[] url = tempExcludes.split(",");
            excludes.addAll(Arrays.asList(url));
        }
        if (StrUtil.isNotBlank(tempEnabled)) {
            enabled = Boolean.parseBoolean(tempEnabled);
        }
    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
            throws IOException, ServletException {
        HttpServletRequest req = (HttpServletRequest) request;
        HttpServletResponse resp = (HttpServletResponse) response;
        if (handleExcludeURL(req, resp)) {
            chain.doFilter(request, response);
            return;
        }
        XssHttpServletRequestWrapper xssRequest = new XssHttpServletRequestWrapper((HttpServletRequest) request);
        chain.doFilter(xssRequest, response);
    }

    private boolean handleExcludeURL(HttpServletRequest request, HttpServletResponse response) {
        if (!enabled) {
            return true;
        }
        if (excludes == null || excludes.isEmpty()) {
            return false;
        }
        String url = request.getServletPath();
        for (String pattern : excludes) {
            Pattern p = Pattern.compile("^" + pattern);
            Matcher m = p.matcher(url);
            if (m.find()) {
                return true;
            }
        }
        return false;
    }

    @Override
    public void destroy() {

    }
}


--- File: play-common/src/main/java/com/starry/common/filter/XssHttpServletRequestWrapper.java ---
package com.starry.common.filter;

import com.starry.common.utils.StringUtils;
import com.starry.common.utils.html.EscapeUtil;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import javax.servlet.ReadListener;
import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import org.apache.commons.io.IOUtils;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;

/**
 * XSS过滤处理
 *
 * @author ruoyi
 */
public class XssHttpServletRequestWrapper extends HttpServletRequestWrapper {
    /**
     * @param request
     */
    public XssHttpServletRequestWrapper(HttpServletRequest request) {
        super(request);
    }

    @Override
    public String[] getParameterValues(String name) {
        String[] values = super.getParameterValues(name);
        if (values != null) {
            int length = values.length;
            String[] escapseValues = new String[length];
            for (int i = 0; i < length; i++) {
                // 防xss攻击和过滤前后空格
                escapseValues[i] = EscapeUtil.clean(values[i]).trim();
            }
            return escapseValues;
        }
        return super.getParameterValues(name);
    }

    @Override
    public ServletInputStream getInputStream() throws IOException {
        // 非json类型，直接返回
        if (!isJsonRequest()) {
            return super.getInputStream();
        }

        // 为空，直接返回
        String json = IOUtils.toString(super.getInputStream(), StandardCharsets.UTF_8);
        if (StringUtils.isEmpty(json)) {
            return super.getInputStream();
        }

        // xss过滤
        json = EscapeUtil.clean(json).trim();
        final ByteArrayInputStream bis = new ByteArrayInputStream(json.getBytes(StandardCharsets.UTF_8));
        return new ServletInputStream() {
            @Override
            public boolean isFinished() {
                return true;
            }

            @Override
            public boolean isReady() {
                return true;
            }

            @Override
            public void setReadListener(ReadListener readListener) {
            }

            @Override
            public int read() throws IOException {
                return bis.read();
            }
        };
    }

    /**
     * 是否是Json请求
     */
    public boolean isJsonRequest() {
        String header = super.getHeader(HttpHeaders.CONTENT_TYPE);
        return MediaType.APPLICATION_JSON_VALUE.equalsIgnoreCase(header);
    }
}


--- File: play-common/src/main/java/com/starry/common/redis/CaptchaService.java ---
package com.starry.common.redis;

import cn.hutool.core.util.StrUtil;
import com.starry.common.constant.CacheConstants;
import com.starry.common.domain.Captcha;
import com.starry.common.utils.CaptchaUtils;
import java.awt.image.BufferedImage;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
import javax.annotation.Resource;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.stereotype.Service;

/**
 * 验证码服务
 *
 * @author admin
 */
@Service
public class CaptchaService {
    /**
     * 拼图验证码允许偏差
     **/
    private static final Integer ALLOW_DEVIATION = 3;

    @Resource
    private StringRedisTemplate stringRedisTemplate;

    /**
     * 校验验证码
     *
     * @param imageKey
     * @param imageCode
     * @return boolean
     **/
    public String checkImageCode(String imageKey, String imageCode) {
        ValueOperations<String, String> ops = stringRedisTemplate.opsForValue();
        String text = ops.get(CacheConstants.CAPTCHA_CODE_KEY + imageKey);
        if (StrUtil.isBlank(text)) {
            return "验证码已失效";
        }
        // 根据移动距离判断验证是否成功
        if (Math.abs(Integer.parseInt(text) - Integer.parseInt(imageCode)) > ALLOW_DEVIATION) {
            return "验证失败，请控制拼图对齐缺口";
        }
        return null;
    }

    /**
     * 缓存验证码，有效期15分钟
     *
     * @param key
     * @param code
     **/
    public void saveImageCode(String key, String code) {
        ValueOperations<String, String> ops = stringRedisTemplate.opsForValue();
        ops.set(CacheConstants.CAPTCHA_CODE_KEY + key, code, 15, TimeUnit.MINUTES);
    }

    /**
     * 获取验证码拼图（生成的抠图和带抠图阴影的大图及抠图坐标）
     **/
    public Object getCaptcha(Captcha captcha) {
        // 参数校验
        CaptchaUtils.checkCaptcha(captcha);
        // 获取画布的宽高
        int canvasWidth = captcha.getCanvasWidth();
        int canvasHeight = captcha.getCanvasHeight();
        // 获取阻塞块的宽高/半径
        int blockWidth = captcha.getBlockWidth();
        int blockHeight = captcha.getBlockHeight();
        int blockRadius = captcha.getBlockRadius();
        // 获取资源图
        BufferedImage canvasImage = CaptchaUtils.getBufferedImage(captcha.getPlace());
        // 调整原图到指定大小
        canvasImage = CaptchaUtils.imageResize(canvasImage, canvasWidth, canvasHeight);
        // 随机生成阻塞块坐标
        int blockX = CaptchaUtils.getNonceByRange(blockWidth, canvasWidth - blockWidth - 10);
        int blockY = CaptchaUtils.getNonceByRange(10, canvasHeight - blockHeight + 1);
        // 阻塞块
        BufferedImage blockImage = new BufferedImage(blockWidth, blockHeight, BufferedImage.TYPE_4BYTE_ABGR);
        // 新建的图像根据轮廓图颜色赋值，源图生成遮罩
        CaptchaUtils.cutByTemplate(canvasImage, blockImage, blockWidth, blockHeight, blockRadius, blockX, blockY);
        // 移动横坐标
        String nonceStr = UUID.randomUUID().toString().replaceAll("-", "");
        // 缓存
        saveImageCode(nonceStr, String.valueOf(blockX));
        // 设置返回参数
        captcha.setNonceStr(nonceStr);
        captcha.setBlockY(blockY);
        captcha.setBlockSrc(CaptchaUtils.toBase64(blockImage, "png"));
        captcha.setCanvasSrc(CaptchaUtils.toBase64(canvasImage, "png"));
        return captcha;
    }
}


--- File: play-common/src/main/java/com/starry/common/redis/RedisCache.java ---
package com.starry.common.redis;

import java.util.Collection;
import java.util.concurrent.TimeUnit;
import javax.annotation.Resource;
import org.springframework.dao.QueryTimeoutException;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.stereotype.Component;

/**
 * @author rieds 工具类
 * @since 2022/8/26
 */
@Component
public class RedisCache {

    @Resource
    public RedisTemplate<String, Object> redisTemplate;

    /**
     * 获得缓存的基本对象。
     *
     * @param key
     *            缓存键值
     * @return 缓存键值对应的数据
     */
    public <T> T getCacheObject(final String key) {
        ValueOperations<String, Object> operations = redisTemplate.opsForValue();
        try {
            return (T) operations.get(key);
        } catch (QueryTimeoutException e) {
            throw new RuntimeException("系统错误，通讯异常");
        }

    }

    /**
     * 缓存基本的对象，Integer、String、实体类等
     *
     * @param key
     *            缓存的键值
     * @param value
     *            缓存的值
     */
    public <T> void setCacheObject(final String key, final T value) {
        redisTemplate.opsForValue().set(key, value);
    }

    /**
     * 缓存基本的对象，Integer、String、实体类等
     *
     * @param key
     *            缓存的键值
     * @param value
     *            缓存的值
     * @param timeout
     *            过期时间
     * @param timeUnit
     *            时间颗粒度
     */
    public <T> void setCacheObject(final String key, final T value, final Long timeout, final TimeUnit timeUnit) {
        redisTemplate.opsForValue().set(key, value, timeout, timeUnit);
    }

    /**
     * 删除单个对象
     *
     * @param key
     */
    public boolean deleteObject(final String key) {
        return redisTemplate.delete(key);
    }

    /**
     * 获得缓存的基本对象列表
     *
     * @param pattern
     *            字符串前缀
     * @return 对象列表
     */
    public Collection<String> keys(final String pattern) {
        return redisTemplate.keys(pattern);
    }

    /**
     * 删除集合对象
     *
     * @param collection
     *            多个对象
     * @return
     */
    public long deleteObject(final Collection collection) {
        return redisTemplate.delete(collection);
    }
}


--- File: play-common/src/main/java/com/starry/common/result/IBaseEnum.java ---
package com.starry.common.result;

/**
 * @author admin
 */
public interface IBaseEnum {

    /**
     * 获取枚举值
     */
    int getCode();

    /**
     * 获取枚举描述
     */
    String getMessage();
}


--- File: play-common/src/main/java/com/starry/common/result/R.java ---
package com.starry.common.result;

import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import java.beans.ConstructorProperties;
import java.io.Serializable;
import lombok.Data;

/**
 * @author admin
 * @since 2021/9/1
 */
@Data
@ApiModel(value = "全局统一返回结果")
public class R implements Serializable {

    public final static String OK_MSG = "请求成功";
    public final static String FAIL_MSG = "请求失败";

    @ApiModelProperty(value = "是否成功")
    private boolean success;

    @ApiModelProperty(value = "返回码")
    private Integer code;

    @ApiModelProperty(value = "返回消息")
    private String message;

    @ApiModelProperty(value = "返回数据")
    private Object data;

    @ApiModelProperty(value = "总条数")
    private Long total;

    @ApiModelProperty(value = "分页信息")
    private PageInfo pageInfo;

    private R() {
    }

    private R(int code, Boolean success, String msg, Object data) {
        this.code = code;
        this.success = success;
        this.message = msg;
        if (data instanceof Page<?>) {
            Page<?> page = (Page<?>) data;
            this.total = page.getTotal();
            this.data = page.getRecords();
            this.pageInfo = new PageInfo((int) page.getCurrent(), (int) page.getSize(), page.getTotal(),
                    page.getPages());
        } else {
            this.data = data;
        }
    }

    public static R ok() {
        R r = new R();
        r.setSuccess(true);
        r.setCode(ResultCodeEnum.SUCCESS.getCode());
        r.setMessage(ResultCodeEnum.SUCCESS.getMessage());
        return r;
    }

    public static R ok(Object data) {
        return new R(ResultCodeEnum.SUCCESS.getCode(), true, ResultCodeEnum.SUCCESS.getMessage(), data);
    }

    public static R ok(String msg, Object data) {
        return new R(ResultCodeEnum.SUCCESS.getCode(), true, msg, data);
    }

    public static R error() {
        R r = new R();
        r.setSuccess(false);
        r.setCode(ResultCodeEnum.FAILED.getCode());
        r.setMessage(ResultCodeEnum.FAILED.getMessage());
        return r;
    }

    public static R error(String msg) {
        return new R(ResultCodeEnum.FAILED.getCode(), false, msg, null);
    }

    public static R error(int errorCode, String msg) {
        return new R(errorCode, false, msg, null);
    }

    public static R unauthorized() {
        return new R(ResultCodeEnum.UNAUTHORIZED.getCode(), false, ResultCodeEnum.UNAUTHORIZED.getMessage(), null);
    }

    public R message(String message) {
        this.setMessage(message);
        return this;
    }

    public R code(Integer code) {
        this.setCode(code);
        return this;
    }

    public R data(Object data) {
        this.setData(data);
        return this;
    }

    @Data
    public static class PageInfo {

        @ApiModelProperty("当前页")
        protected int currentPage;
        @ApiModelProperty("页大小")
        protected int pageSize;
        @ApiModelProperty("总记录数")
        protected long totalCount;
        @ApiModelProperty("总页数")
        protected long totalPage;

        public PageInfo() {
        }

        @ConstructorProperties({"currentPage", "pageSize", "totalCount", "totalPage"})
        public PageInfo(int currentPage, int pageSize, long totalCount, long totalPage) {
            this.currentPage = currentPage;
            this.pageSize = pageSize;
            this.totalCount = totalCount;
            this.totalPage = totalPage;
        }
    }

}


--- File: play-common/src/main/java/com/starry/common/result/ResultCodeEnum.java ---
package com.starry.common.result;

/**
 * @author admin 返回码枚举
 */
public enum ResultCodeEnum implements IBaseEnum {

    SUCCESS(200, "操作成功"), FAILED(500, "操作失败"), VALIDATE_FAILED(404, "参数检验失败"), UNAUTHORIZED(401,
            "无效的会话，或者会话已过期，请重新登录。"), FORBIDDEN(403, "没有相关权限"), TENANT_NOT_FOUND(403, "租户不存在");

    private final int code;
    private final String message;

    ResultCodeEnum(int code, String message) {
        this.code = code;
        this.message = message;
    }

    @Override
    public int getCode() {
        return code;
    }

    @Override
    public String getMessage() {
        return message;
    }
}


--- File: play-common/src/main/java/com/starry/common/sensitive/Sensitive.java ---
package com.starry.common.sensitive;

import com.fasterxml.jackson.annotation.JacksonAnnotationsInside;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * 脱敏注解
 *
 * @author admin
 **/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
@JacksonAnnotationsInside
@JsonSerialize(using = SensitiveSerialize.class)
public @interface Sensitive {

    /**
     * 脱敏数据类型
     */
    SensitiveTypeEnum type();

}


--- File: play-common/src/main/java/com/starry/common/sensitive/SensitiveSerialize.java ---
package com.starry.common.sensitive;

import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.BeanProperty;
import com.fasterxml.jackson.databind.JsonMappingException;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.SerializerProvider;
import com.fasterxml.jackson.databind.ser.ContextualSerializer;
import java.io.IOException;
import java.util.Objects;
import lombok.AllArgsConstructor;
import lombok.NoArgsConstructor;

/**
 * @author admin
 *         <p>
 *         脱敏序列化
 */
@NoArgsConstructor
@AllArgsConstructor
public class SensitiveSerialize extends JsonSerializer<String> implements ContextualSerializer {

    private SensitiveTypeEnum type;

    @Override
    public void serialize(final String originStr, final JsonGenerator jsonGenerator,
            final SerializerProvider serializerProvider) throws IOException {
        switch (type) {
            case CHINESE_NAME :
                jsonGenerator.writeString(SensitiveUtils.chineseName(originStr));
                break;
            case MOBILE_PHONE :
                jsonGenerator.writeString(SensitiveUtils.mobilePhone(originStr));
                break;
            case EMAIL :
                jsonGenerator.writeString(SensitiveUtils.email(originStr));
                break;
            case PASSWORD :
                jsonGenerator.writeString(SensitiveUtils.password(originStr));
                break;
            case KEY :
                jsonGenerator.writeString(SensitiveUtils.key(originStr));
                break;
            default :
                throw new IllegalArgumentException("未定义的敏感信息枚举类" + type);
        }
    }

    @Override
    public JsonSerializer<?> createContextual(final SerializerProvider serializerProvider,
            final BeanProperty beanProperty) throws JsonMappingException {
        if (beanProperty != null) {
            if (Objects.equals(beanProperty.getType().getRawClass(), String.class)) {
                Sensitive sensitive = beanProperty.getAnnotation(Sensitive.class);
                if (sensitive == null) {
                    sensitive = beanProperty.getContextAnnotation(Sensitive.class);
                }
                if (sensitive != null) {
                    return new SensitiveSerialize(sensitive.type());
                }
            }
            return serializerProvider.findValueSerializer(beanProperty.getType(), beanProperty);
        }
        return serializerProvider.findNullValueSerializer(null);
    }

}


--- File: play-common/src/main/java/com/starry/common/sensitive/SensitiveTypeEnum.java ---
package com.starry.common.sensitive;

/**
 * 敏感信息枚举类
 *
 * @author admin
 **/
public enum SensitiveTypeEnum {

    /**
     * 用户名, 李*天, 张*
     */
    CHINESE_NAME,
    /**
     * 手机号, 185****1653
     */
    MOBILE_PHONE,
    /**
     * 电子邮件, r*****o@qq.com
     */
    EMAIL,
    /**
     * 密码, ******
     */
    PASSWORD,
    /**
     * 密钥, 最后三位其他都是***
     */
    KEY

}


--- File: play-common/src/main/java/com/starry/common/sensitive/SensitiveUtils.java ---
package com.starry.common.sensitive;

import cn.hutool.core.util.StrUtil;

/**
 * 数据脱敏工具类
 *
 * @author admin
 * @since 2021/7/19 16:21
 */
public class SensitiveUtils {

    /**
     * 默认填充字符
     */
    public static final String DEFAULT_PAD_STR = "*";

    /**
     * 数据脱敏
     */
    public static String process(String data) {
        return process(data, 2, 1, DEFAULT_PAD_STR);
    }

    /**
     * 数据脱敏
     */
    public static String process(String data, Integer leftLen, Integer rightLen) {
        return process(data, leftLen, rightLen, DEFAULT_PAD_STR);
    }

    /**
     * 对字符串进行脱敏操作
     *
     * @param originStr
     *            原始字符串
     * @param prefixNoMaskLen
     *            左侧需要保留几位明文字段
     * @param suffixNoMaskLen
     *            右侧需要保留几位明文字段
     * @param maskStr
     *            用于遮罩的字符串, 如'*'
     * @return 脱敏后结果
     */
    public static String process(String originStr, int prefixNoMaskLen, int suffixNoMaskLen, String maskStr) {
        if (originStr == null) {
            return null;
        }

        StringBuilder sb = new StringBuilder();
        for (int i = 0, n = originStr.length(); i < n; i++) {
            if (i < prefixNoMaskLen) {
                sb.append(originStr.charAt(i));
                continue;
            }
            if (i > (n - suffixNoMaskLen - 1)) {
                sb.append(originStr.charAt(i));
                continue;
            }
            sb.append(maskStr);
        }
        return sb.toString();
    }

    /**
     * 中文姓名只显示最后一个汉字
     *
     * @param fullName
     *            姓名
     * @return
     */
    public static String chineseName(String fullName) {
        if (fullName == null) {
            return null;
        }
        return process(fullName, 0, 1, DEFAULT_PAD_STR);
    }

    /**
     * 手机号码前三位，后四位，如186****2356
     *
     * @param num
     *            手机号码
     * @return
     */
    public static String mobilePhone(String num) {
        return process(num, 0, 4, DEFAULT_PAD_STR);
    }

    /**
     * 地址只显示到地区
     *
     * @param address
     *            地址
     * @return
     */
    public static String address(String address) {
        return process(address, 6, 0, DEFAULT_PAD_STR);
    }

    /**
     * 电子邮箱 仅显示第一个字母，@后面的地址显示，比如：r**@qq.com
     *
     * @param email
     *            电子邮箱
     * @return
     */
    public static String email(String email) {
        if (email == null) {
            return null;
        }
        int index = StrUtil.indexOf(email, '@');
        if (index <= 1) {
            return email;
        }
        String preEmail = process(email.substring(0, index), 1, 0, DEFAULT_PAD_STR);
        return preEmail + email.substring(index);

    }

    /**
     * 密码的全部字符，如：******
     *
     * @param password
     *            密码
     * @return
     */
    public static String password(String password) {
        if (password == null) {
            return null;
        }
        return "******";
    }

    /**
     * 密钥除了最后三位，全部，比如：***klo
     *
     * @param key
     *            密钥
     * @return 结果
     */
    public static String key(String key) {
        if (key == null) {
            return null;
        }
        int viewLength = 6;
        StringBuilder tmpKey = new StringBuilder(process(key, 0, 3, DEFAULT_PAD_STR));
        if (tmpKey.length() > viewLength) {
            return tmpKey.substring(tmpKey.length() - viewLength);
        } else if (tmpKey.length() < viewLength) {
            int buffLength = viewLength - tmpKey.length();
            for (int i = 0; i < buffLength; i++) {
                tmpKey.insert(0, DEFAULT_PAD_STR);
            }
            return tmpKey.toString();
        } else {
            return tmpKey.toString();
        }
    }

    public static void main(String[] args) {
        String s = mobilePhone("18653653621");
        System.out.println(s);
    }
}


--- File: play-common/src/main/java/com/starry/common/utils/Arith.java ---
package com.starry.common.utils;

import java.math.BigDecimal;
import java.math.RoundingMode;

/**
 * 精确的浮点数运算
 *
 * @author ruoyi
 */
public class Arith {

    /**
     * 默认除法运算精度
     */
    private static final int DEF_DIV_SCALE = 10;

    /**
     * 这个类不能实例化
     */
    private Arith() {
    }

    /**
     * 提供精确的加法运算。
     *
     * @param v1
     *            被加数
     * @param v2
     *            加数
     * @return 两个参数的和
     */
    public static double add(double v1, double v2) {
        BigDecimal b1 = new BigDecimal(Double.toString(v1));
        BigDecimal b2 = new BigDecimal(Double.toString(v2));
        return b1.add(b2).doubleValue();
    }

    /**
     * 提供精确的减法运算。
     *
     * @param v1
     *            被减数
     * @param v2
     *            减数
     * @return 两个参数的差
     */
    public static double sub(double v1, double v2) {
        BigDecimal b1 = new BigDecimal(Double.toString(v1));
        BigDecimal b2 = new BigDecimal(Double.toString(v2));
        return b1.subtract(b2).doubleValue();
    }

    /**
     * 提供精确的乘法运算。
     *
     * @param v1
     *            被乘数
     * @param v2
     *            乘数
     * @return 两个参数的积
     */
    public static double mul(double v1, double v2) {
        BigDecimal b1 = new BigDecimal(Double.toString(v1));
        BigDecimal b2 = new BigDecimal(Double.toString(v2));
        return b1.multiply(b2).doubleValue();
    }

    /**
     * 提供（相对）精确的除法运算，当发生除不尽的情况时，精确到 小数点以后10位，以后的数字四舍五入。
     *
     * @param v1
     *            被除数
     * @param v2
     *            除数
     * @return 两个参数的商
     */
    public static double div(double v1, double v2) {
        return div(v1, v2, DEF_DIV_SCALE);
    }

    /**
     * 提供（相对）精确的除法运算。当发生除不尽的情况时，由scale参数指 定精度，以后的数字四舍五入。
     *
     * @param v1
     *            被除数
     * @param v2
     *            除数
     * @param scale
     *            表示表示需要精确到小数点以后几位。
     * @return 两个参数的商
     */
    public static double div(double v1, double v2, int scale) {
        if (scale < 0) {
            throw new IllegalArgumentException("The scale must be a positive integer or zero");
        }
        BigDecimal b1 = new BigDecimal(Double.toString(v1));
        BigDecimal b2 = new BigDecimal(Double.toString(v2));
        if (b1.compareTo(BigDecimal.ZERO) == 0) {
            return BigDecimal.ZERO.doubleValue();
        }
        return b1.divide(b2, scale, RoundingMode.HALF_UP).doubleValue();
    }

    /**
     * 提供精确的小数位四舍五入处理。
     *
     * @param v
     *            需要四舍五入的数字
     * @param scale
     *            小数点后保留几位
     * @return 四舍五入后的结果
     */
    public static double round(double v, int scale) {
        if (scale < 0) {
            throw new IllegalArgumentException("The scale must be a positive integer or zero");
        }
        BigDecimal b = new BigDecimal(Double.toString(v));
        BigDecimal one = BigDecimal.ONE;
        return b.divide(one, scale, RoundingMode.HALF_UP).doubleValue();
    }
}


--- File: play-common/src/main/java/com/starry/common/utils/CaptchaUtils.java ---
package com.starry.common.utils;

import com.starry.common.domain.Captcha;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.util.Base64;
import java.util.Objects;
import java.util.Random;
import javax.imageio.ImageIO;
import org.apache.commons.lang3.RandomUtils;

/**
 * @author 拼图验证码工具类
 * @since 2022/9/6
 */
public class CaptchaUtils {

    /**
     * 网络图片地址
     **/
    private final static String IMG_URL = "https://loyer.wang/view/ftp/wallpaper/%s.jpg";

    /**
     * 本地图片地址
     **/
    private final static String IMG_PATH = "E:/Temp/wallpaper/%s.jpg";

    /**
     * 入参校验设置默认值
     **/
    public static void checkCaptcha(Captcha captcha) {
        // 设置画布宽度默认值
        if (captcha.getCanvasWidth() == null) {
            captcha.setCanvasWidth(320);
        }
        // 设置画布高度默认值
        if (captcha.getCanvasHeight() == null) {
            captcha.setCanvasHeight(155);
        }
        // 设置阻塞块宽度默认值
        if (captcha.getBlockWidth() == null) {
            captcha.setBlockWidth(65);
        }
        // 设置阻塞块高度默认值
        if (captcha.getBlockHeight() == null) {
            captcha.setBlockHeight(55);
        }
        // 设置阻塞块凹凸半径默认值
        if (captcha.getBlockRadius() == null) {
            captcha.setBlockRadius(9);
        }
        // 设置图片来源默认值
        if (captcha.getPlace() == null) {
            captcha.setPlace(0);
        }
    }

    /**
     * 获取指定范围内的随机数
     **/
    public static int getNonceByRange(int start, int end) {
        Random random = new Random();
        return random.nextInt(end - start + 1) + start;
    }

    /**
     * 获取验证码资源图
     **/
    public static BufferedImage getBufferedImage(Integer place) {
        try {
            // 随机图片
            int nonce = getNonceByRange(0, 1000);
            // 获取网络资源图片
            if (0 == place) {
                String imgUrl = String.format(IMG_URL, nonce);
                URL url = new URL(imgUrl);
                return ImageIO.read(url.openStream());
            }
            // 获取本地图片
            else {
                String imgPath = String.format(IMG_PATH, nonce);
                File file = new File(imgPath);
                return ImageIO.read(file);
            }
        } catch (Exception e) {
            System.out.println("获取拼图资源失败");
            // 异常处理
            return null;
        }
    }

    /**
     * 调整图片大小
     **/
    public static BufferedImage imageResize(BufferedImage bufferedImage, int width, int height) {
        Image image = bufferedImage.getScaledInstance(width, height, Image.SCALE_SMOOTH);
        BufferedImage resultImage = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
        Graphics2D graphics2D = resultImage.createGraphics();
        graphics2D.drawImage(image, 0, 0, null);
        graphics2D.dispose();
        return resultImage;
    }

    /**
     * 抠图，并生成阻塞块
     **/
    public static void cutByTemplate(BufferedImage canvasImage, BufferedImage blockImage, int blockWidth,
            int blockHeight, int blockRadius, int blockX, int blockY) {
        BufferedImage waterImage = new BufferedImage(blockWidth, blockHeight, BufferedImage.TYPE_4BYTE_ABGR);
        // 阻塞块的轮廓图
        int[][] blockData = getBlockData(blockWidth, blockHeight, blockRadius);
        // 创建阻塞块具体形状
        for (int i = 0; i < blockWidth; i++) {
            for (int j = 0; j < blockHeight; j++) {
                try {
                    // 原图中对应位置变色处理
                    if (blockData[i][j] == 1) {
                        // 背景设置为黑色
                        waterImage.setRGB(i, j, Color.BLACK.getRGB());
                        blockImage.setRGB(i, j, canvasImage.getRGB(blockX + i, blockY + j));
                        // 轮廓设置为白色，取带像素和无像素的界点，判断该点是不是临界轮廓点
                        if (blockData[i + 1][j] == 0 || blockData[i][j + 1] == 0 || blockData[i - 1][j] == 0
                                || blockData[i][j - 1] == 0) {
                            blockImage.setRGB(i, j, Color.WHITE.getRGB());
                            waterImage.setRGB(i, j, Color.WHITE.getRGB());
                        }
                    }
                    // 这里把背景设为透明
                    else {
                        blockImage.setRGB(i, j, Color.TRANSLUCENT);
                        waterImage.setRGB(i, j, Color.TRANSLUCENT);
                    }
                } catch (ArrayIndexOutOfBoundsException e) {
                    // 防止数组下标越界异常
                }
            }
        }
        // 在画布上添加阻塞块水印
        addBlockWatermark(canvasImage, waterImage, blockX, blockY);
    }

    /**
     * 构建拼图轮廓轨迹
     **/
    private static int[][] getBlockData(int blockWidth, int blockHeight, int blockRadius) {
        int[][] data = new int[blockWidth][blockHeight];
        double po = Math.pow(blockRadius, 2);
        // 随机生成两个圆的坐标，在4个方向上 随机找到2个方向添加凸/凹
        // 凸/凹1
        int face1 = RandomUtils.nextInt(0, 4);
        // 凸/凹2
        int face2;
        // 保证两个凸/凹不在同一位置
        do {
            face2 = RandomUtils.nextInt(0, 4);
        } while (face1 == face2);
        // 获取凸/凹起位置坐标
        int[] circle1 = getCircleCoords(face1, blockWidth, blockHeight, blockRadius);
        int[] circle2 = getCircleCoords(face2, blockWidth, blockHeight, blockRadius);
        // 随机凸/凹类型
        int shape = getNonceByRange(0, 1);
        // 圆的标准方程 (x-a)²+(y-b)²=r²,标识圆心（a,b）,半径为r的圆
        // 计算需要的小图轮廓，用二维数组来表示，二维数组有两张值，0和1，其中0表示没有颜色，1有颜色
        for (int i = 0; i < blockWidth; i++) {
            for (int j = 0; j < blockHeight; j++) {
                data[i][j] = 0;
                // 创建中间的方形区域
                if ((i >= blockRadius && i <= blockWidth - blockRadius && j >= blockRadius
                        && j <= blockHeight - blockRadius)) {
                    data[i][j] = 1;
                }
                double d1 = Math.pow(i - Objects.requireNonNull(circle1)[0], 2) + Math.pow(j - circle1[1], 2);
                double d2 = Math.pow(i - Objects.requireNonNull(circle2)[0], 2) + Math.pow(j - circle2[1], 2);
                // 创建两个凸/凹
                if (d1 <= po || d2 <= po) {
                    data[i][j] = shape;
                }
            }
        }
        return data;
    }

    /**
     * 根据朝向获取圆心坐标
     */
    private static int[] getCircleCoords(int face, int blockWidth, int blockHeight, int blockRadius) {
        // 上
        if (0 == face) {
            return new int[]{blockWidth / 2 - 1, blockRadius};
        }
        // 左
        else if (1 == face) {
            return new int[]{blockRadius, blockHeight / 2 - 1};
        }
        // 下
        else if (2 == face) {
            return new int[]{blockWidth / 2 - 1, blockHeight - blockRadius - 1};
        }
        // 右
        else if (3 == face) {
            return new int[]{blockWidth - blockRadius - 1, blockHeight / 2 - 1};
        }
        return null;
    }

    /**
     * 在画布上添加阻塞块水印
     */
    private static void addBlockWatermark(BufferedImage canvasImage, BufferedImage blockImage, int x, int y) {
        Graphics2D graphics2D = canvasImage.createGraphics();
        graphics2D.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_ATOP, 0.8f));
        graphics2D.drawImage(blockImage, x, y, null);
        graphics2D.dispose();
    }

    /**
     * BufferedImage转BASE64
     */
    public static String toBase64(BufferedImage bufferedImage, String type) {
        try {
            ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
            ImageIO.write(bufferedImage, type, byteArrayOutputStream);
            String base64 = Base64.getEncoder().encodeToString(byteArrayOutputStream.toByteArray());
            return String.format("data:image/%s;base64,%s", type, base64);
        } catch (IOException e) {
            System.out.println("图片资源转换BASE64失败");
            // 异常处理
            return null;
        }
    }
}


--- File: play-common/src/main/java/com/starry/common/utils/ConvertUtil.java ---
package com.starry.common.utils;

import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;

/**
 * @author admin
 */
@Slf4j
public class ConvertUtil {

    public static <T> T entityToVo(Object source, Class<T> target) {
        if (source == null) {
            return null;
        }
        T targetObject = null;
        try {
            targetObject = target.newInstance();
            BeanUtils.copyProperties(source, targetObject);
        } catch (Exception e) {
            log.error("entityToVo error,source = {},target={}", source, target, e);
        }
        return targetObject;
    }

    public static <T> List<T> entityToVoList(Collection<?> sourceList, Class<T> target) {
        if (sourceList == null) {
            return null;
        }
        List<T> targetList = new ArrayList<>(sourceList.size());

        try {
            for (Object source : sourceList) {
                T targetObject = target.newInstance();
                BeanUtils.copyProperties(source, targetObject);
                targetList.add(targetObject);
            }
        } catch (Exception e) {
            log.error("entityToVo error,source = {},target={}", sourceList, target, e);
        }
        return targetList;
    }

}


--- File: play-common/src/main/java/com/starry/common/utils/HttpUtils.java ---
package com.starry.common.utils;

import java.io.*;
import java.net.ConnectException;
import java.net.SocketTimeoutException;
import java.net.URL;
import java.net.URLConnection;
import java.nio.charset.StandardCharsets;
import java.security.cert.X509Certificate;
import javax.net.ssl.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * 通用http发送方法
 *
 * @author ruoyi
 */
public class HttpUtils {
    private static final Logger log = LoggerFactory.getLogger(HttpUtils.class);

    /**
     * 向指定 URL 发送GET方法的请求
     *
     * @param url
     *            发送请求的 URL
     * @return 所代表远程资源的响应结果
     */
    public static String sendGet(String url) {
        return sendGet(url, StringUtils.EMPTY);
    }

    /**
     * 向指定 URL 发送GET方法的请求
     *
     * @param url
     *            发送请求的 URL
     * @param param
     *            请求参数，请求参数应该是 name1=value1&name2=value2 的形式。
     * @return 所代表远程资源的响应结果
     */
    public static String sendGet(String url, String param) {
        return sendGet(url, param, "utf-8");
    }

    /**
     * 向指定 URL 发送GET方法的请求
     *
     * @param url
     *            发送请求的 URL
     * @param param
     *            请求参数，请求参数应该是 name1=value1&name2=value2 的形式。
     * @param contentType
     *            编码类型
     * @return 所代表远程资源的响应结果
     */
    public static String sendGet(String url, String param, String contentType) {
        StringBuilder result = new StringBuilder();
        BufferedReader in = null;
        try {
            String urlNameString = StringUtils.isNotBlank(param) ? url + "?" + param : url;
            log.info("sendGet - {}", urlNameString);
            URL realUrl = new URL(urlNameString);
            URLConnection connection = realUrl.openConnection();
            connection.setRequestProperty("accept", "*/*");
            connection.setRequestProperty("connection", "Keep-Alive");
            connection.setRequestProperty("user-agent", "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1;SV1)");
            connection.connect();
            in = new BufferedReader(new InputStreamReader(connection.getInputStream(), contentType));
            String line;
            while ((line = in.readLine()) != null) {
                result.append(line);
            }
            log.info("recv - {}", result);
        } catch (ConnectException e) {
            log.error("调用HttpUtils.sendGet ConnectException, url=" + url + ",param=" + param, e);
        } catch (SocketTimeoutException e) {
            log.error("调用HttpUtils.sendGet SocketTimeoutException, url=" + url + ",param=" + param, e);
        } catch (IOException e) {
            log.error("调用HttpUtils.sendGet IOException, url=" + url + ",param=" + param, e);
        } catch (Exception e) {
            log.error("调用HttpsUtil.sendGet Exception, url=" + url + ",param=" + param, e);
        } finally {
            try {
                if (in != null) {
                    in.close();
                }
            } catch (Exception ex) {
                log.error("调用in.close Exception, url=" + url + ",param=" + param, ex);
            }
        }
        return result.toString();
    }

    /**
     * 向指定 URL 发送POST方法的请求
     *
     * @param url
     *            发送请求的 URL
     * @param param
     *            请求参数，请求参数应该是 name1=value1&name2=value2 的形式。
     * @return 所代表远程资源的响应结果
     */
    public static String sendPost(String url, String param) {
        PrintWriter out = null;
        BufferedReader in = null;
        StringBuilder result = new StringBuilder();
        try {
            log.info("sendPost - {}", url);
            URL realUrl = new URL(url);
            URLConnection conn = realUrl.openConnection();
            conn.setRequestProperty("accept", "*/*");
            conn.setRequestProperty("connection", "Keep-Alive");
            conn.setRequestProperty("user-agent", "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1;SV1)");
            conn.setRequestProperty("Accept-Charset", "utf-8");
            conn.setRequestProperty("contentType", "utf-8");
            conn.setDoOutput(true);
            conn.setDoInput(true);
            out = new PrintWriter(conn.getOutputStream());
            out.print(param);
            out.flush();
            in = new BufferedReader(new InputStreamReader(conn.getInputStream(), StandardCharsets.UTF_8));
            String line;
            while ((line = in.readLine()) != null) {
                result.append(line);
            }
            log.info("recv - {}", result);
        } catch (ConnectException e) {
            log.error("调用HttpUtils.sendPost ConnectException, url=" + url + ",param=" + param, e);
        } catch (SocketTimeoutException e) {
            log.error("调用HttpUtils.sendPost SocketTimeoutException, url=" + url + ",param=" + param, e);
        } catch (IOException e) {
            log.error("调用HttpUtils.sendPost IOException, url=" + url + ",param=" + param, e);
        } catch (Exception e) {
            log.error("调用HttpsUtil.sendPost Exception, url=" + url + ",param=" + param, e);
        } finally {
            try {
                if (out != null) {
                    out.close();
                }
                if (in != null) {
                    in.close();
                }
            } catch (IOException ex) {
                log.error("调用in.close Exception, url=" + url + ",param=" + param, ex);
            }
        }
        return result.toString();
    }

    public static String sendSSLPost(String url, String param) {
        StringBuilder result = new StringBuilder();
        String urlNameString = url + "?" + param;
        try {
            log.info("sendSSLPost - {}", urlNameString);
            SSLContext sc = SSLContext.getInstance("SSL");
            sc.init(null, new TrustManager[]{new TrustAnyTrustManager()}, new java.security.SecureRandom());
            URL console = new URL(urlNameString);
            HttpsURLConnection conn = (HttpsURLConnection) console.openConnection();
            conn.setRequestProperty("accept", "*/*");
            conn.setRequestProperty("connection", "Keep-Alive");
            conn.setRequestProperty("user-agent", "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1;SV1)");
            conn.setRequestProperty("Accept-Charset", "utf-8");
            conn.setRequestProperty("contentType", "utf-8");
            conn.setDoOutput(true);
            conn.setDoInput(true);

            conn.setSSLSocketFactory(sc.getSocketFactory());
            conn.setHostnameVerifier(new TrustAnyHostnameVerifier());
            conn.connect();
            InputStream is = conn.getInputStream();
            BufferedReader br = new BufferedReader(new InputStreamReader(is));
            String ret = "";
            while ((ret = br.readLine()) != null) {
                if (ret != null && !"".equals(ret.trim())) {
                    result.append(new String(ret.getBytes(StandardCharsets.ISO_8859_1), StandardCharsets.UTF_8));
                }
            }
            log.info("recv - {}", result);
            conn.disconnect();
            br.close();
        } catch (ConnectException e) {
            log.error("调用HttpUtils.sendSSLPost ConnectException, url=" + url + ",param=" + param, e);
        } catch (SocketTimeoutException e) {
            log.error("调用HttpUtils.sendSSLPost SocketTimeoutException, url=" + url + ",param=" + param, e);
        } catch (IOException e) {
            log.error("调用HttpUtils.sendSSLPost IOException, url=" + url + ",param=" + param, e);
        } catch (Exception e) {
            log.error("调用HttpsUtil.sendSSLPost Exception, url=" + url + ",param=" + param, e);
        }
        return result.toString();
    }

    private static class TrustAnyTrustManager implements X509TrustManager {
        @Override
        public void checkClientTrusted(X509Certificate[] chain, String authType) {
        }

        @Override
        public void checkServerTrusted(X509Certificate[] chain, String authType) {
        }

        @Override
        public X509Certificate[] getAcceptedIssuers() {
            return new X509Certificate[]{};
        }
    }

    private static class TrustAnyHostnameVerifier implements HostnameVerifier {
        @Override
        public boolean verify(String hostname, SSLSession session) {
            return true;
        }
    }
}


--- File: play-common/src/main/java/com/starry/common/utils/IdUtils.java ---
package com.starry.common.utils;

import cn.hutool.core.util.RandomUtil;

/**
 * @author admin
 * @since 2024/5/13 下午11:07
 **/
public class IdUtils {

    /**
     * 生成8位的UUID
     *
     * @return UUID
     */
    public static String getUuid() {
        return RandomUtil.randomString(9).toLowerCase();
    }
}


--- File: play-common/src/main/java/com/starry/common/utils/RegionUtils.java ---
package com.starry.common.utils;

/**
 * @author admin
 * @since 2024/7/25 下午3:15
 **/

import java.io.File;
import java.io.InputStream;
import java.lang.reflect.Method;
import java.util.Objects;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.FileUtils;
import org.lionsoul.ip2region.DataBlock;
import org.lionsoul.ip2region.DbConfig;
import org.lionsoul.ip2region.DbSearcher;
import org.lionsoul.ip2region.Util;
import org.springframework.core.io.ClassPathResource;

/**
 * 根据ip离线查询地址
 *
 * @author admin
 */
@Slf4j
public class RegionUtils {

    private static final String JAVA_TEMP_DIR = "java.io.tmpdir";

    static DbConfig config = null;
    static DbSearcher searcher = null;

    /**
     * 初始化IP库
     */
    static {
        try {
            // 因为jar无法读取文件,复制创建临时文件
            String dbPath = Objects.requireNonNull(RegionUtils.class.getResource("/ip2region/ip2region.db")).getPath();
            File file = new File(dbPath);
            if (!file.exists()) {
                String tmpDir = System.getProperties().getProperty(JAVA_TEMP_DIR);
                dbPath = tmpDir + "ip2region.db";
                file = new File(dbPath);
                ClassPathResource cpr = new ClassPathResource("ip2region" + File.separator + "ip2region.db");
                InputStream resourceAsStream = cpr.getInputStream();
                FileUtils.copyInputStreamToFile(resourceAsStream, file);
            }
            config = new DbConfig();
            searcher = new DbSearcher(config, dbPath);
            log.info("bean [{}]", config);
            log.info("bean [{}]", searcher);
        } catch (Exception e) {
            log.error("init ip region error:", e);
        }
    }

    /**
     * 解析IP
     *
     * @param ip
     * @return
     */
    public static String getRegion(String ip) {
        try {
            // db
            if (searcher == null || StringUtils.isEmpty(ip)) {
                log.error("DbSearcher is null");
                return StringUtils.EMPTY;
            }
            long startTime = System.currentTimeMillis();
            // 查询算法
            int algorithm = DbSearcher.MEMORY_ALGORITYM;
            Method method = null;
            switch (algorithm) {
                case DbSearcher.BTREE_ALGORITHM :
                    method = searcher.getClass().getMethod("btreeSearch", String.class);
                    break;
                case DbSearcher.BINARY_ALGORITHM :
                    method = searcher.getClass().getMethod("binarySearch", String.class);
                    break;
                case DbSearcher.MEMORY_ALGORITYM :
                    method = searcher.getClass().getMethod("memorySearch", String.class);
                    break;
                default :
                    return StringUtils.EMPTY;
            }

            DataBlock dataBlock = null;
            if (!Util.isIpAddress(ip)) {
                log.warn("warning: Invalid ip address");
            }
            dataBlock = (DataBlock) method.invoke(searcher, ip);
            String result = dataBlock.getRegion();
            long endTime = System.currentTimeMillis();
            log.debug("region use time[{}] result[{}]", endTime - startTime, result);
            return result;

        } catch (Exception e) {
            log.error("error:", e);
        }
        return StringUtils.EMPTY;
    }

}


--- File: play-common/src/main/java/com/starry/common/utils/ServletUtils.java ---
package com.starry.common.utils;

import cn.hutool.core.convert.Convert;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.net.URLEncoder;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

/**
 * 客户端工具类
 *
 * @author ruoyi
 */
public class ServletUtils {
    /**
     * 获取String参数
     */
    public static String getParameter(String name) {
        return getRequest().getParameter(name);
    }

    /**
     * 获取String参数
     */
    public static String getParameter(String name, String defaultValue) {
        return Convert.toStr(getRequest().getParameter(name), defaultValue);
    }

    /**
     * 获取Integer参数
     */
    public static Integer getParameterToInt(String name) {
        return Convert.toInt(getRequest().getParameter(name));
    }

    /**
     * 获取Integer参数
     */
    public static Integer getParameterToInt(String name, Integer defaultValue) {
        return Convert.toInt(getRequest().getParameter(name), defaultValue);
    }

    /**
     * 获取Boolean参数
     */
    public static Boolean getParameterToBool(String name) {
        return Convert.toBool(getRequest().getParameter(name));
    }

    /**
     * 获取Boolean参数
     */
    public static Boolean getParameterToBool(String name, Boolean defaultValue) {
        return Convert.toBool(getRequest().getParameter(name), defaultValue);
    }

    /**
     * 获取request
     */
    public static HttpServletRequest getRequest() {
        return getRequestAttributes().getRequest();
    }

    /**
     * 获取response
     */
    public static HttpServletResponse getResponse() {
        return getRequestAttributes().getResponse();
    }

    /**
     * 获取session
     */
    public static HttpSession getSession() {
        return getRequest().getSession();
    }

    public static ServletRequestAttributes getRequestAttributes() {
        RequestAttributes attributes = RequestContextHolder.getRequestAttributes();
        return (ServletRequestAttributes) attributes;
    }

    /**
     * 将字符串渲染到客户端
     *
     * @param response
     *            渲染对象
     * @param string
     *            待渲染的字符串
     */
    public static void renderString(HttpServletResponse response, String string) {
        try {
            response.setStatus(200);
            response.setContentType("application/json");
            response.setCharacterEncoding("utf-8");
            response.getWriter().print(string);
        } catch (IOException e) {

        }
    }

    /**
     * 是否是Ajax异步请求
     *
     * @param request
     */
    /*
     * public static boolean isAjaxRequest(HttpServletRequest request) { String
     * accept = request.getHeader("accept"); if (accept != null &&
     * accept.contains("application/json")) { return true; }
     *
     * String xRequestedWith = request.getHeader("X-Requested-With"); if
     * (xRequestedWith != null && xRequestedWith.contains("XMLHttpRequest")) {
     * return true; }
     *
     * String uri = request.getRequestURI(); if (StringUtils.inStringIgnoreCase(uri,
     * ".json", ".xml")) { return true; }
     *
     * String ajax = request.getParameter("__ajax"); return
     * StringUtils.inStringIgnoreCase(ajax, "json", "xml"); }
     */

    /**
     * 内容编码
     *
     * @param str
     *            内容
     * @return 编码后的内容
     */
    public static String urlEncode(String str) {
        try {
            return URLEncoder.encode(str, "UTF-8");
        } catch (UnsupportedEncodingException e) {
            return StringUtils.EMPTY;
        }
    }

    /**
     * 内容解码
     *
     * @param str
     *            内容
     * @return 解码后的内容
     */
    public static String urlDecode(String str) {
        try {
            return URLDecoder.decode(str, "UTF-8");
        } catch (UnsupportedEncodingException e) {
            return StringUtils.EMPTY;
        }
    }
}


--- File: play-common/src/main/java/com/starry/common/utils/SpringUtils.java ---
package com.starry.common.utils;

import org.springframework.aop.framework.AopContext;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;

/**
 * @author admin spring工具类 方便在非spring管理环境中获取bean
 * @since 2022/7/28
 */
@Component
public class SpringUtils implements BeanFactoryPostProcessor, ApplicationContextAware {

    /**
     * Spring应用上下文环境
     */
    private static ConfigurableListableBeanFactory beanFactory;

    private static ApplicationContext applicationContext;

    /**
     * 获取类型为requiredType的对象
     *
     * @param clz
     * @return
     * @throws BeansException
     */
    public static <T> T getBean(Class<T> clz) throws BeansException {
        T result = (T) beanFactory.getBean(clz);
        return result;
    }

    /**
     * 获取对象
     *
     * @param name
     * @return Object 一个以所给名字注册的bean的实例
     * @throws BeansException
     */
    @SuppressWarnings("unchecked")
    public static <T> T getBean(String name) throws BeansException {
        return (T) beanFactory.getBean(name);
    }

    /**
     * 获取aop代理对象
     *
     * @param invoker
     * @return
     */
    @SuppressWarnings("unchecked")
    public static <T> T getAopProxy(T invoker) {
        return (T) AopContext.currentProxy();
    }

    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
        SpringUtils.beanFactory = beanFactory;
    }

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        SpringUtils.applicationContext = applicationContext;
    }
}


--- File: play-common/src/main/java/com/starry/common/utils/StringUtils.java ---
package com.starry.common.utils;

import cn.hutool.core.text.StrFormatter;
import java.util.Collection;
import java.util.Map;

/**
 * @author admin 字符串工具类
 * @since 2022/7/19
 */
public class StringUtils extends org.apache.commons.lang3.StringUtils {

    /**
     * 空字符串
     */
    private static final String NULLSTR = "";

    /**
     * 下划线
     */
    private static final char SEPARATOR = '_';

    /**
     * 驼峰转下划线命名
     */
    public static String toUnderScoreCase(String str) {
        if (str == null) {
            return null;
        }
        StringBuilder sb = new StringBuilder();
        // 前置字符是否大写
        boolean preCharIsUpperCase = true;
        // 当前字符是否大写
        boolean curreCharIsUpperCase = true;
        // 下一字符是否大写
        boolean nexteCharIsUpperCase = true;
        for (int i = 0; i < str.length(); i++) {
            char c = str.charAt(i);
            if (i > 0) {
                preCharIsUpperCase = Character.isUpperCase(str.charAt(i - 1));
            } else {
                preCharIsUpperCase = false;
            }

            curreCharIsUpperCase = Character.isUpperCase(c);

            if (i < (str.length() - 1)) {
                nexteCharIsUpperCase = Character.isUpperCase(str.charAt(i + 1));
            }

            if (preCharIsUpperCase && curreCharIsUpperCase && !nexteCharIsUpperCase) {
                sb.append(SEPARATOR);
            } else if ((i != 0 && !preCharIsUpperCase) && curreCharIsUpperCase) {
                sb.append(SEPARATOR);
            }
            sb.append(Character.toLowerCase(c));
        }

        return sb.toString();
    }

    /**
     * 判断一个对象是否为空
     *
     * @param object
     *            Object
     * @return true：为空 false：非空
     */
    public static boolean isNull(Object object) {
        return object == null;
    }

    /**
     * 判断一个对象是否非空
     *
     * @param object
     *            Object
     * @return true：非空 false：空
     */
    public static boolean isNotNull(Object object) {
        return !isNull(object);
    }

    /**
     * 判断一个对象数组是否为空
     *
     * @param objects
     *            要判断的对象数组 * @return true：为空 false：非空
     */
    public static boolean isEmpty(Object[] objects) {
        return isNull(objects) || (objects.length == 0);
    }

    /**
     * 判断一个Map是否为空
     *
     * @param map
     *            要判断的Map
     * @return true：为空 false：非空
     */
    public static boolean isEmpty(Map<?, ?> map) {
        return isNull(map) || map.isEmpty();
    }

    /**
     * 判断一个Map是否为空
     *
     * @param map
     *            要判断的Map
     * @return true：非空 false：空
     */
    public static boolean isNotEmpty(Map<?, ?> map) {
        return !isEmpty(map);
    }

    /**
     * 判断一个Collection是否非空，包含List，Set，Queue
     *
     * @param coll
     *            要判断的Collection
     * @return true：非空 false：空
     */
    public static boolean isNotEmpty(Collection<?> coll) {
        return !isEmpty(coll);
    }

    /**
     * 判断一个Collection是否为空， 包含List，Set，Queue
     *
     * @param coll
     *            要判断的Collection
     * @return true：为空 false：非空
     */
    public static boolean isEmpty(Collection<?> coll) {
        return isNull(coll) || coll.isEmpty();
    }

    /**
     * 格式化文本, {} 表示占位符<br>
     * 此方法只是简单将占位符 {} 按照顺序替换为参数<br>
     * 如果想输出 {} 使用 \\转义 { 即可，如果想输出 {} 之前的 \ 使用双转义符 \\\\ 即可<br>
     * 例：<br>
     * 通常使用：format("this is {} for {}", "a", "b") -> this is a for b<br>
     * 转义{}： format("this is \\{} for {}", "a", "b") -> this is \{} for a<br>
     * 转义\： format("this is \\\\{} for {}", "a", "b") -> this is \a for b<br>
     *
     * @param template
     *            文本模板，被替换的部分用 {} 表示
     * @param params
     *            参数值
     * @return 格式化后的文本
     */
    public static String format(String template, Object... params) {
        if (isEmpty(params) || isEmpty(template)) {
            return template;
        }
        return StrFormatter.format(template, params);
    }

    /**
     * 判断给定的set列表中是否包含数组array 判断给定的数组array中是否包含给定的元素value
     *
     * @param collection
     *            给定的集合
     * @param array
     *            给定的数组
     * @return boolean 结果
     */
    public static boolean containsAny(Collection<String> collection, String... array) {
        if (isEmpty(collection) || isEmpty(array)) {
            return false;
        } else {
            for (String str : array) {
                if (collection.contains(str)) {
                    return true;
                }
            }
            return false;
        }
    }

    /**
     * 驼峰式命名法 例如：user_name->userName
     */
    public static String toCamelCase(String s) {
        if (org.apache.commons.lang3.StringUtils.isNotBlank(s)) {
            // 小写
            s = s.toLowerCase();
            StringBuilder sb = new StringBuilder(s.length());
            // 大写
            boolean upperCase = false;
            for (int i = 0; i < s.length(); i++) {
                char c = s.charAt(i);
                if (c == '_') {
                    upperCase = true;
                } else if (upperCase) {
                    sb.append(Character.toUpperCase(c));
                    upperCase = false;
                } else {
                    sb.append(c);
                }
            }
            return sb.toString();
        }
        return s;
    }

    /**
     * 将下划线大写方式命名的字符串转换为驼峰式。如果转换前的下划线大写方式命名的字符串为空，则返回空字符串。 例如：HELLO_WORLD ->
     * HelloWorld
     *
     * @param name
     *            转换前的下划线大写方式命名的字符串
     * @return 转换后的驼峰式命名的字符串
     */
    public static String convertToCamelCase(String name) {
        StringBuilder result = new StringBuilder();
        if (org.apache.commons.lang3.StringUtils.isBlank(name)) {
            return "";
        } else if (!name.contains("_")) {
            // 不含下划线，仅将首字母大写
            return name.substring(0, 1).toUpperCase() + name.substring(1);
        }
        String[] camels = name.split("_");
        for (String camel : camels) {
            // 跳过原始字符串中开头、结尾的下换线或双重下划线
            if (camel.isEmpty()) {
                continue;
            }
            // 首字母大写
            result.append(camel.substring(0, 1).toUpperCase());
            result.append(camel.substring(1).toLowerCase());
        }
        return result.toString();
    }

    public static <T> T cast(Object obj) {
        return (T) obj;
    }
}


--- File: play-common/src/main/java/com/starry/common/utils/ThreadsUtils.java ---
package com.starry.common.utils;

import java.util.concurrent.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * @author admin 线程相关工具类
 * @since 2022/7/25
 */
public class ThreadsUtils {
    private static final Logger logger = LoggerFactory.getLogger(ThreadsUtils.class);

    /**
     * 停止线程池 先使用shutdown, 停止接收新任务并尝试完成所有已存在任务. 如果超时, 则调用shutdownNow,
     * 取消在workQueue中Pending的任务,并中断所有阻塞函数. 如果仍然超時，則強制退出. 另对在shutdown时线程本身被调用中断做了处理.
     */
    public static void shutdownAndAwaitTermination(ExecutorService pool) {
        if (pool != null && !pool.isShutdown()) {
            pool.shutdown();
            try {
                if (!pool.awaitTermination(120, TimeUnit.SECONDS)) {
                    pool.shutdownNow();
                    if (!pool.awaitTermination(120, TimeUnit.SECONDS)) {
                        logger.info("Pool did not terminate");
                    }
                }
            } catch (InterruptedException ie) {
                pool.shutdownNow();
                Thread.currentThread().interrupt();
            }
        }
    }

    /**
     * 打印线程异常信息
     */
    public static void printException(Runnable r, Throwable t) {
        if (t == null && r instanceof Future<?>) {
            try {
                Future<?> future = (Future<?>) r;
                if (future.isDone()) {
                    future.get();
                }
            } catch (CancellationException ce) {
                t = ce;
            } catch (ExecutionException ee) {
                t = ee.getCause();
            } catch (InterruptedException ie) {
                Thread.currentThread().interrupt();
            }
        }
        if (t != null) {
            logger.error(t.getMessage(), t);
        }
    }
}


--- File: play-common/src/main/java/com/starry/common/utils/VerificationCodeUtils.java ---
package com.starry.common.utils;

/**
 * @author admin
 * @since 2024/4/9 13:53
 **/
public class VerificationCodeUtils {

    public static String getVerificationCode(int codeLength) {
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < codeLength; i++) {
            sb.append((int) (Math.random() * 10));
        }
        return sb.toString();
    }
}


--- File: play-common/src/main/java/com/starry/common/utils/file/FileUploadUtils.java ---
package com.starry.common.utils.file;

import java.util.Objects;
import org.springframework.web.multipart.MultipartFile;

/**
 * @author admin 文件上传工具类
 * @since 2022/10/28
 */
public class FileUploadUtils {

    /**
     * 默认大小 50M
     */
    public static final long DEFAULT_MAX_SIZE = 50 * 1024 * 1024;

    /**
     * 默认的文件名最大长度 100
     */
    public static final int DEFAULT_FILE_NAME_LENGTH = 100;

    public static String upload(String baseDir, MultipartFile file, String[] allowedExtension) {
        int fileNameLength = Objects.requireNonNull(file.getOriginalFilename()).length();
        if (fileNameLength > FileUploadUtils.DEFAULT_FILE_NAME_LENGTH) {
            throw new RuntimeException("文件名超过默认的最大长度");
        }
        return null;
    }

}


--- File: play-common/src/main/java/com/starry/common/utils/html/EscapeUtil.java ---
package com.starry.common.utils.html;

import com.starry.common.utils.StringUtils;

/**
 * 转义和反转义工具类
 *
 * @author ruoyi
 */
public class EscapeUtil {
    public static final String RE_HTML_MARK = "(<[^<]*?>)|(<[\\s]*?/[^<]*?>)|(<[^<]*?/[\\s]*?>)";

    private static final char[][] TEXT = new char[64][];

    static {
        for (int i = 0; i < 64; i++) {
            TEXT[i] = new char[]{(char) i};
        }

        // special HTML characters
        TEXT['\''] = "&#039;".toCharArray(); // 单引号
        TEXT['"'] = "&#34;".toCharArray(); // 单引号
        TEXT['&'] = "&#38;".toCharArray(); // &符
        TEXT['<'] = "&#60;".toCharArray(); // 小于号
        TEXT['>'] = "&#62;".toCharArray(); // 大于号
    }

    /**
     * 转义文本中的HTML字符为安全的字符
     *
     * @param text
     *            被转义的文本
     * @return 转义后的文本
     */
    public static String escape(String text) {
        return encode(text);
    }

    /**
     * 还原被转义的HTML特殊字符
     *
     * @param content
     *            包含转义符的HTML内容
     * @return 转换后的字符串
     */
    public static String unescape(String content) {
        return decode(content);
    }

    /**
     * 清除所有HTML标签，但是不删除标签内的内容
     *
     * @param content
     *            文本
     * @return 清除标签后的文本
     */
    public static String clean(String content) {
        return new HTMLFilter().filter(content);
    }

    /**
     * Escape编码
     *
     * @param text
     *            被编码的文本
     * @return 编码后的字符
     */
    private static String encode(String text) {
        int len;
        if ((text == null) || ((len = text.length()) == 0)) {
            return StringUtils.EMPTY;
        }
        StringBuilder buffer = new StringBuilder(len + (len >> 2));
        char c;
        for (int i = 0; i < len; i++) {
            c = text.charAt(i);
            if (c < 64) {
                buffer.append(TEXT[c]);
            } else {
                buffer.append(c);
            }
        }
        return buffer.toString();
    }

    /**
     * Escape解码
     *
     * @param content
     *            被转义的内容
     * @return 解码后的字符串
     */
    public static String decode(String content) {
        if (StringUtils.isEmpty(content)) {
            return content;
        }

        StringBuilder tmp = new StringBuilder(content.length());
        int lastPos = 0, pos = 0;
        char ch;
        while (lastPos < content.length()) {
            pos = content.indexOf("%", lastPos);
            if (pos == lastPos) {
                if (content.charAt(pos + 1) == 'u') {
                    ch = (char) Integer.parseInt(content.substring(pos + 2, pos + 6), 16);
                    tmp.append(ch);
                    lastPos = pos + 6;
                } else {
                    ch = (char) Integer.parseInt(content.substring(pos + 1, pos + 3), 16);
                    tmp.append(ch);
                    lastPos = pos + 3;
                }
            } else {
                if (pos == -1) {
                    tmp.append(content.substring(lastPos));
                    lastPos = content.length();
                } else {
                    tmp.append(content, lastPos, pos);
                    lastPos = pos;
                }
            }
        }
        return tmp.toString();
    }

    public static void main(String[] args) {
        String html = "<script>alert(1);</script>";
        // String html = "<scr<script>ipt>alert(\"XSS\")</scr<script>ipt>";
        // String html = "<123";
        // String html = "123>";
        System.out.println(EscapeUtil.clean(html));
        System.out.println(EscapeUtil.escape(html));
        System.out.println(EscapeUtil.unescape(html));
    }
}


--- File: play-common/src/main/java/com/starry/common/utils/html/HTMLFilter.java ---
package com.starry.common.utils.html;

import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * HTML过滤器，用于去除XSS漏洞隐患。
 *
 * @author ruoyi
 */
public final class HTMLFilter {
    /**
     * regex flag union representing /si modifiers in php
     **/
    private static final int REGEX_FLAGS_SI = Pattern.CASE_INSENSITIVE | Pattern.DOTALL;
    private static final Pattern P_COMMENTS = Pattern.compile("<!--(.*?)-->", Pattern.DOTALL);
    private static final Pattern P_COMMENT = Pattern.compile("^!--(.*)--$", REGEX_FLAGS_SI);
    private static final Pattern P_TAGS = Pattern.compile("<(.*?)>", Pattern.DOTALL);
    private static final Pattern P_END_TAG = Pattern.compile("^/([a-z0-9]+)", REGEX_FLAGS_SI);
    private static final Pattern P_START_TAG = Pattern.compile("^([a-z0-9]+)(.*?)(/?)$", REGEX_FLAGS_SI);
    private static final Pattern P_QUOTED_ATTRIBUTES = Pattern.compile("([a-z0-9]+)=([\"'])(.*?)\\2", REGEX_FLAGS_SI);
    private static final Pattern P_UNQUOTED_ATTRIBUTES = Pattern.compile("([a-z0-9]+)(=)([^\"\\s']+)", REGEX_FLAGS_SI);
    private static final Pattern P_PROTOCOL = Pattern.compile("^([^:]+):", REGEX_FLAGS_SI);
    private static final Pattern P_ENTITY = Pattern.compile("&#(\\d+);?");
    private static final Pattern P_ENTITY_UNICODE = Pattern.compile("&#x([0-9a-f]+);?");
    private static final Pattern P_ENCODE = Pattern.compile("%([0-9a-f]{2});?");
    private static final Pattern P_VALID_ENTITIES = Pattern.compile("&([^&;]*)(?=(;|&|$))");
    private static final Pattern P_VALID_QUOTES = Pattern.compile("(>|^)([^<]+?)(<|$)", Pattern.DOTALL);
    private static final Pattern P_END_ARROW = Pattern.compile("^>");
    private static final Pattern P_BODY_TO_END = Pattern.compile("<([^>]*?)(?=<|$)");
    private static final Pattern P_XML_CONTENT = Pattern.compile("(^|>)([^<]*?)(?=>)");
    private static final Pattern P_STRAY_LEFT_ARROW = Pattern.compile("<([^>]*?)(?=<|$)");
    private static final Pattern P_STRAY_RIGHT_ARROW = Pattern.compile("(^|>)([^<]*?)(?=>)");
    private static final Pattern P_AMP = Pattern.compile("&");
    private static final Pattern P_QUOTE = Pattern.compile("\"");
    private static final Pattern P_LEFT_ARROW = Pattern.compile("<");
    private static final Pattern P_RIGHT_ARROW = Pattern.compile(">");
    private static final Pattern P_BOTH_ARROWS = Pattern.compile("<>");

    // @xxx could grow large... maybe use sesat's ReferenceMap
    private static final ConcurrentMap<String, Pattern> P_REMOVE_PAIR_BLANKS = new ConcurrentHashMap<>();
    private static final ConcurrentMap<String, Pattern> P_REMOVE_SELF_BLANKS = new ConcurrentHashMap<>();

    /**
     * set of allowed html elements, along with allowed attributes for each element
     **/
    private final Map<String, List<String>> vAllowed;
    /**
     * counts of open tags for each (allowable) html element
     **/
    private final Map<String, Integer> vTagCounts = new HashMap<>();

    /**
     * html elements which must always be self-closing (e.g. "<img />")
     **/
    private final String[] vSelfClosingTags;
    /**
     * html elements which must always have separate opening and closing tags (e.g.
     * "<b></b>")
     **/
    private final String[] vNeedClosingTags;
    /**
     * set of disallowed html elements
     **/
    private final String[] vDisallowed;
    /**
     * attributes which should be checked for valid protocols
     **/
    private final String[] vProtocolAtts;
    /**
     * allowed protocols
     **/
    private final String[] vAllowedProtocols;
    /**
     * tags which should be removed if they contain no content (e.g. "<b></b>" or
     * "<b />")
     **/
    private final String[] vRemoveBlanks;
    /**
     * entities allowed within html markup
     **/
    private final String[] vAllowedEntities;
    /**
     * flag determining whether comments are allowed in input String.
     */
    private final boolean stripComment;
    private final boolean encodeQuotes;
    /**
     * flag determining whether to try to make tags when presented with "unbalanced"
     * angle brackets (e.g. "<b text </b>" becomes "<b> text </b>"). If set to
     * false, unbalanced angle brackets will be html escaped.
     */
    private final boolean alwaysMakeTags;

    /**
     * Default constructor.
     */
    public HTMLFilter() {
        vAllowed = new HashMap<>();

        final ArrayList<String> a_atts = new ArrayList<>();
        a_atts.add("href");
        a_atts.add("target");
        vAllowed.put("a", a_atts);

        final ArrayList<String> img_atts = new ArrayList<>();
        img_atts.add("src");
        img_atts.add("width");
        img_atts.add("height");
        img_atts.add("alt");
        vAllowed.put("img", img_atts);

        final ArrayList<String> no_atts = new ArrayList<>();
        vAllowed.put("b", no_atts);
        vAllowed.put("strong", no_atts);
        vAllowed.put("i", no_atts);
        vAllowed.put("em", no_atts);

        vSelfClosingTags = new String[]{"img"};
        vNeedClosingTags = new String[]{"a", "b", "strong", "i", "em"};
        vDisallowed = new String[]{};
        vAllowedProtocols = new String[]{"http", "mailto", "https"}; // no ftp.
        vProtocolAtts = new String[]{"src", "href"};
        vRemoveBlanks = new String[]{"a", "b", "strong", "i", "em"};
        vAllowedEntities = new String[]{"amp", "gt", "lt", "quot"};
        stripComment = true;
        encodeQuotes = true;
        alwaysMakeTags = false;
    }

    /**
     * Map-parameter configurable constructor.
     *
     * @param conf
     *            map containing configuration. keys match field names.
     */
    @SuppressWarnings("unchecked")
    public HTMLFilter(final Map<String, Object> conf) {

        assert conf.containsKey("vAllowed") : "configuration requires vAllowed";
        assert conf.containsKey("vSelfClosingTags") : "configuration requires vSelfClosingTags";
        assert conf.containsKey("vNeedClosingTags") : "configuration requires vNeedClosingTags";
        assert conf.containsKey("vDisallowed") : "configuration requires vDisallowed";
        assert conf.containsKey("vAllowedProtocols") : "configuration requires vAllowedProtocols";
        assert conf.containsKey("vProtocolAtts") : "configuration requires vProtocolAtts";
        assert conf.containsKey("vRemoveBlanks") : "configuration requires vRemoveBlanks";
        assert conf.containsKey("vAllowedEntities") : "configuration requires vAllowedEntities";

        vAllowed = Collections.unmodifiableMap((HashMap<String, List<String>>) conf.get("vAllowed"));
        vSelfClosingTags = (String[]) conf.get("vSelfClosingTags");
        vNeedClosingTags = (String[]) conf.get("vNeedClosingTags");
        vDisallowed = (String[]) conf.get("vDisallowed");
        vAllowedProtocols = (String[]) conf.get("vAllowedProtocols");
        vProtocolAtts = (String[]) conf.get("vProtocolAtts");
        vRemoveBlanks = (String[]) conf.get("vRemoveBlanks");
        vAllowedEntities = (String[]) conf.get("vAllowedEntities");
        stripComment = conf.containsKey("stripComment") ? (Boolean) conf.get("stripComment") : true;
        encodeQuotes = conf.containsKey("encodeQuotes") ? (Boolean) conf.get("encodeQuotes") : true;
        alwaysMakeTags = conf.containsKey("alwaysMakeTags") ? (Boolean) conf.get("alwaysMakeTags") : true;
    }

    // ---------------------------------------------------------------
    // my versions of some PHP library functions
    public static String chr(final int decimal) {
        return String.valueOf((char) decimal);
    }

    public static String htmlSpecialChars(final String s) {
        String result = s;
        result = regexReplace(P_AMP, "&amp;", result);
        result = regexReplace(P_QUOTE, "&quot;", result);
        result = regexReplace(P_LEFT_ARROW, "&lt;", result);
        result = regexReplace(P_RIGHT_ARROW, "&gt;", result);
        return result;
    }

    private static String regexReplace(final Pattern regex_pattern, final String replacement, final String s) {
        Matcher m = regex_pattern.matcher(s);
        return m.replaceAll(replacement);
    }

    // ---------------------------------------------------------------

    private static boolean inArray(final String s, final String[] array) {
        for (String item : array) {
            if (item != null && item.equals(s)) {
                return true;
            }
        }
        return false;
    }

    private void reset() {
        vTagCounts.clear();
    }

    /**
     * given a user submitted input String, filter out any invalid or restricted
     * html.
     *
     * @param input
     *            text (i.e. submitted by a user) than may contain html
     * @return "clean" version of input, with only valid, whitelisted html elements
     *         allowed
     */
    public String filter(final String input) {
        reset();
        String s = input;

        s = escapeComments(s);

        s = balanceHTML(s);

        s = checkTags(s);

        s = processRemoveBlanks(s);

        // s = validateEntities(s);

        return s;
    }

    public boolean isAlwaysMakeTags() {
        return alwaysMakeTags;
    }

    public boolean isStripComments() {
        return stripComment;
    }

    private String escapeComments(final String s) {
        final Matcher m = P_COMMENTS.matcher(s);
        final StringBuffer buf = new StringBuffer();
        if (m.find()) {
            final String match = m.group(1); // (.*?)
            m.appendReplacement(buf, Matcher.quoteReplacement("<!--" + htmlSpecialChars(match) + "-->"));
        }
        m.appendTail(buf);

        return buf.toString();
    }

    private String balanceHTML(String s) {
        if (alwaysMakeTags) {
            //
            // try and form html
            //
            s = regexReplace(P_END_ARROW, "", s);
            // 不追加结束标签
            s = regexReplace(P_BODY_TO_END, "<$1>", s);
            s = regexReplace(P_XML_CONTENT, "$1<$2", s);

        } else {
            //
            // escape stray brackets
            //
            s = regexReplace(P_STRAY_LEFT_ARROW, "&lt;$1", s);
            s = regexReplace(P_STRAY_RIGHT_ARROW, "$1$2&gt;<", s);

            //
            // the last regexp causes '<>' entities to appear
            // (we need to do a lookahead assertion so that the last bracket can
            // be used in the next pass of the regexp)
            //
            s = regexReplace(P_BOTH_ARROWS, "", s);
        }

        return s;
    }

    private String checkTags(String s) {
        Matcher m = P_TAGS.matcher(s);

        final StringBuffer buf = new StringBuffer();
        while (m.find()) {
            String replaceStr = m.group(1);
            replaceStr = processTag(replaceStr);
            m.appendReplacement(buf, Matcher.quoteReplacement(replaceStr));
        }
        m.appendTail(buf);

        // these get tallied in processTag
        // (remember to reset before subsequent calls to filter method)
        final StringBuilder sBuilder = new StringBuilder(buf.toString());
        for (String key : vTagCounts.keySet()) {
            for (int ii = 0; ii < vTagCounts.get(key); ii++) {
                sBuilder.append("</").append(key).append(">");
            }
        }
        s = sBuilder.toString();

        return s;
    }

    private String processRemoveBlanks(final String s) {
        String result = s;
        for (String tag : vRemoveBlanks) {
            if (!P_REMOVE_PAIR_BLANKS.containsKey(tag)) {
                P_REMOVE_PAIR_BLANKS.putIfAbsent(tag, Pattern.compile("<" + tag + "(\\s[^>]*)?></" + tag + ">"));
            }
            result = regexReplace(P_REMOVE_PAIR_BLANKS.get(tag), "", result);
            if (!P_REMOVE_SELF_BLANKS.containsKey(tag)) {
                P_REMOVE_SELF_BLANKS.putIfAbsent(tag, Pattern.compile("<" + tag + "(\\s[^>]*)?/>"));
            }
            result = regexReplace(P_REMOVE_SELF_BLANKS.get(tag), "", result);
        }

        return result;
    }

    private String processTag(final String s) {
        // ending tags
        Matcher m = P_END_TAG.matcher(s);
        if (m.find()) {
            final String name = m.group(1).toLowerCase();
            if (allowed(name)) {
                if (false == inArray(name, vSelfClosingTags)) {
                    if (vTagCounts.containsKey(name)) {
                        vTagCounts.put(name, vTagCounts.get(name) - 1);
                        return "</" + name + ">";
                    }
                }
            }
        }

        // starting tags
        m = P_START_TAG.matcher(s);
        if (m.find()) {
            final String name = m.group(1).toLowerCase();
            final String body = m.group(2);
            String ending = m.group(3);

            // debug( "in a starting tag, name='" + name + "'; body='" + body + "';
            // ending='" + ending + "'" );
            if (allowed(name)) {
                final StringBuilder params = new StringBuilder();

                final Matcher m2 = P_QUOTED_ATTRIBUTES.matcher(body);
                final Matcher m3 = P_UNQUOTED_ATTRIBUTES.matcher(body);
                final List<String> paramNames = new ArrayList<>();
                final List<String> paramValues = new ArrayList<>();
                while (m2.find()) {
                    paramNames.add(m2.group(1)); // ([a-z0-9]+)
                    paramValues.add(m2.group(3)); // (.*?)
                }
                while (m3.find()) {
                    paramNames.add(m3.group(1)); // ([a-z0-9]+)
                    paramValues.add(m3.group(3)); // ([^\"\\s']+)
                }

                String paramName, paramValue;
                for (int ii = 0; ii < paramNames.size(); ii++) {
                    paramName = paramNames.get(ii).toLowerCase();
                    paramValue = paramValues.get(ii);

                    // debug( "paramName='" + paramName + "'" );
                    // debug( "paramValue='" + paramValue + "'" );
                    // debug( "allowed? " + vAllowed.get( name ).contains( paramName ) );

                    if (allowedAttribute(name, paramName)) {
                        if (inArray(paramName, vProtocolAtts)) {
                            paramValue = processParamProtocol(paramValue);
                        }
                        params.append(' ').append(paramName).append("=\"").append(paramValue).append("\"");
                    }
                }

                if (inArray(name, vSelfClosingTags)) {
                    ending = " /";
                }

                if (inArray(name, vNeedClosingTags)) {
                    ending = "";
                }

                if (ending == null || ending.isEmpty()) {
                    if (vTagCounts.containsKey(name)) {
                        vTagCounts.put(name, vTagCounts.get(name) + 1);
                    } else {
                        vTagCounts.put(name, 1);
                    }
                } else {
                    ending = " /";
                }
                return "<" + name + params + ending + ">";
            } else {
                return "";
            }
        }

        // comments
        m = P_COMMENT.matcher(s);
        if (!stripComment && m.find()) {
            return "<" + m.group() + ">";
        }

        return "";
    }

    private String processParamProtocol(String s) {
        s = decodeEntities(s);
        final Matcher m = P_PROTOCOL.matcher(s);
        if (m.find()) {
            final String protocol = m.group(1);
            if (!inArray(protocol, vAllowedProtocols)) {
                // bad protocol, turn into local anchor link instead
                s = "#" + s.substring(protocol.length() + 1);
                if (s.startsWith("#//")) {
                    s = "#" + s.substring(3);
                }
            }
        }

        return s;
    }

    private String decodeEntities(String s) {
        StringBuffer buf = new StringBuffer();

        Matcher m = P_ENTITY.matcher(s);
        while (m.find()) {
            final String match = m.group(1);
            final int decimal = Integer.decode(match);
            m.appendReplacement(buf, Matcher.quoteReplacement(chr(decimal)));
        }
        m.appendTail(buf);
        s = buf.toString();

        buf = new StringBuffer();
        m = P_ENTITY_UNICODE.matcher(s);
        while (m.find()) {
            final String match = m.group(1);
            final int decimal = Integer.valueOf(match, 16);
            m.appendReplacement(buf, Matcher.quoteReplacement(chr(decimal)));
        }
        m.appendTail(buf);
        s = buf.toString();

        buf = new StringBuffer();
        m = P_ENCODE.matcher(s);
        while (m.find()) {
            final String match = m.group(1);
            final int decimal = Integer.valueOf(match, 16);
            m.appendReplacement(buf, Matcher.quoteReplacement(chr(decimal)));
        }
        m.appendTail(buf);
        s = buf.toString();

        s = validateEntities(s);
        return s;
    }

    private String validateEntities(final String s) {
        StringBuffer buf = new StringBuffer();

        // validate entities throughout the string
        Matcher m = P_VALID_ENTITIES.matcher(s);
        while (m.find()) {
            final String one = m.group(1); // ([^&;]*)
            final String two = m.group(2); // (?=(;|&|$))
            m.appendReplacement(buf, Matcher.quoteReplacement(checkEntity(one, two)));
        }
        m.appendTail(buf);

        return encodeQuotes(buf.toString());
    }

    private String encodeQuotes(final String s) {
        if (encodeQuotes) {
            StringBuffer buf = new StringBuffer();
            Matcher m = P_VALID_QUOTES.matcher(s);
            while (m.find()) {
                final String one = m.group(1); // (>|^)
                final String two = m.group(2); // ([^<]+?)
                final String three = m.group(3); // (<|$)
                // 不替换双引号为&quot;，防止json格式无效 regexReplace(P_QUOTE, "&quot;", two)
                m.appendReplacement(buf, Matcher.quoteReplacement(one + two + three));
            }
            m.appendTail(buf);
            return buf.toString();
        } else {
            return s;
        }
    }

    private String checkEntity(final String preamble, final String term) {

        return ";".equals(term) && isValidEntity(preamble) ? '&' + preamble : "&amp;" + preamble;
    }

    private boolean isValidEntity(final String entity) {
        return inArray(entity, vAllowedEntities);
    }

    private boolean allowed(final String name) {
        return (vAllowed.isEmpty() || vAllowed.containsKey(name)) && !inArray(name, vDisallowed);
    }

    private boolean allowedAttribute(final String name, final String paramName) {
        return allowed(name) && (vAllowed.isEmpty() || vAllowed.get(name).contains(paramName));
    }
}


--- File: play-common/src/main/java/com/starry/common/utils/http/HttpHelper.java ---
package com.starry.common.utils.http;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
import javax.servlet.ServletRequest;
import org.apache.commons.lang3.exception.ExceptionUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * 通用http工具封装
 *
 * @author ruoyi
 */
public class HttpHelper {
    private static final Logger LOGGER = LoggerFactory.getLogger(HttpHelper.class);

    public static String getBodyString(ServletRequest request) {
        StringBuilder sb = new StringBuilder();
        BufferedReader reader = null;
        try (InputStream inputStream = request.getInputStream()) {
            reader = new BufferedReader(new InputStreamReader(inputStream, StandardCharsets.UTF_8));
            String line = "";
            while ((line = reader.readLine()) != null) {
                sb.append(line);
            }
        } catch (IOException e) {
            LOGGER.warn("getBodyString出现问题！");
        } finally {
            if (reader != null) {
                try {
                    reader.close();
                } catch (IOException e) {
                    LOGGER.error(ExceptionUtils.getMessage(e));
                }
            }
        }
        return sb.toString();
    }
}


--- File: play-common/src/main/java/com/starry/common/utils/http/HttpUtils.java ---
package com.starry.common.utils.http;

import com.starry.common.constant.Constants;
import java.io.*;
import java.net.ConnectException;
import java.net.SocketTimeoutException;
import java.net.URL;
import java.net.URLConnection;
import java.nio.charset.StandardCharsets;
import java.security.cert.X509Certificate;
import javax.net.ssl.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * 通用http发送方法
 *
 * @author ruoyi
 */
public class HttpUtils {
    private static final Logger log = LoggerFactory.getLogger(HttpUtils.class);

    /**
     * 向指定 URL 发送GET方法的请求
     *
     * @param url
     *            发送请求的 URL
     * @param param
     *            请求参数，请求参数应该是 name1=value1&name2=value2 的形式。
     * @return 所代表远程资源的响应结果
     */
    public static String sendGet(String url, String param) {
        return sendGet(url, param, Constants.UTF8);
    }

    /**
     * 向指定 URL 发送GET方法的请求
     *
     * @param url
     *            发送请求的 URL
     * @param param
     *            请求参数，请求参数应该是 name1=value1&name2=value2 的形式。
     * @param contentType
     *            编码类型
     * @return 所代表远程资源的响应结果
     */
    public static String sendGet(String url, String param, String contentType) {
        StringBuilder result = new StringBuilder();
        BufferedReader in = null;
        try {
            String urlNameString = url + "?" + param;
            log.info("sendGet - {}", urlNameString);
            URL realUrl = new URL(urlNameString);
            URLConnection connection = realUrl.openConnection();
            connection.setRequestProperty("accept", "*/*");
            connection.setRequestProperty("connection", "Keep-Alive");
            connection.setRequestProperty("user-agent", "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1;SV1)");
            connection.connect();
            in = new BufferedReader(new InputStreamReader(connection.getInputStream(), contentType));
            String line;
            while ((line = in.readLine()) != null) {
                result.append(line);
            }
            log.info("recv - {}", result);
        } catch (ConnectException e) {
            log.error("调用HttpUtils.sendGet ConnectException, url=" + url + ",param=" + param, e);
        } catch (SocketTimeoutException e) {
            log.error("调用HttpUtils.sendGet SocketTimeoutException, url=" + url + ",param=" + param, e);
        } catch (IOException e) {
            log.error("调用HttpUtils.sendGet IOException, url=" + url + ",param=" + param, e);
        } catch (Exception e) {
            log.error("调用HttpsUtil.sendGet Exception, url=" + url + ",param=" + param, e);
        } finally {
            try {
                if (in != null) {
                    in.close();
                }
            } catch (Exception ex) {
                log.error("调用in.close Exception, url=" + url + ",param=" + param, ex);
            }
        }
        return result.toString();
    }

    /**
     * 向指定 URL 发送POST方法的请求
     *
     * @param url
     *            发送请求的 URL
     * @param param
     *            请求参数，请求参数应该是 name1=value1&name2=value2 的形式。
     * @return 所代表远程资源的响应结果
     */
    public static String sendPost(String url, String param) {
        PrintWriter out = null;
        BufferedReader in = null;
        StringBuilder result = new StringBuilder();
        try {
            log.info("sendPost - {}", url);
            URL realUrl = new URL(url);
            URLConnection conn = realUrl.openConnection();
            conn.setRequestProperty("accept", "*/*");
            conn.setRequestProperty("connection", "Keep-Alive");
            conn.setRequestProperty("user-agent", "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1;SV1)");
            conn.setRequestProperty("Accept-Charset", "utf-8");
            conn.setRequestProperty("contentType", "utf-8");
            conn.setDoOutput(true);
            conn.setDoInput(true);
            out = new PrintWriter(conn.getOutputStream());
            out.print(param);
            out.flush();
            in = new BufferedReader(new InputStreamReader(conn.getInputStream(), StandardCharsets.UTF_8));
            String line;
            while ((line = in.readLine()) != null) {
                result.append(line);
            }
            log.info("recv - {}", result);
        } catch (ConnectException e) {
            log.error("调用HttpUtils.sendPost ConnectException, url=" + url + ",param=" + param, e);
        } catch (SocketTimeoutException e) {
            log.error("调用HttpUtils.sendPost SocketTimeoutException, url=" + url + ",param=" + param, e);
        } catch (IOException e) {
            log.error("调用HttpUtils.sendPost IOException, url=" + url + ",param=" + param, e);
        } catch (Exception e) {
            log.error("调用HttpsUtil.sendPost Exception, url=" + url + ",param=" + param, e);
        } finally {
            try {
                if (out != null) {
                    out.close();
                }
                if (in != null) {
                    in.close();
                }
            } catch (IOException ex) {
                log.error("调用in.close Exception, url=" + url + ",param=" + param, ex);
            }
        }
        return result.toString();
    }

    public static String sendSSLPost(String url, String param) {
        StringBuilder result = new StringBuilder();
        String urlNameString = url + "?" + param;
        try {
            log.info("sendSSLPost - {}", urlNameString);
            SSLContext sc = SSLContext.getInstance("SSL");
            sc.init(null, new TrustManager[]{new TrustAnyTrustManager()}, new java.security.SecureRandom());
            URL console = new URL(urlNameString);
            HttpsURLConnection conn = (HttpsURLConnection) console.openConnection();
            conn.setRequestProperty("accept", "*/*");
            conn.setRequestProperty("connection", "Keep-Alive");
            conn.setRequestProperty("user-agent", "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1;SV1)");
            conn.setRequestProperty("Accept-Charset", "utf-8");
            conn.setRequestProperty("contentType", "utf-8");
            conn.setDoOutput(true);
            conn.setDoInput(true);

            conn.setSSLSocketFactory(sc.getSocketFactory());
            conn.setHostnameVerifier(new TrustAnyHostnameVerifier());
            conn.connect();
            InputStream is = conn.getInputStream();
            BufferedReader br = new BufferedReader(new InputStreamReader(is));
            String ret = "";
            while ((ret = br.readLine()) != null) {
                if (!ret.trim().isEmpty()) {
                    result.append(new String(ret.getBytes(StandardCharsets.ISO_8859_1), StandardCharsets.UTF_8));
                }
            }
            log.info("recv - {}", result);
            conn.disconnect();
            br.close();
        } catch (ConnectException e) {
            log.error("调用HttpUtils.sendSSLPost ConnectException, url=" + url + ",param=" + param, e);
        } catch (SocketTimeoutException e) {
            log.error("调用HttpUtils.sendSSLPost SocketTimeoutException, url=" + url + ",param=" + param, e);
        } catch (IOException e) {
            log.error("调用HttpUtils.sendSSLPost IOException, url=" + url + ",param=" + param, e);
        } catch (Exception e) {
            log.error("调用HttpsUtil.sendSSLPost Exception, url=" + url + ",param=" + param, e);
        }
        return result.toString();
    }

    private static class TrustAnyTrustManager implements X509TrustManager {
        @Override
        public void checkClientTrusted(X509Certificate[] chain, String authType) {
        }

        @Override
        public void checkServerTrusted(X509Certificate[] chain, String authType) {
        }

        @Override
        public X509Certificate[] getAcceptedIssuers() {
            return new X509Certificate[]{};
        }
    }

    private static class TrustAnyHostnameVerifier implements HostnameVerifier {
        @Override
        public boolean verify(String hostname, SSLSession session) {
            return true;
        }
    }
}


--- File: play-common/src/main/java/com/starry/common/utils/ip/AddressUtils.java ---
package com.starry.common.utils.ip;

import com.starry.common.utils.RegionUtils;
import com.starry.common.utils.StringUtils;
import lombok.extern.slf4j.Slf4j;

/**
 * @author admin 获取地址工具类
 * @since 2022/7/25
 */
@Slf4j
public class AddressUtils {

    /**
     * 未知地址
     **/
    public static final String UNKNOWN = "XX XX";

    public static String getRealAddressByIp(String ip) {
        if ("127.0.0.1".equals(ip) || "0:0:0:0:0:0:0:1".equals(ip)) {
            return "内网IP";
        }
        try {
            String rspStr = RegionUtils.getRegion(ip);
            if (StringUtils.isEmpty(rspStr)) {
                log.error("获取地理位置异常 {}", ip);
                return UNKNOWN;
            }
            String[] obj = rspStr.split("\\|");
            String region = obj[2];
            String city = obj[3];
            return String.format("%s %s", region, city);
        } catch (Exception e) {
            log.error("获取地理位置异常", e);
        }
        return UNKNOWN;
    }
}


--- File: play-common/src/main/java/com/starry/common/utils/ip/IpUtils.java ---
package com.starry.common.utils.ip;

import com.starry.common.utils.StringUtils;
import java.net.InetAddress;
import java.net.UnknownHostException;
import javax.servlet.http.HttpServletRequest;

/**
 * 获取IP方法
 *
 * @author ruoyi
 */
public class IpUtils {
    /**
     * 获取客户端IP
     *
     * @param request
     *            请求对象
     * @return IP地址
     */
    public static String getIpAddr(HttpServletRequest request) {
        if (request == null) {
            return "unknown";
        }
        String ip = request.getHeader("x-forwarded-for");
        if (ip == null || ip.isEmpty() || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getHeader("Proxy-Client-IP");
        }
        if (ip == null || ip.isEmpty() || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getHeader("X-Forwarded-For");
        }
        if (ip == null || ip.isEmpty() || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getHeader("WL-Proxy-Client-IP");
        }
        if (ip == null || ip.isEmpty() || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getHeader("X-Real-IP");
        }

        if (ip == null || ip.isEmpty() || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getRemoteAddr();
        }

        return "0:0:0:0:0:0:0:1".equals(ip) ? "127.0.0.1" : getMultistageReverseProxyIp(ip);
    }

    /**
     * 检查是否为内部IP地址
     *
     * @param ip
     *            IP地址
     * @return 结果
     */
    public static boolean internalIp(String ip) {
        byte[] addr = textToNumericFormatV4(ip);
        return internalIp(addr) || "127.0.0.1".equals(ip);
    }

    /**
     * 检查是否为内部IP地址
     *
     * @param addr
     *            byte地址
     * @return 结果
     */
    private static boolean internalIp(byte[] addr) {
        if (StringUtils.isNull(addr) || addr.length < 2) {
            return true;
        }
        final byte b0 = addr[0];
        final byte b1 = addr[1];
        // 10.x.x.x/8
        final byte SECTION_1 = 0x0A;
        // 172.16.x.x/12
        final byte SECTION_2 = (byte) 0xAC;
        final byte SECTION_3 = (byte) 0x10;
        final byte SECTION_4 = (byte) 0x1F;
        // 192.168.x.x/16
        final byte SECTION_5 = (byte) 0xC0;
        final byte SECTION_6 = (byte) 0xA8;
        switch (b0) {
            case SECTION_1 :
                return true;
            case SECTION_2 :
                if (b1 >= SECTION_3 && b1 <= SECTION_4) {
                    return true;
                }
            case SECTION_5 :
                switch (b1) {
                    case SECTION_6 :
                        return true;
                }
            default :
                return false;
        }
    }

    /**
     * 将IPv4地址转换成字节
     *
     * @param text
     *            IPv4地址
     * @return byte 字节
     */
    public static byte[] textToNumericFormatV4(String text) {
        if (text.isEmpty()) {
            return null;
        }

        byte[] bytes = new byte[4];
        String[] elements = text.split("\\.", -1);
        try {
            long l;
            int i;
            switch (elements.length) {
                case 1 :
                    l = Long.parseLong(elements[0]);
                    if ((l < 0L) || (l > 4294967295L)) {
                        return null;
                    }
                    bytes[0] = (byte) (int) (l >> 24 & 0xFF);
                    bytes[1] = (byte) (int) ((l & 0xFFFFFF) >> 16 & 0xFF);
                    bytes[2] = (byte) (int) ((l & 0xFFFF) >> 8 & 0xFF);
                    bytes[3] = (byte) (int) (l & 0xFF);
                    break;
                case 2 :
                    l = Integer.parseInt(elements[0]);
                    if ((l < 0L) || (l > 255L)) {
                        return null;
                    }
                    bytes[0] = (byte) (int) (l & 0xFF);
                    l = Integer.parseInt(elements[1]);
                    if ((l < 0L) || (l > 16777215L)) {
                        return null;
                    }
                    bytes[1] = (byte) (int) (l >> 16 & 0xFF);
                    bytes[2] = (byte) (int) ((l & 0xFFFF) >> 8 & 0xFF);
                    bytes[3] = (byte) (int) (l & 0xFF);
                    break;
                case 3 :
                    for (i = 0; i < 2; ++i) {
                        l = Integer.parseInt(elements[i]);
                        if ((l < 0L) || (l > 255L)) {
                            return null;
                        }
                        bytes[i] = (byte) (int) (l & 0xFF);
                    }
                    l = Integer.parseInt(elements[2]);
                    if ((l < 0L) || (l > 65535L)) {
                        return null;
                    }
                    bytes[2] = (byte) (int) (l >> 8 & 0xFF);
                    bytes[3] = (byte) (int) (l & 0xFF);
                    break;
                case 4 :
                    for (i = 0; i < 4; ++i) {
                        l = Integer.parseInt(elements[i]);
                        if ((l < 0L) || (l > 255L)) {
                            return null;
                        }
                        bytes[i] = (byte) (int) (l & 0xFF);
                    }
                    break;
                default :
                    return null;
            }
        } catch (NumberFormatException e) {
            return null;
        }
        return bytes;
    }

    /**
     * 获取IP地址
     *
     * @return 本地IP地址
     */
    public static String getHostIp() {
        try {
            return InetAddress.getLocalHost().getHostAddress();
        } catch (UnknownHostException e) {
        }
        return "127.0.0.1";
    }

    /**
     * 获取主机名
     *
     * @return 本地主机名
     */
    public static String getHostName() {
        try {
            return InetAddress.getLocalHost().getHostName();
        } catch (UnknownHostException e) {
        }
        return "未知";
    }

    /**
     * 从多级反向代理中获得第一个非unknown IP地址
     *
     * @param ip
     *            获得的IP地址
     * @return 第一个非unknown IP地址
     */
    public static String getMultistageReverseProxyIp(String ip) {
        // 多级反向代理检测
        if (ip != null && ip.indexOf(",") > 0) {
            final String[] ips = ip.trim().split(",");
            for (String subIp : ips) {
                if (false == isUnknown(subIp)) {
                    ip = subIp;
                    break;
                }
            }
        }
        return ip;
    }

    /**
     * 检测给定字符串是否为未知，多用于检测HTTP请求相关
     *
     * @param checkString
     *            被检测的字符串
     * @return 是否未知
     */
    public static boolean isUnknown(String checkString) {
        return StringUtils.isBlank(checkString) || "unknown".equalsIgnoreCase(checkString);
    }
}


--- File: play-generator/README.md ---
# Play Generator - 代码生成器

这是一个独立的代码生成器工具，可以通过 main 方法直接运行生成代码。

## 功能特性

- 支持 MySQL 数据库表结构读取
- 自动生成 Entity、Mapper、Service、Controller 等代码文件
- 基于 Velocity 模板引擎
- 支持批量生成多个表
- 可自定义输出目录和包名

## 使用方法

提供两种方式来使用代码生成器：

### 方式一：使用配置文件（推荐）

#### 1. 修改配置文件

编辑 `src/main/resources/config.properties` 文件：

```properties
# 数据库配置
db.url=jdbc:mysql://localhost:3306/your_database?useUnicode=true&characterEncoding=utf-8&serverTimezone=Asia/Shanghai
db.driver=com.mysql.cj.jdbc.Driver
db.username=your_username
db.password=your_password

# 代码生成配置
gen.author=your_name
gen.packageName=com.your.package
gen.outputDir=./generated-code
gen.autoRemovePre=false
gen.tablePrefix=sys_
gen.tplCategory=crud

# 要生成的表名，多个表名用逗号分隔
gen.tableNames=your_table1,your_table2,your_table3
```

#### 2. 运行生成器

直接运行 `MainGeneratorWithConfig.main()` 方法或使用脚本：
- Windows: 双击 `run.bat`
- Linux/Mac: 执行 `./run.sh`

### 方式二：代码配置

#### 1. 配置数据库连接

编辑 `MainGenerator.java` 文件中的数据库配置：

```java
// 数据库配置
GeneratorConfig config = new GeneratorConfig();
config.setUrl("jdbc:mysql://localhost:3306/your_database?useUnicode=true&characterEncoding=utf-8&serverTimezone=Asia/Shanghai");
config.setDriverName("com.mysql.cj.jdbc.Driver");
config.setUsername("your_username");
config.setPassword("your_password");
```

#### 2. 配置生成参数

```java
// 生成配置
config.setAuthor("your_name");
config.setPackageName("com.your.package");
config.setOutputDir("./generated-code");
config.setAutoRemovePre(false);
config.setTablePrefix("sys_");
```

#### 3. 指定要生成的表名

```java
// 要生成的表名（可以配置多个）
String[] tableNames = {
    "your_table1",
    "your_table2",
    "your_table3"
};
```

### 4. 运行生成器

#### 方式一：通过 Maven 运行
```bash
mvn clean compile exec:java
```

#### 方式二：通过 IDE 运行
直接运行 `MainGenerator.main()` 方法

#### 方式三：打包后运行
```bash
mvn clean package
java -cp target/classes:target/lib/* com.starry.generator.MainGenerator
```

## 生成的代码结构

```
generated-code/
├── src/main/java/com/your/package/
│   ├── entity/          # 实体类
│   ├── mapper/          # Mapper 接口
│   ├── service/         # Service 接口
│   ├── service/impl/    # Service 实现类
│   └── controller/      # Controller 类
└── src/main/resources/
    └── mapper/          # MyBatis XML 文件
```

## 配置参数说明

| 参数 | 说明 | 默认值 |
|------|------|--------|
| url | 数据库连接地址 | - |
| driverName | 数据库驱动类名 | com.mysql.cj.jdbc.Driver |
| username | 数据库用户名 | - |
| password | 数据库密码 | - |
| author | 代码作者 | admin |
| packageName | 生成代码的包名 | com.starry.play |
| outputDir | 代码输出目录 | ./generated-code |
| autoRemovePre | 是否自动移除表前缀 | false |
| tablePrefix | 表前缀 | sys_ |
| tplCategory | 模板类型(crud/tree/sub) | crud |

## 注意事项

1. **重要**：首次使用前，请修改 `src/main/resources/config.properties` 文件中的数据库连接信息
2. 确保数据库连接正常，数据库服务器可访问
3. 确保指定的表存在于数据库中
4. 生成的代码会覆盖同名文件，请注意备份
5. 支持的数据库类型：MySQL 5.7+
6. 确保数据库用户有足够的权限读取表结构信息

## 常见问题

### Q: 如何自定义模板？
A: 模板文件位于 `src/main/resources/vm/` 目录下，可以根据需要修改 `.vm` 文件。

### Q: 如何修改生成的文件结构？
A: 修改 `CodeGenerator.getFileName()` 方法中的文件路径逻辑。

### Q: 支持其他数据库吗？
A: 目前只支持 MySQL，如需支持其他数据库，需要修改 SQL 查询语句和驱动配置。 

--- File: play-generator/pom.xml ---
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xmlns="http://maven.apache.org/POM/4.0.0"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>com.starry</groupId>
        <artifactId>play-with</artifactId>
        <version>1.0</version>
    </parent>

    <artifactId>play-generator</artifactId>

    <properties>
        <maven.compiler.source>11</maven.compiler.source>
        <maven.compiler.target>11</maven.compiler.target>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>

    <dependencies>

        <!--velocity代码生成使用模板 -->
        <dependency>
            <groupId>org.apache.velocity</groupId>
            <artifactId>velocity-engine-core</artifactId>
        </dependency>

        <!-- 通用模快-->
        <dependency>
            <groupId>com.starry</groupId>
            <artifactId>play-common</artifactId>
            <version>1.0</version>
        </dependency>

        <!-- 自定义验证注解 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-validation</artifactId>
        </dependency>

        <!--简化对象封装工具-->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.30</version>
            <scope>provided</scope>
        </dependency>
        <!-- Add lombok annotation processor -->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok-mapstruct-binding</artifactId>
            <version>0.2.0</version>
        </dependency>

        <!-- MySQL驱动 -->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.33</version>
        </dependency>

        <!-- 日志框架 -->
        <dependency>
            <groupId>ch.qos.logback</groupId>
            <artifactId>logback-classic</artifactId>
        </dependency>

    </dependencies>

<!--    <build>-->
<!--        <plugins>-->
<!--            <plugin>-->
<!--                <groupId>org.apache.maven.plugins</groupId>-->
<!--                <artifactId>maven-compiler-plugin</artifactId>-->
<!--                <version>3.8.1</version>-->
<!--                <configuration>-->
<!--                    <source>8</source>-->
<!--                    <target>8</target>-->
<!--                    <encoding>UTF-8</encoding>-->
<!--                </configuration>-->
<!--            </plugin>-->
<!--            -->
<!--            &lt;!&ndash; 配置主类，可以直接运行 &ndash;&gt;-->
<!--            <plugin>-->
<!--                <groupId>org.codehaus.mojo</groupId>-->
<!--                <artifactId>exec-maven-plugin</artifactId>-->
<!--                <version>3.1.0</version>-->
<!--                <configuration>-->
<!--                    <mainClass>com.starry.generator.MainGeneratorWithConfig</mainClass>-->
<!--                </configuration>-->
<!--            </plugin>-->
<!--        </plugins>-->
<!--    </build>-->

</project>

--- File: play-generator/run.bat ---
@echo off
echo 开始编译和运行代码生成器...
mvn clean compile exec:java
pause 

--- File: play-generator/run.sh ---
#!/bin/bash
echo "开始编译和运行代码生成器..."
mvn clean compile exec:java 

--- File: play-generator/src/main/java/com/starry/generator/MainGenerator.java ---
package com.starry.generator;

import com.starry.generator.config.GeneratorConfig;
import com.starry.generator.core.CodeGenerator;

/**
 * 代码生成器主类 通过main方法直接运行生成代码
 *
 * @author admin
 * @since 2024-01-01
 */
public class MainGenerator {

    public static void main(String[] args) {
        // 数据库配置
        GeneratorConfig config = new GeneratorConfig();
        config.setUrl(
                "jdbc:mysql://122.51.20.105:3306/play-with?useUnicode=true&characterEncoding=utf-8&serverTimezone=Asia/Shanghai");
        config.setDriverName("com.mysql.cj.jdbc.Driver");
        config.setUsername("root");
        config.setPassword("KdaKRZ2trpdhNePa");

        // 生成配置
        config.setAuthor("huchuansai");
        config.setPackageName("com.starry.admin");
        config.setOutputDir("./generated-code");
        config.setAutoRemovePre(false);
        config.setTablePrefix("sys_");

        // 要生成的表名（可以配置多个）
        String[] tableNames = {"sys_role",};

        // 创建代码生成器并执行生成
        CodeGenerator generator = new CodeGenerator(config);

        try {
            System.out.println("开始生成代码...");
            generator.generateCode(tableNames);
            System.out.println("代码生成完成！输出目录：" + config.getOutputDir());
        } catch (Exception e) {
            System.err.println("代码生成失败：" + e.getMessage());
            e.printStackTrace();
        }
    }
}


--- File: play-generator/src/main/java/com/starry/generator/MainGeneratorWithConfig.java ---
package com.starry.generator;

import com.starry.generator.config.GeneratorConfig;
import com.starry.generator.core.CodeGenerator;
import java.io.IOException;
import java.io.InputStream;
import java.util.Properties;

/**
 * 代码生成器主类 - 支持配置文件版本 通过读取 config.properties 配置文件来生成代码
 *
 * @author admin
 * @since 2024-01-01
 */
public class MainGeneratorWithConfig {

    public static void main(String[] args) {
        try {
            // 读取配置文件
            GeneratorConfig config = loadConfig();

            // 获取要生成的表名
            String tableNamesStr = getProperty("gen.tableNames", "");
            if (tableNamesStr.isEmpty()) {
                System.err.println("错误：未配置要生成的表名，请在 config.properties 中设置 gen.tableNames");
                return;
            }
            String[] tableNames = tableNamesStr.split(",");

            // 清理表名（去除前后空格）
            for (int i = 0; i < tableNames.length; i++) {
                tableNames[i] = tableNames[i].trim();
            }

            // 创建代码生成器并执行生成
            CodeGenerator generator = new CodeGenerator(config);

            System.out.println("=== 代码生成器配置信息 ===");
            System.out.println("数据库地址: " + config.getUrl());
            System.out.println("包名: " + config.getPackageName());
            System.out.println("输出目录: " + config.getOutputDir());
            System.out.println("作者: " + config.getAuthor());
            System.out.println("要生成的表: " + String.join(", ", tableNames));
            System.out.println("========================");
            System.out.println();

            System.out.println("开始生成代码...");
            generator.generateCode(tableNames);
            System.out.println();
            System.out.println("代码生成完成！输出目录：" + config.getOutputDir());

        } catch (Exception e) {
            System.err.println("代码生成失败：" + e.getMessage());
            e.printStackTrace();
        }
    }

    private static Properties properties;

    private static GeneratorConfig loadConfig() throws IOException {
        // 加载配置文件
        properties = new Properties();
        try (InputStream input = MainGeneratorWithConfig.class.getClassLoader()
                .getResourceAsStream("config.properties")) {
            if (input == null) {
                throw new IOException("配置文件 config.properties 未找到");
            }
            properties.load(input);
        }

        // 创建配置对象
        GeneratorConfig config = new GeneratorConfig();

        // 数据库配置
        config.setUrl(getProperty("db.url", ""));
        config.setDatabaseName(getProperty("db.name", ""));
        config.setDriverName(getProperty("db.driver", "com.mysql.cj.jdbc.Driver"));
        config.setUsername(getProperty("db.username", ""));
        config.setPassword(getProperty("db.password", ""));

        // 生成配置
        config.setAuthor(getProperty("gen.author", "admin"));
        config.setPackageName(getProperty("gen.packageName", "com.starry.play"));
        config.setOutputDir(getProperty("gen.outputDir", "./generated-code"));
        config.setAutoRemovePre(Boolean.parseBoolean(getProperty("gen.autoRemovePre", "false")));
        config.setTablePrefix(getProperty("gen.tablePrefix", "sys_"));
        config.setTplCategory(getProperty("gen.tplCategory", "crud"));

        // 验证必需的配置
        if (config.getUrl().isEmpty()) {
            throw new IllegalArgumentException("数据库连接地址不能为空，请配置 db.url");
        }
        if (config.getUsername().isEmpty()) {
            throw new IllegalArgumentException("数据库用户名不能为空，请配置 db.username");
        }

        return config;
    }

    private static String getProperty(String key, String defaultValue) {
        return properties.getProperty(key, defaultValue);
    }
}


--- File: play-generator/src/main/java/com/starry/generator/config/GenConfig.java ---
package com.starry.generator.config;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.PropertySource;
import org.springframework.stereotype.Component;

/**
 * @author admin 读取代码生成相关配置
 * @since 2022/7/15
 */
@Component
@ConfigurationProperties(prefix = "gen")
@PropertySource(value = {"classpath:generator.yml"})
public class GenConfig {

    /**
     * 作者
     */
    public static String author;

    /**
     * 生成包路径
     */
    public static String packageName;

    /**
     * 自动去除表前缀，默认是false
     */
    public static boolean autoRemovePre;

    /**
     * 表前缀(类名不会包含表前缀)
     */
    public static String tablePrefix;

    public static String getAuthor() {
        return author;
    }

    @Value("${author}")
    public void setAuthor(String author) {
        GenConfig.author = author;
    }

    public static String getPackageName() {
        return packageName;
    }

    @Value("${packageName}")
    public void setPackageName(String packageName) {
        GenConfig.packageName = packageName;
    }

    public static boolean getAutoRemovePre() {
        return autoRemovePre;
    }

    @Value("${autoRemovePre}")
    public void setAutoRemovePre(boolean autoRemovePre) {
        GenConfig.autoRemovePre = autoRemovePre;
    }

    public static String getTablePrefix() {
        return tablePrefix;
    }

    @Value("${tablePrefix}")
    public void setTablePrefix(String tablePrefix) {
        GenConfig.tablePrefix = tablePrefix;
    }
}


--- File: play-generator/src/main/java/com/starry/generator/config/GeneratorConfig.java ---
package com.starry.generator.config;

import lombok.Data;

/**
 * 代码生成器配置类 用于配置数据库连接和生成参数
 *
 * @author admin
 * @since 2024-01-01
 */
@Data
public class GeneratorConfig {

    // 数据库连接配置
    private String url;
    private String driverName;
    private String username;
    private String password;
    private String databaseName;

    // 代码生成配置
    private String author;
    private String packageName;
    private String outputDir;
    private boolean autoRemovePre;
    private String tablePrefix;

    // 模板类型 (crud, tree, sub)
    private String tplCategory = "crud";

    // 功能名称
    private String functionName;

    // 模块名
    private String moduleName;

    // 业务名
    private String businessName;

    public GeneratorConfig() {
        // 设置默认值
        this.author = "admin";
        this.packageName = "com.starry.play";
        this.outputDir = "./generated-code";
        this.autoRemovePre = false;
        this.tablePrefix = "sys_";
        this.tplCategory = "crud";
    }

    /**
     * 从URL中获取数据库名
     */
    public String getDatabaseName() {
        if (databaseName != null && !databaseName.isEmpty()) {
            return databaseName;
        }

        // 如果没有手动设置数据库名，从URL中解析
        if (url != null) {
            String dbName = url.substring(url.lastIndexOf("/") + 1);
            if (dbName.contains("?")) {
                dbName = dbName.substring(0, dbName.indexOf("?"));
            }
            return dbName;
        }
        return "";
    }
}


--- File: play-generator/src/main/java/com/starry/generator/constant/GenConstants.java ---
package com.starry.generator.constant;

/**
 * @author admin 代码生成通用常量
 */
public class GenConstants {

    /**
     * 单表（增删改查）
     */
    public static final String TPL_CRUD = "crud";

    /**
     * 树表（增删改查）
     */
    public static final String TPL_TREE = "tree";

    /**
     * 主子表（增删改查）
     */
    public static final String TPL_SUB = "sub";

    /**
     * 树编码字段
     */
    public static final String TREE_CODE = "treeCode";

    /**
     * 树父编码字段
     */
    public static final String TREE_PARENT_CODE = "treeParentCode";

    /**
     * 树名称字段
     */
    public static final String TREE_NAME = "treeName";

    /**
     * 上级菜单ID字段
     */
    public static final String PARENT_MENU_ID = "parentMenuId";

    /**
     * 上级菜单名称字段
     */
    public static final String PARENT_MENU_NAME = "parentMenuName";

    /**
     * 数据库字符串类型
     */
    public static final String[] COLUMN_TYPE_STR = {"char", "varchar", "nvarchar", "varchar2"};

    /**
     * 数据库文本类型
     */
    public static final String[] COLUMN_TYPE_TEXT = {"tinytext", "text", "mediumtext", "longtext"};

    /**
     * 数据库时间类型
     */
    public static final String[] COLUMN_TYPE_TIME = {"datetime", "time", "date", "timestamp"};

    /**
     * 数据库数字类型
     */
    public static final String[] COLUMNTYPE_NUMBER = {"tinyint", "smallint", "mediumint", "int", "number", "integer",
            "bit", "bigint", "float", "double", "decimal"};

    /**
     * 页面不需要编辑字段
     */
    public static final String[] COLUMN_NAME_NOT_EDIT = {"id", "create_by", "create_time", "del_flag"};

    /**
     * 页面不需要显示的列表字段
     */
    public static final String[] COLUMN_NAME_NOT_LIST = {"id", "create_by", "create_time", "del_flag", "update_by",
            "update_time"};

    /**
     * 页面不需要查询字段
     */
    public static final String[] COLUMN_NAME_NOT_QUERY = {"id", "create_by", "create_time", "del_flag", "update_by",
            "update_time", "remark"};

    /**
     * Entity基类字段
     */
    public static final String[] BASE_ENTITY = {"createBy", "createTime", "updateBy", "updateTime", "remark"};

    /**
     * Tree基类字段
     */
    public static final String[] TREE_ENTITY = {"parentName", "parentId", "orderNum", "ancestors", "children"};

    /**
     * 文本框
     */
    public static final String HTML_INPUT = "input";

    /**
     * 文本域
     */
    public static final String HTML_TEXTAREA = "textarea";

    /**
     * 下拉框
     */
    public static final String HTML_SELECT = "select";

    /**
     * 单选框
     */
    public static final String HTML_RADIO = "radio";

    /**
     * 复选框
     */
    public static final String HTML_CHECKBOX = "checkbox";

    /**
     * 日期控件
     */
    public static final String HTML_DATETIME = "datetime";

    /**
     * 图片上传控件
     */
    public static final String HTML_IMAGE_UPLOAD = "imageUpload";

    /**
     * 文件上传控件
     */
    public static final String HTML_FILE_UPLOAD = "fileUpload";

    /**
     * 富文本控件
     */
    public static final String HTML_EDITOR = "editor";

    /**
     * 字符串类型
     */
    public static final String TYPE_STRING = "String";

    /**
     * 整型
     */
    public static final String TYPE_INTEGER = "Integer";

    /**
     * 长整型
     */
    public static final String TYPE_LONG = "Long";

    /**
     * 浮点型
     */
    public static final String TYPE_DOUBLE = "Double";

    /**
     * 高精度计算类型
     */
    public static final String TYPE_BIGDECIMAL = "BigDecimal";

    /**
     * 时间类型
     */
    public static final String TYPE_DATE = "Date";

    /**
     * 模糊查询
     */
    public static final String QUERY_LIKE = "LIKE";

    /**
     * 相等查询
     */
    public static final String QUERY_EQ = "EQ";

    /**
     * 需要
     */
    public static final String REQUIRE = "1";
}


--- File: play-generator/src/main/java/com/starry/generator/controller/GenController.java ---
package com.starry.generator.controller;

import cn.hutool.core.convert.Convert;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.starry.common.annotation.Log;
import com.starry.common.enums.BusinessType;
import com.starry.common.result.R;
import com.starry.generator.entity.GenTableEntity;
import com.starry.generator.entity.vo.GenTableVo;
import com.starry.generator.service.GenTableService;
import io.swagger.annotations.ApiOperation;
import java.io.IOException;
import java.util.List;
import java.util.Map;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletResponse;
import org.apache.commons.io.IOUtils;
import org.springframework.web.bind.annotation.*;

/**
 * @author admin
 * @since 2022/7/14
 */
@RestController
@RequestMapping("/tool/gen")
public class GenController {

    @Resource
    private GenTableService genTableService;

    @ApiOperation(value = "查询数据库列表")
    @GetMapping("/db/list")
    public R dataList(GenTableEntity genTableEntity) {
        IPage<GenTableEntity> list = genTableService.selectDbTableList(genTableEntity);
        return R.ok(list);
    }

    @ApiOperation(value = "保存表结构")
    @PostMapping("/importTable")
    public R importTableSave(String tables) {
        String[] tableNames = Convert.toStrArray(tables);
        // 查询表信息
        List<GenTableEntity> tableList = genTableService.selectDbTableListByNames(tableNames);
        genTableService.importGenTable(tableList);
        return R.ok();
    }

    @ApiOperation(value = "查询生成表列表")
    @GetMapping("/list")
    public R genList(GenTableVo vo) {
        return R.ok(genTableService.selectGenTableList(vo));
    }

    @ApiOperation(value = "预览代码")
    @GetMapping("/preview/{tableId}")
    public R preview(@PathVariable("tableId") Long tableId) {
        Map<String, String> dataMap = genTableService.previewCode(tableId);
        return R.ok(dataMap);
    }

    @ApiOperation(value = "生成代码（下载方式）")
    @GetMapping("/download/{tableName}")
    public void download(HttpServletResponse response, @PathVariable("tableName") String tableName) throws IOException {
        byte[] data = genTableService.downloadCode(tableName);
        genCode(response, data);
    }

    @Log(title = "代码生成", businessType = BusinessType.GENCODE)
    @ApiOperation(value = "批量生成代码")
    @GetMapping("/download/batch")
    public void batchGenCode(HttpServletResponse response, String tables) throws IOException {
        String[] tableNames = Convert.toStrArray(tables);
        byte[] data = genTableService.downloadCode(tableNames);
        genCode(response, data);
    }

    @ApiOperation(value = "删除代码生成")
    @DeleteMapping("/{tableIds}")
    public R remove(@PathVariable Long[] tableIds) {
        genTableService.deleteGenTableByIds(tableIds);
        return R.ok();
    }

    private void genCode(HttpServletResponse response, byte[] data) throws IOException {
        response.reset();
        response.addHeader("Access-Control-Allow-Origin", "*");
        response.addHeader("Access-Control-Expose-Headers", "Content-Disposition");
        response.setHeader("Content-Disposition", "attachment; filename=\"admin.zip\"");
        response.addHeader("Content-Length", "" + data.length);
        response.setContentType("application/octet-stream; charset=UTF-8");
        IOUtils.write(data, response.getOutputStream());
    }
}


--- File: play-generator/src/main/java/com/starry/generator/core/CodeGenerator.java ---
package com.starry.generator.core;

import com.starry.generator.config.GeneratorConfig;
import com.starry.generator.entity.GenTableColumnEntity;
import com.starry.generator.entity.GenTableEntity;
import com.starry.generator.utils.GenUtils;
import com.starry.generator.utils.VelocityInitializer;
import com.starry.generator.utils.VelocityUtils;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.StringWriter;
import java.sql.*;
import java.util.ArrayList;
import java.util.List;
import lombok.extern.slf4j.Slf4j;
import org.apache.velocity.Template;
import org.apache.velocity.VelocityContext;
import org.apache.velocity.app.Velocity;

/**
 * 核心代码生成器
 *
 * @author admin
 * @since 2024-01-01
 */
@Slf4j
public class CodeGenerator {

    private final GeneratorConfig config;

    public CodeGenerator(GeneratorConfig config) {
        this.config = config;
    }

    /**
     * 生成代码
     *
     * @param tableNames
     *            表名数组
     */
    public void generateCode(String[] tableNames) throws Exception {
        // 初始化 Velocity
        VelocityInitializer.initVelocity();

        // 创建输出目录
        createOutputDirectory();

        for (String tableName : tableNames) {
            log.info("开始生成表 {} 的代码", tableName);
            generateTableCode(tableName);
            log.info("表 {} 代码生成完成", tableName);
        }
    }

    /**
     * 生成单个表的代码
     *
     * @param tableName
     *            表名
     */
    private void generateTableCode(String tableName) throws Exception {
        // 获取表信息
        GenTableEntity table = getTableInfo(tableName);
        if (table == null) {
            log.warn("表 {} 不存在，跳过生成", tableName);
            return;
        }

        // 获取表字段信息
        List<GenTableColumnEntity> columns = getTableColumns(tableName);
        table.setColumns(columns);

        // 初始化表信息
        initTableInfo(table);

        // 初始化字段信息
        initColumnInfo(table);

        // 设置主键列信息
        setPkColumn(table);

        // 设置模板变量信息
        VelocityContext context = VelocityUtils.prepareContext(table);

        // 获取模版列表
        List<String> templates = VelocityUtils.getTemplateList(table.getTplCategory());

        for (String template : templates) {
            // 渲染模板
            StringWriter sw = new StringWriter();
            Template tpl = Velocity.getTemplate(template, "UTF-8");
            tpl.merge(context, sw);

            // 写入文件
            writeToFile(template, table, sw.toString());
        }
    }

    /**
     * 获取表信息
     */
    private GenTableEntity getTableInfo(String tableName) throws Exception {
        String sql = "SELECT table_name, table_comment FROM information_schema.tables "
                + "WHERE table_schema = ? AND table_name = ?";

        try (Connection conn = getConnection(); PreparedStatement stmt = conn.prepareStatement(sql)) {

            String dbName = getDatabaseName();
            log.info("查询表信息 - 数据库名: {}, 表名: {}", dbName, tableName);

            stmt.setString(1, dbName);
            stmt.setString(2, tableName);

            try (ResultSet rs = stmt.executeQuery()) {
                if (rs.next()) {
                    GenTableEntity table = new GenTableEntity();
                    table.setTableName(rs.getString("table_name"));
                    table.setTableComment(rs.getString("table_comment"));
                    log.info("找到表: {}, 注释: {}", table.getTableName(), table.getTableComment());
                    return table;
                } else {
                    // 如果找不到表，尝试查询所有表来调试
                    log.warn("未找到表 {}，正在查询数据库 {} 中的所有表...", tableName, dbName);
                    showAllTables(conn, dbName);
                }
            }
        }
        return null;
    }

    /**
     * 显示数据库中的所有表（用于调试）
     */
    private void showAllTables(Connection conn, String dbName) throws Exception {
        String sql = "SELECT table_name FROM information_schema.tables WHERE table_schema = ? LIMIT 10";
        try (PreparedStatement stmt = conn.prepareStatement(sql)) {
            stmt.setString(1, dbName);
            try (ResultSet rs = stmt.executeQuery()) {
                log.info("数据库 {} 中的表:", dbName);
                boolean hasTable = false;
                while (rs.next()) {
                    hasTable = true;
                    log.info("  - {}", rs.getString("table_name"));
                }
                if (!hasTable) {
                    log.warn("数据库 {} 中没有找到任何表，请检查数据库名称是否正确", dbName);
                }
            }
        }
    }

    /**
     * 获取表字段信息
     */
    private List<GenTableColumnEntity> getTableColumns(String tableName) throws Exception {
        List<GenTableColumnEntity> columns = new ArrayList<>();

        String sql = "SELECT column_name, data_type, column_comment, is_nullable, "
                + "column_key, column_default, extra FROM information_schema.columns "
                + "WHERE table_schema = ? AND table_name = ? ORDER BY ordinal_position";

        try (Connection conn = getConnection(); PreparedStatement stmt = conn.prepareStatement(sql)) {

            String dbName = getDatabaseName();
            stmt.setString(1, dbName);
            stmt.setString(2, tableName);

            try (ResultSet rs = stmt.executeQuery()) {
                while (rs.next()) {
                    GenTableColumnEntity column = new GenTableColumnEntity();
                    column.setColumnName(rs.getString("column_name"));
                    column.setColumnType(rs.getString("data_type"));
                    column.setColumnComment(rs.getString("column_comment"));
                    column.setIsRequired("NO".equals(rs.getString("is_nullable")) ? "1" : "0");
                    column.setIsPk("PRI".equals(rs.getString("column_key")) ? "1" : "0");
                    column.setIsIncrement("auto_increment".equals(rs.getString("extra")) ? "1" : "0");

                    columns.add(column);
                }
            }
        }

        return columns;
    }

    /**
     * 初始化表信息
     */
    private void initTableInfo(GenTableEntity table) {
        table.setClassName(GenUtils.convertClassName(table.getTableName()));
        table.setPackageName(config.getPackageName());
        table.setModuleName(GenUtils.getModuleName(config.getPackageName()));
        table.setBusinessName(GenUtils.getBusinessName(table.getTableName()));
        table.setFunctionName(table.getTableComment());
        table.setFunctionAuthor(config.getAuthor());
        table.setTplCategory(config.getTplCategory());
    }

    /**
     * 初始化字段信息
     */
    private void initColumnInfo(GenTableEntity table) {
        if (table.getColumns() != null) {
            for (GenTableColumnEntity column : table.getColumns()) {
                GenUtils.initColumnField(column, table);
            }
        }
    }

    /**
     * 设置主键列信息
     */
    private void setPkColumn(GenTableEntity table) {
        for (GenTableColumnEntity column : table.getColumns()) {
            if ("1".equals(column.getIsPk())) {
                table.setPkColumn(column);
                break;
            }
        }

        if (table.getPkColumn() == null) {
            table.setPkColumn(table.getColumns().get(0));
        }
    }

    /**
     * 写入文件
     */
    private void writeToFile(String template, GenTableEntity table, String content) throws IOException {
        String fileName = getFileName(template, table);
        File file = new File(fileName);

        // 创建父目录
        file.getParentFile().mkdirs();

        try (FileWriter writer = new FileWriter(file)) {
            writer.write(content);
        }

        log.info("生成文件：{}", fileName);
    }

    /**
     * 获取文件名
     */
    private String getFileName(String template, GenTableEntity table) {
        String outputDir = config.getOutputDir();
        String packageName = table.getPackageName();
        String moduleName = table.getModuleName();
        String businessName = table.getBusinessName();
        String className = table.getClassName();

        String javaPath = outputDir + "/src/main/java/" + packageName.replace(".", "/");
        String resourcesPath = outputDir + "/src/main/resources";

        if (template.contains("entity.java.vm")) {
            return javaPath + "/entity/" + className + "Entity.java";
        } else if (template.contains("mapper.java.vm")) {
            return javaPath + "/mapper/" + className + "Mapper.java";
        } else if (template.contains("service.java.vm")) {
            return javaPath + "/service/I" + className + "Service.java";
        } else if (template.contains("serviceImpl.java.vm")) {
            return javaPath + "/service/impl/" + className + "ServiceImpl.java";
        } else if (template.contains("controller.java.vm")) {
            return javaPath + "/controller/" + className + "Controller.java";
        } else if (template.contains("queryVo.java.vm")) {
            return javaPath + "/vo/" + className + "QueryVo.java";
        } else if (template.contains("addVo.java.vm")) {
            return javaPath + "/vo/" + className + "AddVo.java";
        } else if (template.contains("mapper.xml.vm")) {
            return resourcesPath + "/mapper/" + className + "Mapper.xml";
        } else if (template.contains("sql.vm")) {
            return outputDir + "/" + businessName + "Menu.sql";
        } else if (template.contains("api.js.vm")) {
            return outputDir + "/api/" + moduleName + "/" + businessName + ".js";
        } else if (template.contains("index.vue.vm")) {
            return outputDir + "/views/" + moduleName + "/" + businessName + "/index.vue";
        }

        return outputDir + "/" + template.replace(".vm", "");
    }

    /**
     * 获取数据库连接
     */
    private Connection getConnection() throws Exception {
        Class.forName(config.getDriverName());
        return DriverManager.getConnection(config.getUrl(), config.getUsername(), config.getPassword());
    }

    /**
     * 从URL中获取数据库名
     */
    private String getDatabaseName() {
        return config.getDatabaseName();
    }

    /**
     * 创建输出目录
     */
    private void createOutputDirectory() {
        File outputDir = new File(config.getOutputDir());
        if (!outputDir.exists()) {
            outputDir.mkdirs();
        }
    }
}


--- File: play-generator/src/main/java/com/starry/generator/entity/GenTableColumnEntity.java ---
package com.starry.generator.entity;

import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import com.starry.common.domain.BaseEntity;
import com.starry.common.utils.StringUtils;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import java.io.Serializable;
import javax.validation.constraints.NotBlank;
import lombok.Getter;
import lombok.Setter;

/**
 * @author admin 代码生成业务字段表 gen_table_column
 * @since 2022/7/14
 */
@Getter
@Setter
@TableName("gen_table_column")
@ApiModel(value = "GenTableColumn对象", description = "代码生成业务表字段")
public class GenTableColumnEntity extends BaseEntity<GenTableEntity> implements Serializable {

    /**
     * 编号
     */
    @ApiModelProperty("主键ID")
    @TableId(value = "table_id", type = IdType.AUTO)
    private Long columnId;

    /**
     * 归属表编号
     */
    private Long tableId;

    /**
     * 列名称
     */
    private String columnName;

    /**
     * 列描述
     */
    private String columnComment;

    /**
     * 列类型
     */
    private String columnType;

    /**
     * JAVA类型
     */
    private String javaType;

    /**
     * JAVA字段名
     */
    @NotBlank(message = "Java属性不能为空")
    private String javaField;

    /**
     * 是否主键（1是）
     */
    private String isPk;

    /**
     * 是否自增（1是）
     */
    private String isIncrement;

    /**
     * 是否必填（1是）
     */
    private String isRequired;

    /**
     * 是否为插入字段（1是）
     */
    private String isInsert;

    /**
     * 是否编辑字段（1是）
     */
    private String isEdit;

    /**
     * 是否列表字段（1是）
     */
    private String isList;

    /**
     * 是否查询字段（1是）
     */
    private String isQuery;

    /**
     * 查询方式（EQ等于、NE不等于、GT大于、LT小于、LIKE模糊、BETWEEN范围）
     */
    private String queryType;

    /**
     * 显示类型（input文本框、textarea文本域、select下拉框、checkbox复选框、radio单选框、datetime日期控件、image图片上传控件、upload文件上传控件、editor富文本控件）
     */
    private String htmlType;

    /**
     * 字典类型
     */
    private String dictType;

    /**
     * 排序
     */
    private Integer sort;

    public static boolean isSuperColumn(String javaField) {
        return StringUtils.equalsAnyIgnoreCase(javaField,
                // BaseEntity
                "createdBy", "createdTime", "updatedBy", "updatedTime", "deleted",
                // TreeEntity
                "parentName", "parentId", "orderNum", "ancestors");
    }

    public boolean isSuperColumn() {
        return isSuperColumn(this.javaField);
    }

    public String getCapJavaField() {
        return StringUtils.capitalize(javaField);
    }

}


--- File: play-generator/src/main/java/com/starry/generator/entity/GenTableEntity.java ---
package com.starry.generator.entity;

import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import com.starry.common.domain.BaseEntity;
import com.starry.common.utils.StringUtils;
import com.starry.generator.constant.GenConstants;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import java.util.List;
import javax.validation.constraints.NotBlank;
import lombok.Getter;
import lombok.Setter;

/**
 * @author admin 业务表 gen_table
 * @since 2022/7/14
 */
@Getter
@Setter
@TableName("gen_table")
@ApiModel(value = "GenTable对象", description = "代码生成业务表")
public class GenTableEntity extends BaseEntity<GenTableEntity> {

    @ApiModelProperty("主键ID")
    @TableId(value = "table_id", type = IdType.AUTO)
    private Long tableId;

    /**
     * 表名称
     */
    @NotBlank(message = "表名称不能为空")
    private String tableName;

    /**
     * 表描述
     */
    @NotBlank(message = "表描述不能为空")
    private String tableComment;

    /**
     * 关联父表的表名
     */
    private String subTableName;

    /**
     * 本表关联父表的外键名
     */
    private String subTableFkName;

    /**
     * 实体类名称(首字母大写)
     */
    @NotBlank(message = "实体类名称不能为空")
    private String className;

    /**
     * 使用的模板（crud单表操作 tree树表操作 sub主子表操作）
     */
    private String tplCategory;

    /**
     * 生成包路径
     */
    @NotBlank(message = "生成包路径不能为空")
    private String packageName;

    /**
     * 生成模块名
     */
    @NotBlank(message = "生成模块名不能为空")
    private String moduleName;

    /**
     * 生成业务名
     */
    @NotBlank(message = "生成业务名不能为空")
    private String businessName;

    /**
     * 生成功能名
     */
    @NotBlank(message = "生成功能名不能为空")
    private String functionName;

    /**
     * 生成作者
     */
    @NotBlank(message = "作者不能为空")
    private String functionAuthor;

    /**
     * 生成代码方式（0zip压缩包 1自定义路径）
     */
    private String genType;

    /**
     * 生成路径（不填默认项目路径）
     */
    private String genPath;

    /**
     * 主键信息
     */
    @TableField(exist = false)
    private GenTableColumnEntity pkColumn;

    /**
     * 子表信息
     */
    @TableField(exist = false)
    private GenTableEntity subTable;

    /**
     * 表列信息
     */
    @TableField(exist = false)
    private List<GenTableColumnEntity> columns;

    /**
     * 其它生成选项
     */
    private String options;

    /**
     * 树编码字段
     */
    @TableField(exist = false)
    private String treeCode;

    /**
     * 树父编码字段
     */
    @TableField(exist = false)
    private String treeParentCode;

    /**
     * 树名称字段
     */
    @TableField(exist = false)
    private String treeName;

    /**
     * 上级菜单ID字段
     */
    @TableField(exist = false)
    private String parentMenuId;

    /**
     * 上级菜单名称字段
     */
    @TableField(exist = false)
    private String parentMenuName;

    public static boolean isCrud(String tplCategory) {
        return tplCategory != null && StringUtils.equals(GenConstants.TPL_CRUD, tplCategory);
    }

    public static boolean isTree(String tplCategory) {
        return tplCategory != null && StringUtils.equals(GenConstants.TPL_TREE, tplCategory);
    }

    public boolean isCrud() {
        return isCrud(this.tplCategory);
    }

    public boolean isTree() {
        return isTree(this.tplCategory);
    }
}


--- File: play-generator/src/main/java/com/starry/generator/entity/vo/GenTableVo.java ---
package com.starry.generator.entity.vo;

import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.starry.common.domain.BasePageEntity;
import com.starry.generator.entity.GenTableColumnEntity;
import io.swagger.annotations.ApiModelProperty;
import java.util.List;
import javax.validation.constraints.NotBlank;
import lombok.Data;
import lombok.EqualsAndHashCode;

/**
 * @author admin 业务表 gen_table
 * @since 2022/7/14
 */

@EqualsAndHashCode(callSuper = true)
@Data
public class GenTableVo extends BasePageEntity {

    @ApiModelProperty("主键ID")
    @TableId(value = "table_id", type = IdType.AUTO)
    private Long tableId;

    /**
     * 表名称
     */
    @NotBlank(message = "表名称不能为空")
    private String tableName;

    /**
     * 表描述
     */
    @NotBlank(message = "表描述不能为空")
    private String tableComment;

    /**
     * 关联父表的表名
     */
    private String subTableName;

    /**
     * 本表关联父表的外键名
     */
    private String subTableFkName;

    /**
     * 实体类名称(首字母大写)
     */
    @NotBlank(message = "实体类名称不能为空")
    private String className;

    /**
     * 使用的模板（crud单表操作 tree树表操作 sub主子表操作）
     */
    private String tplCategory;

    /**
     * 生成包路径
     */
    @NotBlank(message = "生成包路径不能为空")
    private String packageName;

    /**
     * 生成模块名
     */
    @NotBlank(message = "生成模块名不能为空")
    private String moduleName;

    /**
     * 生成业务名
     */
    @NotBlank(message = "生成业务名不能为空")
    private String businessName;

    /**
     * 生成功能名
     */
    @NotBlank(message = "生成功能名不能为空")
    private String functionName;

    /**
     * 生成作者
     */
    @NotBlank(message = "作者不能为空")
    private String functionAuthor;

    /**
     * 生成代码方式（0zip压缩包 1自定义路径）
     */
    private String genType;

    /**
     * 生成路径（不填默认项目路径）
     */
    private String genPath;

    /**
     * 主键信息
     */
    @TableField(exist = false)
    private GenTableColumnEntity pkColumn;

    /**
     * 子表信息
     */
    @TableField(exist = false)
    private GenTableVo subTable;

    /**
     * 表列信息
     */
    @TableField(exist = false)
    private List<GenTableColumnEntity> columns;

    /**
     * 其它生成选项
     */
    private String options;

    /**
     * 树编码字段
     */
    @TableField(exist = false)
    private String treeCode;

    /**
     * 树父编码字段
     */
    @TableField(exist = false)
    private String treeParentCode;

    /**
     * 树名称字段
     */
    @TableField(exist = false)
    private String treeName;

    /**
     * 上级菜单ID字段
     */
    @TableField(exist = false)
    private String parentMenuId;

    /**
     * 上级菜单名称字段
     */
    @TableField(exist = false)
    private String parentMenuName;

}


--- File: play-generator/src/main/java/com/starry/generator/mapper/GenTableColumnMapper.java ---
package com.starry.generator.mapper;

import com.baomidou.mybatisplus.annotation.InterceptorIgnore;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.starry.generator.entity.GenTableColumnEntity;
import java.util.List;

/**
 * <p>
 * 代码生成业务表字段 Mapper 接口
 * </p>
 *
 * @author admin
 * @since 2022-07-15
 */
public interface GenTableColumnMapper extends BaseMapper<GenTableColumnEntity> {

    /**
     * 根据表名称查询列信息
     *
     * @param tableName
     *            表名称
     * @return 列信息
     */
    @InterceptorIgnore(tenantLine = "1")
    List<GenTableColumnEntity> selectDbTableColumnsByName(String tableName);

}


--- File: play-generator/src/main/java/com/starry/generator/mapper/GenTableMapper.java ---
package com.starry.generator.mapper;

import com.baomidou.mybatisplus.annotation.InterceptorIgnore;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.starry.generator.entity.GenTableEntity;
import com.starry.generator.entity.vo.GenTableVo;
import java.util.List;
import org.apache.ibatis.annotations.Param;

/**
 * @author admin 业务 数据层
 * @since 2022/7/14
 */
public interface GenTableMapper extends BaseMapper<GenTableEntity> {

    /**
     * 查询业务列表
     *
     * @param genTableEntity
     *            业务信息
     * @return 业务集合
     */

    IPage<GenTableEntity> selectGenTableList(Page<GenTableEntity> page,
            @Param(value = "genTableEntity") GenTableVo genTableEntity);

    @InterceptorIgnore(tenantLine = "1")
    IPage<GenTableEntity> selectDbTableList(Page page, @Param("genTableEntity") GenTableEntity genTableEntity);

    /**
     * 查询数据库列表
     *
     * @param tableNames
     *            表名称组
     * @return 数据库表集合
     */
    @InterceptorIgnore(tenantLine = "1")
    List<GenTableEntity> selectDbTableListByNames(String[] tableNames);

    /**
     * 查询所有表信息
     *
     * @return 表信息集合
     */
    List<GenTableEntity> selectGenTableAll();

    /**
     * 查询表ID业务信息
     *
     * @param id
     *            业务ID
     * @return 业务信息
     */
    GenTableEntity selectGenTableById(Long id);

    /**
     * 查询表名称业务信息
     *
     * @param tableName
     *            表名称
     * @return 业务信息
     */
    GenTableEntity selectGenTableByName(String tableName);
}


--- File: play-generator/src/main/java/com/starry/generator/service/GenTableService.java ---
package com.starry.generator.service;

import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.service.IService;
import com.starry.generator.entity.GenTableEntity;
import com.starry.generator.entity.vo.GenTableVo;
import java.util.List;
import java.util.Map;

/**
 * @author admin
 * @since 2022/7/14
 */
public interface GenTableService extends IService<GenTableEntity> {

    /**
     * 查询数据库列表
     *
     * @param genTableEntity
     *            业务信息
     * @return 数据库表集合
     */
    /* List<GenTableEntity> selectDbTableList(GenTableEntity genTableEntity); */

    IPage<GenTableEntity> selectDbTableList(GenTableEntity genTableEntity);

    /**
     * 查询据库列表
     *
     * @param tableNames
     *            表名称组
     * @return 数据库表集合
     */
    List<GenTableEntity> selectDbTableListByNames(String[] tableNames);

    /**
     * 导入表结构
     *
     * @param tableList
     *            导入表列表
     */
    void importGenTable(List<GenTableEntity> tableList);

    /**
     * 查询业务列表
     *
     * @param genTableEntity
     *            业务信息
     * @return 业务集合
     */
    IPage<GenTableEntity> selectGenTableList(GenTableVo genTableEntity);

    /**
     * 预览代码
     *
     * @param tableId
     *            表编号
     * @return 预览数据列表
     */
    Map<String, String> previewCode(Long tableId);

    /**
     * 生成代码（下载方式）
     *
     * @param tableName
     *            表名称
     * @return 数据
     */
    byte[] downloadCode(String tableName);

    /**
     * 批量生成代码（下载方式）
     *
     * @param tableNames
     *            表数组
     * @return 数据
     */
    byte[] downloadCode(String[] tableNames);

    /**
     * 删除业务信息
     *
     * @param tableIds
     *            需要删除的表数据ID
     */
    void deleteGenTableByIds(Long[] tableIds);
}


--- File: play-generator/src/main/java/com/starry/generator/service/GenTableServiceImpl.java ---
package com.starry.generator.service;

import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.convert.Convert;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.starry.common.utils.ServletUtils;
import com.starry.common.utils.StringUtils;
import com.starry.generator.constant.GenConstants;
import com.starry.generator.entity.GenTableColumnEntity;
import com.starry.generator.entity.GenTableEntity;
import com.starry.generator.entity.vo.GenTableVo;
import com.starry.generator.mapper.GenTableColumnMapper;
import com.starry.generator.mapper.GenTableMapper;
import com.starry.generator.utils.GenUtils;
import com.starry.generator.utils.VelocityInitializer;
import com.starry.generator.utils.VelocityUtils;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.StringWriter;
import java.util.Arrays;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;
import javax.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.IOUtils;
import org.apache.velocity.Template;
import org.apache.velocity.VelocityContext;
import org.apache.velocity.app.Velocity;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

/**
 * @author admin
 * @since 2022/7/14
 */
@Slf4j
@Service
public class GenTableServiceImpl extends ServiceImpl<GenTableMapper, GenTableEntity> implements GenTableService {

    @Resource
    private GenTableColumnMapper genTableColumnMapper;

    /*
     * @Override public List<GenTableEntity> selectDbTableList(GenTableEntity
     * genTableEntity) { return baseMapper.selectDbTableList(genTableEntity); }
     */
    @Override
    public IPage<GenTableEntity> selectDbTableList(GenTableEntity genTableEntity) {
        Page mpPage = new Page(Convert.toLong(ServletUtils.getParameterToInt("pageNum"), 1L),
                Convert.toLong(ServletUtils.getParameterToInt("pageSize"), 10L));
        return baseMapper.selectDbTableList(mpPage, genTableEntity);
    }

    @Override
    public List<GenTableEntity> selectDbTableListByNames(String[] tableNames) {
        return baseMapper.selectDbTableListByNames(tableNames);
    }

    @Transactional(rollbackFor = Exception.class)
    @Override
    public void importGenTable(List<GenTableEntity> tableList) {
        if (CollectionUtil.isNotEmpty(tableList)) {
            for (GenTableEntity table : tableList) {
                String tableName = table.getTableName();
                GenUtils.initTable(table);
                // 保存生成表数据
                int row = baseMapper.insert(table);
                if (row > 0) {
                    // 查询数据库表列信息
                    List<GenTableColumnEntity> genTableColumnEntities = genTableColumnMapper
                            .selectDbTableColumnsByName(tableName);
                    if (CollectionUtil.isNotEmpty(genTableColumnEntities)) {
                        for (GenTableColumnEntity column : genTableColumnEntities) {
                            GenUtils.initColumnField(column, table);
                            // 保存生成表字段数据
                            genTableColumnMapper.insert(column);
                        }
                    }
                }
            }
        }
    }

    @Override
    public IPage<GenTableEntity> selectGenTableList(GenTableVo vo) {
        return baseMapper.selectGenTableList(new Page<>(vo.getPageNum(), vo.getPageSize()), vo);
    }

    @Override
    public Map<String, String> previewCode(Long tableId) {
        Map<String, String> dataMap = new LinkedHashMap<>();
        // 查询表信息
        GenTableEntity table = baseMapper.selectGenTableById(tableId);
        // 设置主子表信息
        setSubTable(table);
        // 设置主键列信息
        setPkColumn(table);
        // 初始化
        VelocityInitializer.initVelocity();
        // 设置模板变量信息
        VelocityContext context = VelocityUtils.prepareContext(table);
        // 获取模版列表
        List<String> templates = VelocityUtils.getTemplateList(table.getTplCategory());
        for (String template : templates) {
            // 渲染模板
            StringWriter sw = new StringWriter();
            Template tpl = Velocity.getTemplate(template, "UTF-8");
            tpl.merge(context, sw);
            dataMap.put(template, sw.toString());
        }
        return dataMap;
    }

    @Override
    public byte[] downloadCode(String tableName) {
        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
        ZipOutputStream zip = new ZipOutputStream(outputStream);
        generatorCode(tableName, zip);
        IOUtils.closeQuietly(zip);
        return outputStream.toByteArray();
    }

    @Override
    public byte[] downloadCode(String[] tableNames) {
        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
        ZipOutputStream zip = new ZipOutputStream(outputStream);
        for (String tableName : tableNames) {
            generatorCode(tableName, zip);
        }
        IOUtils.closeQuietly(zip);
        return outputStream.toByteArray();
    }

    @Transactional(rollbackFor = Exception.class)
    @Override
    public void deleteGenTableByIds(Long[] tableIds) {
        baseMapper.deleteBatchIds(Arrays.asList(tableIds));
        List<GenTableColumnEntity> genTableColumnEntities = genTableColumnMapper
                .selectList(new LambdaQueryWrapper<GenTableColumnEntity>().in(GenTableColumnEntity::getTableId,
                        Arrays.asList(tableIds)));
        genTableColumnMapper.deleteBatchIds(genTableColumnEntities);
    }

    private void generatorCode(String tableName, ZipOutputStream zip) {
        // 查询表信息
        GenTableEntity table = baseMapper.selectGenTableByName(tableName);
        // 设置主子表信息
        setSubTable(table);
        // 设置主键列信息
        setPkColumn(table);
        // 初始化
        VelocityInitializer.initVelocity();
        // 设置模板变量信息
        VelocityContext context = VelocityUtils.prepareContext(table);
        // 获取模版列表
        List<String> templates = VelocityUtils.getTemplateList(table.getTplCategory());
        for (String template : templates) {
            // 渲染模板
            StringWriter sw = new StringWriter();
            Template tpl = Velocity.getTemplate(template, "UTF-8");
            tpl.merge(context, sw);
            try {
                // 添加到zip
                zip.putNextEntry(new ZipEntry(VelocityUtils.getFileName(template, table)));
                IOUtils.write(sw.toString(), zip, "UTF-8");
                IOUtils.closeQuietly(sw);
                zip.flush();
                zip.closeEntry();
            } catch (IOException e) {
                log.error("渲染模板失败，表名：" + table.getTableName(), e);
            }
        }
    }

    public void setSubTable(GenTableEntity table) {
        String subTableName = table.getSubTableName();
        if (StringUtils.isNotBlank(subTableName)) {

        }
    }

    /**
     * 设置主键列信息
     *
     * @param table
     *            业务表信息
     */
    public void setPkColumn(GenTableEntity table) {
        for (GenTableColumnEntity column : table.getColumns()) {
            if ("1".equals(column.getIsPk())) {
                table.setPkColumn(column);
                break;
            }
            if (StringUtils.isNull(table.getPkColumn())) {
                table.setPkColumn(table.getColumns().get(0));
            }
            if (GenConstants.TPL_SUB.equals(table.getTplCategory())) {
                for (GenTableColumnEntity columnSub : table.getSubTable().getColumns()) {
                    if ("1".equals(columnSub.getIsPk())) {
                        table.getSubTable().setPkColumn(columnSub);
                        break;
                    }
                    if (StringUtils.isNull(table.getSubTable().getPkColumn())) {
                        table.getSubTable().setPkColumn(table.getSubTable().getColumns().get(0));
                    }
                }
            }
        }
    }
}


--- File: play-generator/src/main/java/com/starry/generator/utils/GenUtils.java ---
package com.starry.generator.utils;

import com.starry.generator.config.GenConfig;
import com.starry.generator.constant.GenConstants;
import com.starry.generator.entity.GenTableColumnEntity;
import com.starry.generator.entity.GenTableEntity;
import java.util.Arrays;
import org.apache.commons.lang3.StringUtils;

/**
 * @author admin 代码生成器 工具类
 * @since 2022/7/15
 */
public class GenUtils {

    /**
     * 初始化表信息
     */
    public static void initTable(GenTableEntity genTableEntity) {
        genTableEntity.setClassName(convertClassName(genTableEntity.getTableName()));
        genTableEntity.setPackageName(GenConfig.getPackageName());
        genTableEntity.setModuleName(getModuleName(GenConfig.getPackageName()));
        genTableEntity.setBusinessName(getBusinessName(genTableEntity.getTableName()));
        genTableEntity.setFunctionName(genTableEntity.getTableComment());
        genTableEntity.setFunctionAuthor(GenConfig.getAuthor());
    }

    /**
     * 表名转换成Java类名
     *
     * @param tableName
     *            表名称
     * @return 类名
     */
    public static String convertClassName(String tableName) {
        // 获取是否自动去除表前缀
        boolean autoRemovePre = GenConfig.getAutoRemovePre();
        // 获取表前缀
        String tablePrefix = GenConfig.getTablePrefix();
        if (autoRemovePre && StringUtils.isNotBlank(tablePrefix)) {
            String[] searchList = StringUtils.split(tablePrefix, ",");
            tableName = replaceFirst(tableName, searchList);
        }
        return convertToCamelCase(tableName);
    }

    /**
     * 批量替换前缀
     *
     * @param replacement
     *            替换值
     * @param searchList
     *            替换列表
     * @return
     */
    public static String replaceFirst(String replacement, String[] searchList) {
        String text = replacement;
        for (String searchString : searchList) {
            if (replacement.startsWith(searchString)) {
                text = replacement.replaceFirst(searchString, "");
                break;
            }
        }
        return text;
    }

    /**
     * 将下划线大写方式命名的字符串转换为驼峰式。如果转换前的下划线大写方式命名的字符串为空，则返回空字符串。 例如：HELLO_WORLD ->
     * HelloWorld
     *
     * @param name
     *            转换前的下划线大写方式命名的字符串
     * @return 转换后的驼峰式命名的字符串
     */
    public static String convertToCamelCase(String name) {
        StringBuilder result = new StringBuilder();
        if (StringUtils.isBlank(name)) {
            return "";
        } else if (!name.contains("_")) {
            // 不含下划线，仅将首字母大写
            return name.substring(0, 1).toUpperCase() + name.substring(1);
        }
        String[] camels = name.split("_");
        for (String camel : camels) {
            // 跳过原始字符串中开头、结尾的下换线或双重下划线
            if (camel.isEmpty()) {
                continue;
            }
            // 首字母大写
            result.append(camel.substring(0, 1).toUpperCase());
            result.append(camel.substring(1).toLowerCase());
        }
        return result.toString();
    }

    /**
     * 获取模块名
     *
     * @param packageName
     *            包名
     * @return 模块名
     */
    public static String getModuleName(String packageName) {
        int lastIndex = packageName.lastIndexOf(".");
        int nameLength = packageName.length();
        return StringUtils.substring(packageName, lastIndex + 1, nameLength);
    }

    /**
     * 获取业务名
     *
     * @param tableName
     *            表名
     * @return 业务名
     */
    public static String getBusinessName(String tableName) {
        int lastIndex = tableName.lastIndexOf("_");
        int nameLength = tableName.length();
        return StringUtils.substring(tableName, lastIndex + 1, nameLength);
    }

    /**
     * 初始化列属性字段
     */
    public static void initColumnField(GenTableColumnEntity column, GenTableEntity table) {
        String dataType = getDbType(column.getColumnType());
        String columnName = column.getColumnName();
        column.setTableId(table.getTableId());
        // 设置java字段名
        column.setJavaField(toCamelCase(columnName));
        // 设置默认类型
        column.setJavaType(GenConstants.TYPE_STRING);
        column.setQueryType(GenConstants.QUERY_EQ);

        if (arraysContains(GenConstants.COLUMN_TYPE_STR, dataType)
                || arraysContains(GenConstants.COLUMN_TYPE_TEXT, dataType)) {
            // 字符串长度超过500设置为文本域
            Integer columnLength = getColumnLength(column.getColumnType());
            String htmlType = columnLength >= 500 || arraysContains(GenConstants.COLUMN_TYPE_TEXT, dataType)
                    ? GenConstants.HTML_TEXTAREA
                    : GenConstants.HTML_INPUT;
            column.setHtmlType(htmlType);
        } else if (arraysContains(GenConstants.COLUMN_TYPE_TIME, dataType)) {
            // 时间类型
            column.setJavaType(GenConstants.TYPE_DATE);
            column.setHtmlType(GenConstants.HTML_DATETIME);
        } else if (arraysContains(GenConstants.COLUMNTYPE_NUMBER, dataType)) {
            // 数字类型
            column.setHtmlType(GenConstants.HTML_INPUT);
            // 如果是浮点型 统一用BigDecimal
            String[] str = StringUtils.split(StringUtils.substringBetween(column.getColumnType(), "(", ")"), ",");
            if (str != null && str.length == 2 && Integer.parseInt(str[1]) > 0) {
                column.setJavaType(GenConstants.TYPE_BIGDECIMAL);
            } else if (str != null && str.length == 1 && Integer.parseInt(str[0]) <= 10) {
                // 如果是整形
                column.setJavaType(GenConstants.TYPE_INTEGER);
            } else {
                // 长整形
                column.setJavaType(GenConstants.TYPE_LONG);
            }
        }

        // 插入字段（默认所有字段都需要插入）
        column.setIsInsert(GenConstants.REQUIRE);

        // 编辑字段
        if (!arraysContains(GenConstants.COLUMN_NAME_NOT_EDIT, columnName) && !"1".equals(column.getIsPk())) {
            column.setIsEdit(GenConstants.REQUIRE);
        }
        // 列表字段
        if (!arraysContains(GenConstants.COLUMN_NAME_NOT_LIST, columnName) && !"1".equals(column.getIsPk())) {
            column.setIsList(GenConstants.REQUIRE);
        }
        // 查询字段
        if (!arraysContains(GenConstants.COLUMN_NAME_NOT_QUERY, columnName) && !"1".equals(column.getIsPk())) {
            column.setIsQuery(GenConstants.REQUIRE);
        }

        // 查询字段类型
        if (StringUtils.endsWithIgnoreCase(columnName, "name")) {
            column.setQueryType(GenConstants.QUERY_LIKE);
        }
        // 状态字段设置单选框
        if (StringUtils.endsWithIgnoreCase(columnName, "status")) {
            column.setHtmlType(GenConstants.HTML_RADIO);
        }
        // 类型&性别字段设置下拉框
        else if (StringUtils.endsWithIgnoreCase(columnName, "type")
                || StringUtils.endsWithIgnoreCase(columnName, "sex")) {
            column.setHtmlType(GenConstants.HTML_SELECT);
        }
        // 图片字段设置图片上传控件
        else if (StringUtils.endsWithIgnoreCase(columnName, "image")) {
            column.setHtmlType(GenConstants.HTML_IMAGE_UPLOAD);
        }
        // 文件字段设置文件上传控件
        else if (StringUtils.endsWithIgnoreCase(columnName, "file")) {
            column.setHtmlType(GenConstants.HTML_FILE_UPLOAD);
        }
        // 内容字段设置富文本控件
        else if (StringUtils.endsWithIgnoreCase(columnName, "content")) {
            column.setHtmlType(GenConstants.HTML_EDITOR);
        }
    }

    /**
     * 获取数据库类型字段 例如： bigint 、varchar(64)
     *
     * @param columnType
     *            列类型
     * @return 截取后的列类型
     */
    public static String getDbType(String columnType) {
        if (StringUtils.indexOf(columnType, "(") > 0) {
            return StringUtils.substringBefore(columnType, "(");
        } else {
            return columnType;
        }
    }

    /**
     * 获取字段长度
     *
     * @param columnType
     *            列类型
     * @return 截取后的列类型
     */
    public static Integer getColumnLength(String columnType) {
        if (StringUtils.indexOf(columnType, "(") > 0) {
            String length = StringUtils.substringBetween(columnType, "(", ")");
            return Integer.valueOf(length);
        } else {
            return 0;
        }
    }

    /**
     * 驼峰式命名法 例如：user_name->userName
     */
    public static String toCamelCase(String s) {
        if (StringUtils.isNotBlank(s)) {
            // 小写
            s = s.toLowerCase();
            StringBuilder sb = new StringBuilder(s.length());
            // 大写
            boolean upperCase = false;
            for (int i = 0; i < s.length(); i++) {
                char c = s.charAt(i);
                if (c == '_') {
                    upperCase = true;
                } else if (upperCase) {
                    sb.append(Character.toUpperCase(c));
                    upperCase = false;
                } else {
                    sb.append(c);
                }
            }
            return sb.toString();
        }
        return s;
    }

    /**
     * 校验数组是否包含指定值
     *
     * @param arr
     *            数组
     * @param targetValue
     *            值
     * @return 是否包含
     */
    public static boolean arraysContains(String[] arr, String targetValue) {
        return Arrays.asList(arr).contains(targetValue);
    }
}


--- File: play-generator/src/main/java/com/starry/generator/utils/VelocityInitializer.java ---
package com.starry.generator.utils;

import java.util.Properties;
import org.apache.velocity.app.Velocity;

/**
 * @author VelocityEngine工厂
 * @since 2022/7/19
 */
public class VelocityInitializer {

    public static void initVelocity() {
        Properties p = new Properties();
        // 加载classpath目录下的vm文件
        p.setProperty("resource.loader.file.class",
                "org.apache.velocity.runtime.resource.loader.ClasspathResourceLoader");
        // 定义字符集
        p.setProperty(Velocity.INPUT_ENCODING, "UTF-8");
        // 初始化Velocity引擎，指定配置Properties
        Velocity.init(p);
    }
}


--- File: play-generator/src/main/java/com/starry/generator/utils/VelocityUtils.java ---
package com.starry.generator.utils;

import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.StrUtil;
import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONObject;
import com.starry.common.utils.StringUtils;
import com.starry.generator.constant.GenConstants;
import com.starry.generator.entity.GenTableColumnEntity;
import com.starry.generator.entity.GenTableEntity;
import java.util.*;
import org.apache.velocity.VelocityContext;

/**
 * @author admin 模板处理工具类
 * @since 2022/7/19
 */
public class VelocityUtils {

    /**
     * 默认上级菜单，系统工具
     */
    private static final String DEFAULT_PARENT_MENU_ID = "3";

    /**
     * 项目空间路径
     */
    private static final String PROJECT_PATH = "main/java";
    /**
     * mybatis空间路径
     */
    private static final String MYBATIS_PATH = "main/resources/mapper";

    /**
     * 设置模板变量信息
     *
     * @return 模板列表
     */
    public static VelocityContext prepareContext(GenTableEntity genTableEntity) {
        String moduleName = genTableEntity.getModuleName();
        String businessName = genTableEntity.getBusinessName();
        String packageName = genTableEntity.getPackageName();
        String tplCategory = genTableEntity.getTplCategory();
        String functionName = genTableEntity.getFunctionName();

        VelocityContext velocityContext = new VelocityContext();
        velocityContext.put("tplCategory", genTableEntity.getTplCategory());
        velocityContext.put("tableName", genTableEntity.getTableName());
        velocityContext.put("functionName", StrUtil.isNotBlank(functionName) ? functionName : "【请填写功能名称】");
        velocityContext.put("ClassName", genTableEntity.getClassName());
        // 首字母小写
        velocityContext.put("className", StringUtils.uncapitalize(genTableEntity.getClassName()));
        velocityContext.put("moduleName", genTableEntity.getModuleName());
        // 首字母大写
        velocityContext.put("BusinessName", StringUtils.capitalize(genTableEntity.getBusinessName()));
        velocityContext.put("businessName", genTableEntity.getBusinessName());
        velocityContext.put("basePackage", getPackagePrefix(packageName));
        velocityContext.put("packageName", packageName);
        velocityContext.put("author", genTableEntity.getFunctionAuthor());
        velocityContext.put("datetime", DateUtil.format(new Date(), "yyyy-MM-dd"));
        velocityContext.put("pkColumn", genTableEntity.getPkColumn());
        velocityContext.put("importList", getImportList(genTableEntity));
        velocityContext.put("permissionPrefix", getPermissionPrefix(moduleName, businessName));
        velocityContext.put("columns", genTableEntity.getColumns());
        velocityContext.put("table", genTableEntity);
        velocityContext.put("dicts", getDicts(genTableEntity));
        setMenuVelocityContext(velocityContext, genTableEntity);
        if (GenConstants.TPL_TREE.equals(tplCategory)) {
            setTreeVelocityContext(velocityContext, genTableEntity);
        }
        if (GenConstants.TPL_SUB.equals(tplCategory)) {
            setSubVelocityContext(velocityContext, genTableEntity);
        }
        return velocityContext;
    }

    /**
     * 获取包前缀
     *
     * @param packageName
     *            包名称
     * @return 包前缀名称
     */
    public static String getPackagePrefix(String packageName) {
        int lastIndex = packageName.lastIndexOf(".");
        return StringUtils.substring(packageName, 0, lastIndex);
    }

    /**
     * 根据列类型获取导入包
     *
     * @param genTableEntity
     *            业务表对象
     * @return 返回需要导入的包列表
     */
    public static HashSet<String> getImportList(GenTableEntity genTableEntity) {
        List<GenTableColumnEntity> columns = genTableEntity.getColumns();
        GenTableEntity subGenTableEntity = genTableEntity.getSubTable();
        HashSet<String> importList = new HashSet<>();
        if (StringUtils.isNotNull(subGenTableEntity)) {
            importList.add("java.util.List");
        }
        for (GenTableColumnEntity column : columns) {
            // 不是基础字段 且是 时间类型
            if (!column.isSuperColumn() && GenConstants.TYPE_DATE.equals(column.getJavaType())) {
                importList.add("java.util.Date");
                importList.add("com.fasterxml.jackson.annotation.JsonFormat");
            } else if (!column.isSuperColumn() && GenConstants.TYPE_BIGDECIMAL.equals(column.getJavaType())) {
                importList.add("java.math.BigDecimal");
            }
        }
        return importList;
    }

    /**
     * 获取权限前缀
     *
     * @param moduleName
     *            模块名称
     * @param businessName
     *            业务名称
     * @return 返回权限前缀
     */
    public static String getPermissionPrefix(String moduleName, String businessName) {
        return StringUtils.format("{}:{}", moduleName, businessName);
    }

    /**
     * 根据列类型获取字典组
     *
     * @param genTableEntity
     *            业务表对象
     * @return 返回字典组
     */
    public static String getDicts(GenTableEntity genTableEntity) {
        List<GenTableColumnEntity> columns = genTableEntity.getColumns();
        Set<String> dicts = new HashSet<>();
        addDicts(dicts, columns);
        if (StringUtils.isNotNull(genTableEntity.getSubTable())) {
            List<GenTableColumnEntity> subColumns = genTableEntity.getSubTable().getColumns();
            addDicts(dicts, subColumns);
        }
        return StringUtils.join(dicts, ", ");
    }

    /**
     * 添加字典列表
     *
     * @param dicts
     *            字典列表
     * @param columns
     *            列集合
     */
    public static void addDicts(Set<String> dicts, List<GenTableColumnEntity> columns) {
        for (GenTableColumnEntity column : columns) {
            if (!column.isSuperColumn() && StrUtil.isNotBlank(column.getDictType()) && StringUtils.equalsAny(
                    column.getHtmlType(),
                    new String[]{GenConstants.HTML_SELECT, GenConstants.HTML_RADIO, GenConstants.HTML_CHECKBOX})) {
                dicts.add("'" + column.getDictType() + "'");
            }
        }
    }

    public static void setMenuVelocityContext(VelocityContext context, GenTableEntity genTableEntity) {
        String options = genTableEntity.getOptions();
        JSONObject paramsObj = JSON.parseObject(options);
        String parentMenuId = getParentMenuId(paramsObj);
        context.put("parentMenuId", parentMenuId);
    }

    public static void setTreeVelocityContext(VelocityContext context, GenTableEntity genTableEntity) {
        String options = genTableEntity.getOptions();
        JSONObject paramsObj = JSON.parseObject(options);
        String treeCode = getTreeCode(paramsObj);
        String treeParentCode = getTreeParentCode(paramsObj);
        String treeName = getTreeName(paramsObj);

        context.put("treeCode", treeCode);
        context.put("treeParentCode", treeParentCode);
        context.put("treeName", treeName);
        context.put("expandColumn", getExpandColumn(genTableEntity));
        if (paramsObj.containsKey(GenConstants.TREE_PARENT_CODE)) {
            context.put("tree_parent_code", paramsObj.getString(GenConstants.TREE_PARENT_CODE));
        }
        if (paramsObj.containsKey(GenConstants.TREE_NAME)) {
            context.put("tree_name", paramsObj.getString(GenConstants.TREE_NAME));
        }
    }

    public static void setSubVelocityContext(VelocityContext context, GenTableEntity genTableEntity) {
        GenTableEntity subTable = genTableEntity.getSubTable();
        String subTableName = genTableEntity.getSubTableName();
        String subTableFkName = genTableEntity.getSubTableFkName();
        String subClassName = genTableEntity.getSubTable().getClassName();
        String subTableFkClassName = StringUtils.convertToCamelCase(subTableFkName);

        context.put("subTable", subTable);
        context.put("subTableName", subTableName);
        context.put("subTableFkName", subTableFkName);
        context.put("subTableFkClassName", subTableFkClassName);
        context.put("subTableFkclassName", StringUtils.uncapitalize(subTableFkClassName));
        context.put("subClassName", subClassName);
        context.put("subclassName", StringUtils.uncapitalize(subClassName));
        context.put("subImportList", getImportList(genTableEntity.getSubTable()));
    }

    /**
     * 获取上级菜单ID字段
     *
     * @param paramsObj
     *            生成其他选项
     * @return 上级菜单ID字段
     */
    public static String getParentMenuId(JSONObject paramsObj) {
        if (StringUtils.isNotEmpty(paramsObj) && paramsObj.containsKey(GenConstants.PARENT_MENU_ID)
                && StrUtil.isNotBlank(paramsObj.getString(GenConstants.PARENT_MENU_ID))) {
            return paramsObj.getString(GenConstants.PARENT_MENU_ID);
        }
        return DEFAULT_PARENT_MENU_ID;
    }

    /**
     * 获取树编码
     *
     * @param paramsObj
     *            生成其他选项
     * @return 树编码
     */
    public static String getTreeCode(JSONObject paramsObj) {
        if (paramsObj.containsKey(GenConstants.TREE_CODE)) {
            return StringUtils.toCamelCase(paramsObj.getString(GenConstants.TREE_CODE));
        }
        return StringUtils.EMPTY;
    }

    /**
     * 获取树父编码
     *
     * @param paramsObj
     *            生成其他选项
     * @return 树父编码
     */
    public static String getTreeParentCode(JSONObject paramsObj) {
        if (paramsObj.containsKey(GenConstants.TREE_PARENT_CODE)) {
            return StringUtils.toCamelCase(paramsObj.getString(GenConstants.TREE_PARENT_CODE));
        }
        return StringUtils.EMPTY;
    }

    /**
     * 获取树名称
     *
     * @param paramsObj
     *            生成其他选项
     * @return 树名称
     */
    public static String getTreeName(JSONObject paramsObj) {
        if (paramsObj.containsKey(GenConstants.TREE_NAME)) {
            return StringUtils.toCamelCase(paramsObj.getString(GenConstants.TREE_NAME));
        }
        return StringUtils.EMPTY;
    }

    public static int getExpandColumn(GenTableEntity genTableEntity) {
        String options = genTableEntity.getOptions();
        JSONObject paramsObj = JSON.parseObject(options);
        String treeName = paramsObj.getString(GenConstants.TREE_NAME);
        int num = 0;
        for (GenTableColumnEntity column : genTableEntity.getColumns()) {
            if ("1".equals(column.getIsList())) {
                num++;
                String columnName = column.getColumnName();
                if (columnName.equals(treeName)) {
                    break;
                }
            }
        }
        return num;
    }

    /**
     * 获取模板信息
     *
     * @return 模板列表
     */
    public static List<String> getTemplateList(String tplCategory) {
        List<String> templates = new ArrayList<>();
        templates.add("vm/java/entity.java.vm");
        templates.add("vm/java/mapper.java.vm");
        templates.add("vm/java/service.java.vm");
        templates.add("vm/java/serviceImpl.java.vm");
        templates.add("vm/java/controller.java.vm");
        templates.add("vm/java/queryVo.java.vm");
        templates.add("vm/java/addVo.java.vm");
        templates.add("vm/xml/mapper.xml.vm");
        templates.add("vm/sql/sql.vm");
        templates.add("vm/js/api.js.vm");
        if (GenConstants.TPL_CRUD.equals(tplCategory)) {
            templates.add("vm/vue/index.vue.vm");
        } else if (GenConstants.TPL_TREE.equals(tplCategory)) {
            templates.add("vm/vue/index-tree.vue.vm");
        } else if (GenConstants.TPL_SUB.equals(tplCategory)) {
            templates.add("vm/vue/index.vue.vm");
            templates.add("vm/java/sub-domain.java.vm");
        }
        return templates;
    }

    /**
     * 获取文件名
     *
     * @return 文件名称
     */
    public static String getFileName(String template, GenTableEntity genTableEntity) {
        // 文件名称
        String fileName = "";
        // 包路径
        String packageName = genTableEntity.getPackageName();
        // 模块名
        String moduleName = genTableEntity.getModuleName();
        // 大写类名
        String className = genTableEntity.getClassName();
        // 业务名称
        String businessName = genTableEntity.getBusinessName();

        // 相关生成文件目录
        String javaPath = PROJECT_PATH + "/" + StringUtils.replace(packageName, ".", "/");
        String mybatisPath = MYBATIS_PATH + "/" + moduleName;
        String vuePath = "vue";

        if (template.contains("entity.java.vm")) {
            fileName = StringUtils.format("{}/module/entity/{}Entity.java", javaPath, className);
        } else if (template.contains("sub-domain.java.vm")
                && StringUtils.equals(GenConstants.TPL_SUB, genTableEntity.getTplCategory())) {
            fileName = StringUtils.format("{}/domain/{}.java", javaPath, genTableEntity.getSubTable().getClassName());
        } else if (template.contains("mapper.java.vm")) {
            fileName = StringUtils.format("{}/mapper/{}Mapper.java", javaPath, className);
        } else if (template.contains("service.java.vm")) {
            fileName = StringUtils.format("{}/service/I{}Service.java", javaPath, className);
        } else if (template.contains("serviceImpl.java.vm")) {
            fileName = StringUtils.format("{}/service/impl/{}ServiceImpl.java", javaPath, className);
        } else if (template.contains("controller.java.vm")) {
            fileName = StringUtils.format("{}/controller/{}Controller.java", javaPath, className);
        } else if (template.contains("queryVo.java.vm")) {
            fileName = StringUtils.format("{}/vo/{}QueryVo.java", javaPath, className);
        } else if (template.contains("addVo.java.vm")) {
            fileName = StringUtils.format("{}/vo/{}AddVo.java", javaPath, className);
        } else if (template.contains("mapper.xml.vm")) {
            fileName = StringUtils.format("{}/{}Mapper.xml", mybatisPath, className);
        } else if (template.contains("sql.vm")) {
            fileName = businessName + "Menu.sql";
        } else if (template.contains("api.js.vm")) {
            fileName = StringUtils.format("{}/api/{}/{}.js", vuePath, moduleName, businessName);
        } else if (template.contains("index.vue.vm")) {
            fileName = StringUtils.format("{}/views/{}/{}/index.vue", vuePath, moduleName, businessName);
        } else if (template.contains("index-tree.vue.vm")) {
            fileName = StringUtils.format("{}/views/{}/{}/index.vue", vuePath, moduleName, businessName);
        }
        return fileName;
    }
}


--- File: play-generator/src/main/resources/config.properties ---
# 数据库配置
db.url=jdbc:mysql://122.51.20.105:3306/play-with?useUnicode=true&characterEncoding=utf-8&serverTimezone=Asia/Shanghai
db.driver=com.mysql.cj.jdbc.Driver
db.name=play-with
db.username=root
db.password=KdaKRZ2trpdhNePa

# 代码生成配置
gen.author=huchuansai
gen.packageName=com.starry.admin
gen.outputDir=./generated-code
gen.autoRemovePre=false
gen.tablePrefix=sys_
gen.tplCategory=crud

# 要生成的表名，多个表名用逗号分隔
gen.tableNames=sys_role

--- File: play-generator/src/main/resources/generator.properties ---
dataSource.url=jdbc:mysql://42.194.222.32:3306/admin?useUnicode=true&characterEncoding=utf-8&serverTimezone=Asia/Shanghai
dataSource.driverName=com.mysql.cj.jdbc.Driver
dataSource.username=root
dataSource.password=yx_admin
package.base=com.admin.modules

--- File: play-generator/src/main/resources/generator.yml ---
# 代码生成
gen:
  # 作者
  author: admin
  # 默认生成包路径 system 需改成自己的模块名称 如 system monitor tool
  packageName: com.starry.play
  # 自动去除表前缀，默认是false
  autoRemovePre: false
  # 表前缀（生成类名不会包含表前缀，多个用逗号分隔）
  tablePrefix: sys_

--- File: play-generator/src/main/resources/logback.xml ---
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
    <appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
        <encoder>
            <pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
        </encoder>
    </appender>
    
    <root level="INFO">
        <appender-ref ref="CONSOLE"/>
    </root>
</configuration> 

--- File: play-generator/src/main/resources/mapper/generator/GenTableColumnMapper.xml ---
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.starry.generator.mapper.GenTableColumnMapper">
    <!-- 通用查询映射结果 -->
    <resultMap id="BaseResultMap" type="com.starry.generator.entity.GenTableColumnEntity">
        <id column="column_id" property="columnId"/>
        <result column="table_id" property="tableId"/>
        <result column="column_name" property="columnName"/>
        <result column="column_comment" property="columnComment"/>
        <result column="column_type" property="columnType"/>
        <result column="java_type" property="javaType"/>
        <result column="java_field" property="javaField"/>
        <result column="is_pk" property="isPk"/>
        <result column="is_increment" property="isIncrement"/>
        <result column="is_required" property="isRequired"/>
        <result column="is_insert" property="isInsert"/>
        <result column="is_edit" property="isEdit"/>
        <result column="is_list" property="isList"/>
        <result column="is_query" property="isQuery"/>
        <result column="query_type" property="queryType"/>
        <result column="html_type" property="htmlType"/>
        <result column="dict_type" property="dictType"/>
        <result column="sort" property="sort"/>
        <result column="updated_time" property="updatedTime"/>
        <result column="updated_by" property="updatedBy"/>
        <result column="created_time" property="createdTime"/>
        <result column="created_by" property="createdBy"/>
        <result column="deleted" property="deleted"/>
        <result column="version" property="version"/>
    </resultMap>

    <select id="selectDbTableColumnsByName" resultMap="BaseResultMap">
        select column_name, (case when (is_nullable = 'no' <![CDATA[ && ]]> column_key != 'PRI') then '1' else null end)
        as is_required, (case when column_key = 'PRI' then '1' else '0' end) as is_pk, ordinal_position as sort,
        column_comment, (case when extra = 'auto_increment' then '1' else '0' end) as is_increment, column_type
        from information_schema.columns where table_schema = (select database()) and table_name = (#{tableName})
        order by ordinal_position
    </select>
</mapper>


--- File: play-generator/src/main/resources/mapper/generator/GenTableMapper.xml ---
<?xml version="1.0" encoding="utf-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
<mapper namespace="com.starry.generator.mapper.GenTableMapper">
    <resultMap id="BaseResultMap" type="com.starry.generator.entity.GenTableEntity">
        <id property="tableId" column="table_id"/>
        <result property="tableName" column="table_name"/>
        <result property="tableComment" column="table_comment"/>
        <result property="subTableName" column="sub_table_name"/>
        <result property="subTableFkName" column="sub_table_fk_name"/>
        <result property="className" column="class_name"/>
        <result property="tplCategory" column="tpl_category"/>
        <result property="packageName" column="package_name"/>
        <result property="moduleName" column="module_name"/>
        <result property="businessName" column="business_name"/>
        <result property="functionName" column="function_name"/>
        <result property="functionAuthor" column="function_author"/>
        <result property="genType" column="gen_type"/>
        <result property="genPath" column="gen_path"/>
        <result property="options" column="options"/>
        <result column="updated_time" property="updatedTime"/>
        <result column="updated_by" property="updatedBy"/>
        <result column="created_time" property="createdTime"/>
        <result column="created_by" property="createdBy"/>
        <result column="deleted" property="deleted"/>

        <collection property="columns" javaType="java.util.List"
                    ofType="com.starry.generator.entity.GenTableColumnEntity">
            <id property="columnId" column="column_id"/>
            <result property="tableId" column="table_id"/>
            <result property="columnName" column="column_name"/>
            <result property="columnComment" column="column_comment"/>
            <result column="java_type" property="javaType"/>
            <result column="java_field" property="javaField"/>
            <result column="is_pk" property="isPk"/>
            <result column="is_increment" property="isIncrement"/>
            <result column="is_required" property="isRequired"/>
            <result column="is_insert" property="isInsert"/>
            <result column="is_edit" property="isEdit"/>
            <result column="is_list" property="isList"/>
            <result column="is_query" property="isQuery"/>
            <result column="query_type" property="queryType"/>
            <result column="html_type" property="htmlType"/>
            <result column="dict_type" property="dictType"/>
            <result column="sort" property="sort"/>
        </collection>
    </resultMap>

    <sql id="selectGenTableVo">
        select table_id,
               table_name,
               table_comment,
               sub_table_name,
               sub_table_fk_name,
               class_name,
               tpl_category,
               package_name,
               module_name,
               business_name,
               function_name,
               function_author,
               gen_type,
               gen_path,
               options,
               created_by,
               created_time,
               updated_by,
               updated_time
        from gen_table
    </sql>

    <select id="selectDbTableList" resultMap="BaseResultMap">
        select table_name, table_comment, create_time, update_time from information_schema.tables
        where table_schema = (select database())
        AND table_name NOT LIKE 'qrtz_%' AND table_name NOT LIKE 'gen_%'
        AND table_name NOT IN (select table_name from gen_table where deleted = 0)
        <if test="genTableEntity.tableName != null and genTableEntity.tableName != ''">
            AND lower(table_name) like lower(concat('%', #{genTableEntity.tableName}, '%'))
        </if>
        <if test="genTableEntity.tableComment != null and genTableEntity.tableComment != ''">
            AND lower(table_comment) like lower(concat('%', #{genTableEntity.tableComment}, '%'))
        </if>
        <if test="genTableEntity.beginTime != null and genTableEntity.beginTime != ''">
            <!-- 开始时间检索 -->
            AND date_format(create_time,'%y%m%d') &gt;= date_format(#{genTableEntity.beginTime},'%y%m%d')
        </if>
        <if test="genTableEntity.endTime != null and genTableEntity.endTime != ''">
            <!-- 结束时间检索 -->
            AND date_format(create_time,'%y%m%d') &lt;= date_format(#{genTableEntity.endTime},'%y%m%d')
        </if>
        order by create_time desc
    </select>

    <select id="selectDbTableListByNames" resultMap="BaseResultMap">
        select table_name, table_comment, create_time, update_time from information_schema.tables
        where table_name NOT LIKE 'qrtz_%' and table_name NOT LIKE 'gen_%' and table_schema = (select database())
        and table_name in
        <foreach collection="array" item="name" open="(" separator="," close=")">
            #{name}
        </foreach>
    </select>

    <select id="selectGenTableList" resultMap="BaseResultMap">
        <include refid="selectGenTableVo"/>
        where deleted = 0
        <if test="genTableEntity.tableName != null and genTableEntity.tableName != ''">
            AND lower(table_name) like lower(concat('%', #{genTableEntity.tableName}, '%'))
        </if>
        <if test="genTableEntity.tableComment != null and genTableEntity.tableComment != ''">
            AND lower(table_comment) like lower(concat('%', #{genTableEntity.tableComment}, '%'))
        </if>
        <if test="genTableEntity.beginTime != null and genTableEntity.beginTime != ''">
            <!-- 开始时间检索 -->
            AND date_format(created_time,'%y%m%d') &gt;= date_format(#{genTableEntity.beginTime},'%y%m%d')
        </if>
        <if test="genTableEntity.endTime != null and genTableEntity.endTime != ''">
            <!-- 结束时间检索 -->
            AND date_format(created_time,'%y%m%d') &lt;= date_format(#{genTableEntity.endTime},'%y%m%d')
        </if>
        order by created_time desc
    </select>

    <select id="selectGenTableById" resultMap="BaseResultMap">
        SELECT t.table_id, t.table_name, t.table_comment, t.sub_table_name, t.sub_table_fk_name, t.class_name,
        t.tpl_category, t.package_name, t.module_name, t.business_name, t.function_name, t.function_author, t.gen_type,
        t.gen_path, t.options,
        c.column_id, c.column_name, c.column_comment, c.column_type, c.java_type, c.java_field, c.is_pk, c.is_increment,
        c.is_required, c.is_insert, c.is_edit, c.is_list, c.is_query, c.query_type, c.html_type, c.dict_type, c.sort
        FROM gen_table t
        LEFT JOIN gen_table_column c ON t.table_id = c.table_id and c.deleted = 0
        where t.deleted = 0 and t.table_id = #{tableId} order by c.sort
    </select>

    <select id="selectGenTableByName" resultMap="BaseResultMap">
        SELECT t.table_id, t.table_name, t.table_comment, t.sub_table_name, t.sub_table_fk_name, t.class_name,
        t.tpl_category, t.package_name, t.module_name, t.business_name, t.function_name, t.function_author, t.gen_type,
        t.gen_path, t.options,
        c.column_id, c.column_name, c.column_comment, c.column_type, c.java_type, c.java_field, c.is_pk, c.is_increment,
        c.is_required, c.is_insert, c.is_edit, c.is_list, c.is_query, c.query_type, c.html_type, c.dict_type, c.sort
        FROM gen_table t
        LEFT JOIN gen_table_column c ON t.table_id = c.table_id and c.deleted = 0
        where t.deleted = 0 and t.table_name = #{tableName} order by c.sort
    </select>
</mapper>

--- File: play-generator/src/main/resources/vm/java/addVo.java.vm ---
package ${packageName}.vo;

import com.fasterxml.jackson.annotation.JsonFormat;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import org.springframework.format.annotation.DateTimeFormat;
#foreach ($import in $importList)
import ${import};
#end
import javax.validation.constraints.NotNull;
import javax.validation.constraints.NotBlank;
import java.time.LocalDateTime;

/**
 * ${functionName}添加参数VO
 *
 * @author ${author}
 * @since ${datetime}
 */
@Data
@ApiModel(value = "${functionName}添加参数", description = "新增${functionName}信息的请求参数")
public class ${ClassName}AddVo {

#foreach ($column in $columns)
#if(!$column.superColumn && !$column.pk)
#if($column.insert == "1")
    /**
     * $column.columnComment
     */
#if($column.javaType == 'Date')
    @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
#if($column.isRequired == "1")
    @NotNull(message = "${column.columnComment}不能为空")
    @ApiModelProperty(value = "$column.columnComment", required = true, example = "2024-01-01 00:00:00", notes = "$column.columnComment")
#else
    @ApiModelProperty(value = "$column.columnComment", example = "2024-01-01 00:00:00", notes = "$column.columnComment")
#end
#elseif($column.javaType == 'String')
#if($column.isRequired == "1")
    @NotBlank(message = "${column.columnComment}不能为空")
    @ApiModelProperty(value = "$column.columnComment", required = true, example = "示例${column.columnComment}", notes = "$column.columnComment")
#else
    @ApiModelProperty(value = "$column.columnComment", example = "示例${column.columnComment}", notes = "$column.columnComment")
#end
#elseif($column.javaType == 'Integer')
#if($column.isRequired == "1")
    @NotNull(message = "${column.columnComment}不能为空")
    @ApiModelProperty(value = "$column.columnComment", required = true, example = "1", notes = "$column.columnComment")
#else
    @ApiModelProperty(value = "$column.columnComment", example = "1", notes = "$column.columnComment")
#end
#elseif($column.javaType == 'Long')
#if($column.isRequired == "1")
    @NotNull(message = "${column.columnComment}不能为空")
    @ApiModelProperty(value = "$column.columnComment", required = true, example = "1", notes = "$column.columnComment")
#else
    @ApiModelProperty(value = "$column.columnComment", example = "1", notes = "$column.columnComment")
#end
#else
#if($column.isRequired == "1")
    @NotNull(message = "${column.columnComment}不能为空")
    @ApiModelProperty(value = "$column.columnComment", required = true, notes = "$column.columnComment")
#else
    @ApiModelProperty(value = "$column.columnComment", notes = "$column.columnComment")
#end
#end
    private $column.javaType $column.javaField;

#end
#end
#end
} 

--- File: play-generator/src/main/resources/vm/java/controller.java.vm ---
package ${packageName}.controller;

import com.starry.common.enums.BusinessType;
import com.starry.common.result.R;
import com.starry.common.annotation.Log;
import ${packageName}.entity.${ClassName}Entity;
import ${packageName}.vo.${ClassName}QueryVo;
import ${packageName}.vo.${ClassName}AddVo;
import ${packageName}.service.I${ClassName}Service;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiImplicitParams;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiParam;
import io.swagger.annotations.ApiResponse;
import io.swagger.annotations.ApiResponses;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;

import javax.annotation.Resource;
import com.baomidou.mybatisplus.core.metadata.IPage;

/**
 * ${functionName}Controller
 *
 * @author ${author}
 * @since  ${datetime}
 */
@Api(tags = "${functionName}管理", description = "${functionName}信息管理相关接口，包括查询、新增、修改、删除等操作")
@RestController
@RequestMapping("/${moduleName}/${businessName}")
public class ${ClassName}Controller {
    @Resource
    private I${ClassName}Service ${className}Service;

/**
 * 分页查询${functionName}列表
 */
@ApiOperation(value = "分页查询${functionName}", notes = "分页查询${functionName}信息列表")
@ApiResponses({
    @ApiResponse(code = 200, message = "操作成功", response = ${ClassName}Entity.class, responseContainer = "Page")
})
@PreAuthorize("@customSs.hasPermission('${permissionPrefix}:list')")
@PostMapping("/listByPage")
#if($table.crud || $table.sub)
public R listByPage(@ApiParam(value = "查询条件", required = true) @Validated @RequestBody ${ClassName}QueryVo vo) {
    return R.ok(${className}Service.selectByPage(vo));
}
#elseif($table.tree)
public R listByPage(@ApiParam(value = "查询条件", required = true) @Validated @RequestBody ${ClassName}QueryVo vo) {
    return R.ok(${className}Service.select${ClassName}List(vo));
}
#end

/**
 * 查询所有${functionName}列表
 */
@ApiOperation(value = "查询所有${functionName}", notes = "获取所有${functionName}信息列表")
@ApiResponses({
    @ApiResponse(code = 200, message = "操作成功", response = ${ClassName}Entity.class, responseContainer = "List")
})
@PreAuthorize("@customSs.hasPermission('${permissionPrefix}:list')")
@GetMapping("/listAll")
public R listAll() {
    return R.ok(${className}Service.selectAll());
}

    /**
     * 获取${functionName}详细信息
     */
    @ApiOperation(value = "获取${functionName}详情", notes = "根据ID获取${functionName}详细信息")
    @ApiImplicitParam(name = "${pkColumn.javaField}", value = "${functionName}ID", required = true, paramType = "path", dataType = "${pkColumn.javaType}", example = "1")
    @ApiResponses({
        @ApiResponse(code = 200, message = "操作成功", response = ${ClassName}Entity.class)
    })
    @PreAuthorize("@customSs.hasPermission('${permissionPrefix}:query')")
    @GetMapping(value = "/{${pkColumn.javaField}}")
    public R getInfo(@PathVariable("${pkColumn.javaField}") ${pkColumn.javaType} ${pkColumn.javaField}) {
        return R.ok(${className}Service.select${ClassName}By${pkColumn.capJavaField}(${pkColumn.javaField}));
    }

    /**
     * 新增${functionName}
     */
    @ApiOperation(value = "新增${functionName}", notes = "创建新的${functionName}信息")
    @ApiResponses({
        @ApiResponse(code = 200, message = "操作成功"),
        @ApiResponse(code = 500, message = "添加失败")
    })
    @PreAuthorize("@customSs.hasPermission('${permissionPrefix}:create')")
    @Log(title = "${functionName}", businessType = BusinessType.INSERT)
    @PostMapping("/create")
    public R create(@ApiParam(value = "${functionName}信息", required = true) @Validated @RequestBody ${ClassName}AddVo vo) {
        boolean success = ${className}Service.create(vo);
        if (success) {
            return R.ok();
        }
        return R.error("添加失败");
    }

    /**
     * 修改${functionName}
     */
    @ApiOperation(value = "修改${functionName}", notes = "根据ID修改${functionName}信息")
    @ApiImplicitParam(name = "${pkColumn.javaField}", value = "${functionName}ID", required = true, paramType = "path", dataType = "${pkColumn.javaType}", example = "1")
    @ApiResponses({
        @ApiResponse(code = 200, message = "操作成功"),
        @ApiResponse(code = 500, message = "修改失败")
    })
    @PreAuthorize("@customSs.hasPermission('${permissionPrefix}:edit')")
    @Log(title = "${functionName}", businessType = BusinessType.UPDATE)
    @PostMapping(value = "/update/{${pkColumn.javaField}}")
    public R update(@PathVariable ${pkColumn.javaType} ${pkColumn.javaField}, @ApiParam(value = "${functionName}信息", required = true) @RequestBody ${ClassName}Entity ${className}) {
        ${className}.set${pkColumn.capJavaField}(${pkColumn.javaField});
        boolean success = ${className}Service.update(${className});
        if (success) {
            return R.ok();
        }
        return R.error("修改失败");
    }

    /**
     * 删除${functionName}
     */
    @ApiOperation(value = "删除${functionName}", notes = "根据ID批量删除${functionName}信息")
    @ApiImplicitParam(name = "${pkColumn.javaField}s", value = "${functionName}ID数组", required = true, paramType = "path", dataType = "${pkColumn.javaType}[]", example = "1,2,3")
    @ApiResponses({
        @ApiResponse(code = 200, message = "操作成功", response = Integer.class)
    })
    @PreAuthorize("@customSs.hasPermission('${permissionPrefix}:remove')")
    @Log(title = "${functionName}", businessType = BusinessType.DELETE)
    @DeleteMapping("/{${pkColumn.javaField}s}")
    public R remove(@PathVariable ${pkColumn.javaType}[] ${pkColumn.javaField}s) {
        return R.ok(${className}Service.delete${ClassName}By${pkColumn.capJavaField}s(${pkColumn.javaField}s));
    }
}


--- File: play-generator/src/main/resources/vm/java/entity.java.vm ---
package ${packageName}.entity;

#foreach ($import in $importList)
import ${import};
#end
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.experimental.Accessors;
import cn.hutool.core.util.StrUtil;

import java.util.Date;
#if($table.crud || $table.sub)
import com.starry.common.domain.BaseEntity;
#elseif($table.tree)
import com.starry.common.domain.TreeEntity;
#end

/**
 * ${functionName}对象 ${tableName}
 *
 * @author ${author}
 * @since  ${datetime}
 */
    #if($table.crud || $table.sub)
        #set($Entity="BaseEntity")
    #elseif($table.tree)
        #set($Entity="TreeEntity")
    #end
        @Data
        @EqualsAndHashCode(callSuper = false)
        @TableName("$table.tableName")
        public class ${ClassName}Entity extends BaseEntity<${ClassName}Entity> {



    #foreach ($column in $columns)
        #if(!$table.isSuperColumn($column.javaField))
            /** $column.columnComment */
            #if($column.list)
                #set($parentheseIndex=$column.columnComment.indexOf("（"))
                #if($parentheseIndex != -1)
                    #set($comment=$column.columnComment.substring(0, $parentheseIndex))
                #else
                    #set($comment=$column.columnComment)
                #end
                #if($parentheseIndex != -1)
                @Excel(name = "${comment}", readConverterExp = "$column.readConverterExp()")
                #elseif($column.javaType == 'Date')
                @JsonFormat(pattern = "yyyy-MM-dd")
                @Excel(name = "${comment}", width = 30, dateFormat = "yyyy-MM-dd")
                #else
                @Excel(name = "${comment}")
                #end
            #end
        private $column.javaType $column.javaField;

        #end
    #end
    #if($table.sub)
        /** $table.subTable.functionName信息 */
        private List<${subClassName}> ${subclassName}List;

    #end

}


--- File: play-generator/src/main/resources/vm/java/mapper.java.vm ---
package ${packageName}.mapper;


import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import ${packageName}.entity.${ClassName};
#if($table.sub)
import ${packageName}.entity .${subClassName};
#end

/**
 * ${functionName}Mapper接口
 *
 * @author ${author}
 * @since  ${datetime}
 */
public interface ${ClassName}Mapper extends BaseMapper<${ClassName}Entity> {


    #if($table.sub)

        /**
         * 批量删除${subTable.functionName}
         * @param ${pkColumn.javaField}s 需要删除的数据主键集合
         * @return 结果
         */
        int delete${subClassName}By${subTableFkClassName}s(${pkColumn.javaType}[] ${pkColumn.javaField}s);

        /**
         * 批量新增${subTable.functionName}
         * @param ${subclassName}List ${subTable.functionName}列表
         * @return 结果
         */
        int batch${subClassName}(List<${subClassName}> ${subclassName}List);


    #end
}


--- File: play-generator/src/main/resources/vm/java/module.module.vo.java.vm ---
package ${packageName}.entity;

#foreach ($import in $importList)
import ${import};
#end
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.experimental.Accessors;

import java.util.Date;
#if($table.crud || $table.sub)
import com.starry.common.domain.BaseEntity;
#elseif($table.tree)
import com.starry.common.domain.TreeEntity;
#end

/**
 * ${functionName}对象 ${tableName}
 *
 * @author ${author}
 * @since  ${datetime}
 */
#if($table.crud || $table.sub)
    #set($Entity="BasePageEntity")
#elseif($table.tree)
    #set($Entity="TreeEntity")
#end
@Data
@EqualsAndHashCode(callSuper = false)
public class ${ClassName} extends ${Entity}{

private static final long serialVersionUID = 1L;

    #foreach ($column in $columns)
        #if(!$table.isSuperColumn($column.javaField))
        /** $column.columnComment */
            #if($column.list)
                #set($parentheseIndex=$column.columnComment.indexOf("（"))
                #if($parentheseIndex != -1)
                    #set($comment=$column.columnComment.substring(0, $parentheseIndex))
                #else
                    #set($comment=$column.columnComment)
                #end
                #if($parentheseIndex != -1)
                @Excel(name = "${comment}", readConverterExp = "$column.readConverterExp()")
                #elseif($column.javaType == 'Date')
                @JsonFormat(pattern = "yyyy-MM-dd")
                @Excel(name = "${comment}", width = 30, dateFormat = "yyyy-MM-dd")
                #else
                @Excel(name = "${comment}")
                #end
            #end
        private $column.javaType $column.javaField;

        #end
    #end
    #if($table.sub)
    /** $table.subTable.functionName信息 */
    private List<${subClassName}> ${subclassName}List;

    #end

}


--- File: play-generator/src/main/resources/vm/java/queryVo.java.vm ---
package ${packageName}.vo;

import com.starry.common.domain.BasePageEntity;
import com.fasterxml.jackson.annotation.JsonFormat;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import lombok.EqualsAndHashCode;
#foreach ($import in $importList)
import ${import};
#end
#if($table.tree)
import java.util.List;
#end

/**
 * ${functionName}查询参数VO
 *
 * @author ${author}
 * @since ${datetime}
 */
@EqualsAndHashCode(callSuper = true)
@Data
@ApiModel(value = "${functionName}查询参数", description = "查询${functionName}信息的条件参数")
public class ${ClassName}QueryVo extends BasePageEntity {

#foreach ($column in $columns)
#if(!$column.superColumn)
#if($column.query)
    /**
     * $column.columnComment
     */
#if($column.javaType == 'Date')
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    @ApiModelProperty(value = "$column.columnComment", example = "2024-01-01 00:00:00", notes = "$column.columnComment")
#elseif($column.javaType == 'String')
    @ApiModelProperty(value = "$column.columnComment", example = "示例${column.columnComment}", notes = "$column.columnComment")
#elseif($column.javaType == 'Integer')
    @ApiModelProperty(value = "$column.columnComment", example = "1", notes = "$column.columnComment")
#elseif($column.javaType == 'Long')
    @ApiModelProperty(value = "$column.columnComment", example = "1", notes = "$column.columnComment")
#else
    @ApiModelProperty(value = "$column.columnComment", notes = "$column.columnComment")
#end
    private $column.javaType $column.javaField;

#end
#end
#end
} 

--- File: play-generator/src/main/resources/vm/java/service.java.vm ---
package ${packageName}.service;

import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.service.IService;
import ${packageName}.entity .${ClassName};

/**
 * ${functionName}Service接口
 *
 * @author ${author}
 * @since  ${datetime}
 */
public interface I${ClassName}Service extends IService<${ClassName}Entity> {
    /**
     * 查询${functionName}
     * @param ${pkColumn.javaField} ${functionName}主键
     * @return ${functionName}
     */
        ${ClassName}Entity select${ClassName}By${pkColumn.capJavaField}(${pkColumn.javaType} ${pkColumn.javaField});

    /**
     * 查询${functionName}列表
     * @param ${className} ${functionName}
     * @return ${functionName}集合
     */
    IPage<${ClassName}Entity> select${ClassName}ByPage(${ClassName}Entity ${className});

    /**
     * 新增${functionName}
     * @param ${className} ${functionName}
     * @return 结果
     */
    boolean create(${ClassName}Entity ${className});

    /**
     * 修改${functionName}
     * @param ${className} ${functionName}
     * @return 结果
     */
    boolean update(${ClassName}Entity ${className});

    /**
     * 批量删除${functionName}
     *
     * @param ${pkColumn.javaField}s 需要删除的${functionName}主键集合
     * @return 结果
     */
    int delete${ClassName}By${pkColumn.capJavaField}s(${pkColumn.javaType}[] ${pkColumn.javaField}s);

    /**
     * 删除${functionName}信息
     *
     * @param ${pkColumn.javaField} ${functionName}主键
     * @return 结果
     */
    int delete${ClassName}By${pkColumn.capJavaField}(${pkColumn.javaType} ${pkColumn.javaField});
}


--- File: play-generator/src/main/resources/vm/java/serviceImpl.java.vm ---
package ${packageName}.service.impl;

import java.util.Arrays;

import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.starry.common.utils.IdUtils;
import cn.hutool.core.util.StrUtil;

import javax.annotation.Resource;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import org.springframework.stereotype.Service;
    #if($table.sub)
    import java.util.ArrayList;

    import com.starry.common.utils.StringUtils;
    import org.springframework.transaction.annotation.Transactional;
    import ${packageName}.entity.${subClassName};
    #end
import ${packageName}.mapper.${ClassName}Mapper;
import ${packageName}.entity.${ClassName};
import ${packageName}.service.I${ClassName}Service;

/**
 * ${functionName}Service业务层处理
 *
 * @author ${author}
 * @since  ${datetime}
 */
@Service
public class ${ClassName}ServiceImpl extends ServiceImpl<${ClassName}Mapper, ${ClassName}Entity> implements I${ClassName}Service {
    @Resource
    private ${ClassName}Mapper ${className}Mapper;

    /**
     * 查询${functionName}
     * @param ${pkColumn.javaField} ${functionName}主键
     * @return ${functionName}
     */
    @Override
    public ${ClassName}Entity select${ClassName}By${pkColumn.capJavaField}(${pkColumn.javaType} ${pkColumn.javaField}) {
        return this.baseMapper.selectById(id);
    }

    /**
     * 查询${functionName}列表
     * @param ${className} ${functionName}
     * @return ${functionName}
     */
    @Override
    public IPage<${ClassName}Entity> select${ClassName}ByPage(${ClassName}Entity ${className}) {
        Page<${ClassName}Entity> page = new Page<>(1, 10);
        return this.baseMapper.selectPage(page, new LambdaQueryWrapper<${ClassName}Entity>());
    }

    /**
     * 新增${functionName}
     * @param ${className} ${functionName}
     * @return 结果
     */
        #if($table.sub)
        @Transactional(rollbackFor = Exception.class)
        #end
    @Override
    public boolean create(${ClassName}Entity ${className}) {
        #if($table.sub)
            int rows = ${className}Mapper.insert${ClassName}(${className});
            insert${subClassName}(${className});
            return rows;
        #else
            if (StrUtil.isBlankIfStr(${className}.getId())){
            ${className}.setId(IdUtils.getUuid());
        }
            return save(${className});
        #end
    }

    /**
     * 修改${functionName}
     * @param ${className} ${functionName}
     * @return 结果
     */
        #if($table.sub)
        @Transactional(rollbackFor = Exception.class)
        #end
    @Override
    public boolean update(${ClassName}Entity ${className}) {
        #if($table.sub)
                ${className}Mapper.delete${subClassName}By${subTableFkClassName}(${className}.get${pkColumn.capJavaField}())
            ;
            insert${subClassName}(${className});
        #end
        return updateById(${className});
    }

    /**
     * 批量删除${functionName}
     * @param ${pkColumn.javaField}s 需要删除的${functionName}主键
     * @return 结果
     */
        #if($table.sub)
        @Transactional(rollbackFor = Exception.class)
        #end
    @Override
    public int delete${ClassName}By${pkColumn.capJavaField}s(${pkColumn.javaType}[] ${pkColumn.javaField}s) {
        #if($table.sub)
                ${className}Mapper.delete${subClassName}By${subTableFkClassName}s(${pkColumn.javaField}s);
        #end
        return ${className}Mapper.deleteBatchIds(Arrays.asList(${pkColumn.javaField}s));
    }

    /**
     * 删除${functionName}信息
     * @param ${pkColumn.javaField} ${functionName}主键
     * @return 结果
     */
        #if($table.sub)
        @Transactional(rollbackFor = Exception.class)
        #end
    @Override
    public int delete${ClassName}By${pkColumn.capJavaField}(${pkColumn.javaType} ${pkColumn.javaField}) {
        #if($table.sub)
                ${className}Mapper.delete${subClassName}By${subTableFkClassName}(${pkColumn.javaField});
        #end
        return ${className}Mapper.deleteById(${pkColumn.javaField});
    }
    #if($table.sub)

        /**
         * 新增${subTable.functionName}信息
         * @param ${className} ${functionName}对象
         */
        public void insert${subClassName}(${ClassName}Entity ${className}) {
            List<${subClassName}> ${subclassName}List = ${className}.get${subClassName}List();
            ${pkColumn.javaType} ${pkColumn.javaField} = ${className}.get${pkColumn.capJavaField}();
            if (StringUtils.isNotNull(${subclassName}List)) {
                List<${subClassName}> list = new ArrayList<${subClassName}>();
                for (${subClassName} ${subclassName} :${subclassName}List)
                {
                    ${subclassName}.set${subTableFkClassName}(${pkColumn.javaField});
                    list.add(${subclassName});
                }
                if (!list.isEmpty()) {
                        ${className}Mapper.batch${subClassName}(list);
                }
            }
        }
    #end
}


--- File: play-generator/src/main/resources/vm/js/api.js.vm ---
import request from '@/utils/request'

// 查询${functionName}列表
export function list${BusinessName}(query) {
    return request({
        url: '/${moduleName}/${businessName}/list',
        method: 'get',
        params: query
    })
}

// 查询${functionName}详细
export function get${BusinessName}(${pkColumn.javaField}) {
    return request({
        url: '/${moduleName}/${businessName}/' + ${pkColumn.javaField},
        method: 'get'
    })
}

// 新增${functionName}
export function add${BusinessName}(data) {
    return request({
        url: '/${moduleName}/${businessName}/create',
        method: 'post',
        data: data
    })
}

// 修改${functionName}
export function update${BusinessName}(${pkColumn.javaField}, data) {
    return request({
        url: '/${moduleName}/${businessName}/update/' + ${pkColumn.javaField},
        method: 'post',
        data: data
    })
}

// 删除${functionName}
export function del${BusinessName}(${pkColumn.javaField}) {
    return request({
        url: '/${moduleName}/${businessName}/' + ${pkColumn.javaField},
        method: 'delete'
    })
}


--- File: play-generator/src/main/resources/vm/sql/sql.vm ---
-- 菜单 SQL
insert into sys_menu (menu_name, parent_id, sort, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, created_by, created_time, updated_by, updated_time)
values('${functionName}', '${parentMenuId}', '1', '${businessName}', '${moduleName}/${businessName}/index', 1, 0, 'C', '0', '0', '${permissionPrefix}:list', '#', 'admin', sysdate(), '', null);

-- 按钮父菜单ID
SELECT @parentId := LAST_INSERT_ID();

-- 按钮 SQL
insert into sys_menu (menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, created_by, created_time, updated_by, updated_time)
values('${functionName}查询', @parentId, '1',  '#', '', 1, 0, 'F', '0', '0', '${permissionPrefix}:query',        '#', 'admin', sysdate(), '', null);

insert into sys_menu (menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, created_by, created_time, updated_by, updated_time)
values('${functionName}新增', @parentId, '2',  '#', '', 1, 0, 'F', '0', '0', '${permissionPrefix}:create',          '#', 'admin', sysdate(), '', null);

insert into sys_menu (menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, created_by, created_time, updated_by, updated_time)
values('${functionName}修改', @parentId, '3',  '#', '', 1, 0, 'F', '0', '0', '${permissionPrefix}:edit',         '#', 'admin', sysdate(), '', null);

insert into sys_menu (menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, created_by, created_time, updated_by, updated_time)
values('${functionName}删除', @parentId, '4',  '#', '', 1, 0, 'F', '0', '0', '${permissionPrefix}:remove',       '#', 'admin', sysdate(), '', null);

insert into sys_menu (menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, created_by, created_time, updated_by, updated_time)
values('${functionName}导出', @parentId, '5',  '#', '', 1, 0, 'F', '0', '0', '${permissionPrefix}:export',       '#', 'admin', sysdate(), '', null);

--- File: play-generator/src/main/resources/vm/vue/index-tree.vue.vm ---
<template>
  <div class="app-container">
    <el-form :model="queryParams" ref="queryForm" size="small" :inline="true" v-show="showSearch" label-width="68px">
        #foreach($column in $columns)
            #if($column.query)
                #set($dictType=$column.dictType)
                #set($AttrName=$column.javaField.substring(0,1).toUpperCase() + ${column.javaField.substring(1)})
                #set($parentheseIndex=$column.columnComment.indexOf("（"))
                #if($parentheseIndex != -1)
                    #set($comment=$column.columnComment.substring(0, $parentheseIndex))
                #else
                    #set($comment=$column.columnComment)
                #end
                #if($column.htmlType == "input")
                  <el-form-item label="${comment}" prop="${column.javaField}">
                    <el-input
                        v-model="queryParams.${column.javaField}"
                        placeholder="请输入${comment}"
                        clearable
                        @keyup.enter.native="handleQuery"
                    />
                  </el-form-item>
                #elseif(($column.htmlType == "select" || $column.htmlType == "radio") && "" != $dictType)
                  <el-form-item label="${comment}" prop="${column.javaField}">
                    <el-select v-model="queryParams.${column.javaField}" placeholder="请选择${comment}" clearable>
                      <el-option
                          v-for="dict in dict.type.${dictType}"
                          :key="dict.value"
                          :label="dict.label"
                          :value="dict.value"
                      />
                    </el-select>
                  </el-form-item>
                #elseif(($column.htmlType == "select" || $column.htmlType == "radio") && $dictType)
                  <el-form-item label="${comment}" prop="${column.javaField}">
                    <el-select v-model="queryParams.${column.javaField}" placeholder="请选择${comment}" clearable>
                      <el-option label="请选择字典生成" value=""/>
                    </el-select>
                  </el-form-item>
                #elseif($column.htmlType == "datetime" && $column.queryType != "BETWEEN")
                  <el-form-item label="${comment}" prop="${column.javaField}">
                    <el-date-picker clearable
                                    v-model="queryParams.${column.javaField}"
                                    type="date"
                                    value-format="yyyy-MM-dd"
                                    placeholder="选择${comment}">
                    </el-date-picker>
                  </el-form-item>
                #elseif($column.htmlType == "datetime" && $column.queryType == "BETWEEN")
                  <el-form-item label="${comment}">
                    <el-date-picker
                        v-model="daterange${AttrName}"
                        style="width: 240px"
                        value-format="yyyy-MM-dd"
                        type="daterange"
                        range-separator="-"
                        start-placeholder="开始日期"
                        end-placeholder="结束日期"
                    ></el-date-picker>
                  </el-form-item>
                #end
            #end
        #end
      <el-form-item>
        <el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery">搜索</el-button>
        <el-button icon="el-icon-refresh" size="mini" @click="resetQuery">重置</el-button>
      </el-form-item>
    </el-form>

    <el-row :gutter="10" class="mb8">
      <el-col :span="1.5">
        <el-button
            type="primary"
            plain
            icon="el-icon-plus"
            size="mini"
            @click="handleAdd"
            v-hasPermi="['${moduleName}:${businessName}:add']"
        >新增
        </el-button>
      </el-col>
      <el-col :span="1.5">
        <el-button
            type="info"
            plain
            icon="el-icon-sort"
            size="mini"
            @click="toggleExpandAll"
        >展开/折叠
        </el-button>
      </el-col>
      <right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
    </el-row>

    <el-table
        v-if="refreshTable"
        v-loading="loading"
        :data="${businessName}List"
        row-key="${treeCode}"
        :default-expand-all="isExpandAll"
        :tree-props="{children: 'children', hasChildren: 'hasChildren'}"
    >
        #foreach($column in $columns)
            #set($javaField=$column.javaField)
            #set($parentheseIndex=$column.columnComment.indexOf("（"))
            #if($parentheseIndex != -1)
                #set($comment=$column.columnComment.substring(0, $parentheseIndex))
            #else
                #set($comment=$column.columnComment)
            #end
            #if($column.pk)
            #elseif($column.list && $column.htmlType == "datetime")
              <el-table-column label="${comment}" align="center" prop="${javaField}" width="180">
                <template slot-scope="scope">
                  <span>{{ parseTime(scope.row.${javaField}, '{y}-{m}-{d}') }}</span>
                </template>
              </el-table-column>
            #elseif($column.list && $column.htmlType == "imageUpload")
              <el-table-column label="${comment}" align="center" prop="${javaField}" width="100">
                <template slot-scope="scope">
                  <image-preview :src="scope.row.${javaField}" :width="50" :height="50"/>
                </template>
              </el-table-column>
            #elseif($column.list && "" != $column.dictType)
              <el-table-column label="${comment}" align="center" prop="${javaField}">
                <template slot-scope="scope">
                    #if($column.htmlType == "checkbox")
                      <dict-tag :options="dict.type.${column.dictType}"
                                :value="scope.row.${javaField} ? scope.row.${javaField}.split(',') : []"/>
                    #else
                      <dict-tag :options="dict.type.${column.dictType}" :value="scope.row.${javaField}"/>
                    #end
                </template>
              </el-table-column>
            #elseif($column.list && "" != $javaField)
                #if(${foreach.index} == 1)
                  <el-table-column label="${comment}" prop="${javaField}"/>
                #else
                  <el-table-column label="${comment}" align="center" prop="${javaField}"/>
                #end
            #end
        #end
      <el-table-column label="操作" align="center" class-name="small-padding fixed-width">
        <template slot-scope="scope">
          <el-button
              size="mini"
              type="text"
              icon="el-icon-edit"
              @click="handleUpdate(scope.row)"
              v-hasPermi="['${moduleName}:${businessName}:edit']"
          >修改
          </el-button>
          <el-button
              size="mini"
              type="text"
              icon="el-icon-plus"
              @click="handleAdd(scope.row)"
              v-hasPermi="['${moduleName}:${businessName}:add']"
          >新增
          </el-button>
          <el-button
              size="mini"
              type="text"
              icon="el-icon-delete"
              @click="handleDelete(scope.row)"
              v-hasPermi="['${moduleName}:${businessName}:remove']"
          >删除
          </el-button>
        </template>
      </el-table-column>
    </el-table>

    <!-- 添加或修改${functionName}对话框 -->
    <el-dialog :title="title" :visible.sync="open" width="500px" append-to-body>
      <el-form ref="form" :model="form" :rules="rules" label-width="80px">
          #foreach($column in $columns)
              #set($field=$column.javaField)
              #if($column.insert && !$column.pk)
                  #if(($column.usableColumn) || (!$column.superColumn))
                      #set($parentheseIndex=$column.columnComment.indexOf("（"))
                      #if($parentheseIndex != -1)
                          #set($comment=$column.columnComment.substring(0, $parentheseIndex))
                      #else
                          #set($comment=$column.columnComment)
                      #end
                      #set($dictType=$column.dictType)
                      #if("" != $treeParentCode && $column.javaField == $treeParentCode)
                        <el-form-item label="${comment}" prop="${treeParentCode}">
                          <treeselect v-model="form.${treeParentCode}" :options="${businessName}Options"
                                      :normalizer="normalizer" placeholder="请选择${comment}"/>
                        </el-form-item>
                      #elseif($column.htmlType == "input")
                        <el-form-item label="${comment}" prop="${field}">
                          <el-input v-model="form.${field}" placeholder="请输入${comment}"/>
                        </el-form-item>
                      #elseif($column.htmlType == "imageUpload")
                        <el-form-item label="${comment}">
                          <image-upload v-model="form.${field}"/>
                        </el-form-item>
                      #elseif($column.htmlType == "fileUpload")
                        <el-form-item label="${comment}">
                          <file-upload v-model="form.${field}"/>
                        </el-form-item>
                      #elseif($column.htmlType == "editor")
                        <el-form-item label="${comment}">
                          <editor v-model="form.${field}" :min-height="192"/>
                        </el-form-item>
                      #elseif($column.htmlType == "select" && "" != $dictType)
                        <el-form-item label="${comment}" prop="${field}">
                          <el-select v-model="form.${field}" placeholder="请选择${comment}">
                            <el-option
                                v-for="dict in dict.type.${dictType}"
                                :key="dict.value"
                                :label="dict.label"
                                #if($column.javaType == "Integer" || $column.javaType ==
                                    "Long"):value="parseInt(dict.value)"#else:value="dict.value"#end

                            ></el-option>
                          </el-select>
                        </el-form-item>
                      #elseif($column.htmlType == "select" && $dictType)
                        <el-form-item label="${comment}" prop="${field}">
                          <el-select v-model="form.${field}" placeholder="请选择${comment}">
                            <el-option label="请选择字典生成" value=""/>
                          </el-select>
                        </el-form-item>
                      #elseif($column.htmlType == "checkbox" && "" != $dictType)
                        <el-form-item label="${comment}">
                          <el-checkbox-group v-model="form.${field}">
                            <el-checkbox
                                v-for="dict in dict.type.${dictType}"
                                :key="dict.value"
                                :label="dict.value">
                              {{dict.label}}
                            </el-checkbox>
                          </el-checkbox-group>
                        </el-form-item>
                      #elseif($column.htmlType == "checkbox" && $dictType)
                        <el-form-item label="${comment}">
                          <el-checkbox-group v-model="form.${field}">
                            <el-checkbox>请选择字典生成</el-checkbox>
                          </el-checkbox-group>
                        </el-form-item>
                      #elseif($column.htmlType == "radio" && "" != $dictType)
                        <el-form-item label="${comment}">
                          <el-radio-group v-model="form.${field}">
                            <el-radio
                                v-for="dict in dict.type.${dictType}"
                                :key="dict.value"
                                #if($column.javaType == "Integer" || $column.javaType ==
                                    "Long"):label="parseInt(dict.value)"#else:label="dict.value"#end

                            >{{dict.label}}
                            </el-radio>
                          </el-radio-group>
                        </el-form-item>
                      #elseif($column.htmlType == "radio" && $dictType)
                        <el-form-item label="${comment}">
                          <el-radio-group v-model="form.${field}">
                            <el-radio label="1">请选择字典生成</el-radio>
                          </el-radio-group>
                        </el-form-item>
                      #elseif($column.htmlType == "datetime")
                        <el-form-item label="${comment}" prop="${field}">
                          <el-date-picker clearable
                                          v-model="form.${field}"
                                          type="date"
                                          value-format="yyyy-MM-dd"
                                          placeholder="选择${comment}">
                          </el-date-picker>
                        </el-form-item>
                      #elseif($column.htmlType == "textarea")
                        <el-form-item label="${comment}" prop="${field}">
                          <el-input v-model="form.${field}" type="textarea" placeholder="请输入内容"/>
                        </el-form-item>
                      #end
                  #end
              #end
          #end
      </el-form>
      <div slot="footer" class="dialog-footer">
        <el-button type="primary" @click="submitForm">确 定</el-button>
        <el-button @click="cancel">取 消</el-button>
      </div>
    </el-dialog>
  </div>
</template>

<script>
  import {get${BusinessName}, list${BusinessName}} from "@/api/";
  import Treeselect from "@riophae/vue-treeselect";
  import "@riophae/vue-treeselect/dist/vue-treeselect.css";

  export default {
    name: "${BusinessName}",
      #if(${dicts} != '')
        dicts: [${dicts}],
      #end
    components: {
      Treeselect
    },
    data() {
      return {
        // 遮罩层
        loading: true,
        // 显示搜索条件
        showSearch: true,
        // ${functionName}表格数据
              ${businessName}List: [],
        // ${functionName}树选项
              ${businessName}Options: [],
        // 弹出层标题
        title: "",
        // 是否显示弹出层
        open: false,
        // 是否展开，默认全部展开
        isExpandAll: true,
        // 重新渲染表格状态
        refreshTable: true,
          #foreach ($column in $columns)
              #if($column.htmlType == "datetime" && $column.queryType == "BETWEEN")
                  #set($AttrName=$column.javaField.substring(0,1).toUpperCase() + ${column.javaField.substring(1)})
                // $comment时间范围
                daterange${AttrName}: [],
              #end
          #end
        // 查询参数
        queryParams: {
            #foreach ($column in $columns)
                #if($column.query)
                        $column.javaField: null#if($foreach.count != $columns.size()),#end
                #end
            #end
        },
        // 表单参数
        form: {},
        // 表单校验
        rules: {
            #foreach ($column in $columns)
                #if($column.required)
                    #set($parentheseIndex=$column.columnComment.indexOf("（"))
                    #if($parentheseIndex != -1)
                        #set($comment=$column.columnComment.substring(0, $parentheseIndex))
                    #else
                        #set($comment=$column.columnComment)
                    #end
                        $column.javaField: [
                    {
                      required: true,
                      message: "$comment不能为空", trigger: #if($column.htmlType == "select")"change"
                        #else"blur"#end }
                  ]#if($foreach.count != $columns.size()),#end
                #end
            #end
        }
      };
    },
    created() {
      this.getList();
    },
    methods: {
      /** 查询${functionName}列表 */
      getList() {
        this.loading = true;
          #foreach ($column in $columns)
              #if($column.htmlType == "datetime" && $column.queryType == "BETWEEN")
                this.queryParams.params = {};
                  #break
              #end
          #end
          #foreach ($column in $columns)
              #if($column.htmlType == "datetime" && $column.queryType == "BETWEEN")
                  #set($AttrName=$column.javaField.substring(0,1).toUpperCase() + ${column.javaField.substring(1)})
                if (null != this.daterange${AttrName} && '' != this.daterange${AttrName}) {
                  this.queryParams.params["begin${AttrName}"] = this.daterange${AttrName}[0];
                  this.queryParams.params["end${AttrName}"] = this.daterange${AttrName}[1];
                }
              #end
          #end
        list${BusinessName}(this.queryParams).then(response => {
          this.${businessName}List = this.handleTree(response.data, "${treeCode}", "${treeParentCode}");
          this.loading = false;
        });
      },
      /** 转换${functionName}数据结构 */
      normalizer(node) {
        if (node.children && !node.children.length) {
          delete node.children;
        }
        return {
          id: node.${treeCode},
          label: node.${treeName},
          children: node.children
        };
      },
      /** 查询${functionName}下拉树结构 */
      getTreeselect() {
        list${BusinessName}().then(response => {
          this.${businessName}Options = [];
          const data = {${treeCode}: 0, ${treeName}: '顶级节点', children: []};
          data.children = this.handleTree(response.data, "${treeCode}", "${treeParentCode}");
          this.${businessName}Options.push(data);
        });
      },
      // 取消按钮
      cancel() {
        this.open = false;
        this.reset();
      },
      // 表单重置
      reset() {
        this.form = {
            #foreach ($column in $columns)
                #if($column.htmlType == "radio")
                    $column.javaField: #if($column.javaType == "Integer" || $column.javaType == "Long")0
                    #else"0"#end#if($foreach.count != $columns.size()),#end

                #elseif($column.htmlType == "checkbox")
                        $column.javaField: []#if($foreach.count != $columns.size()),#end
                #else
                        $column.javaField: null#if($foreach.count != $columns.size()),#end
                #end
            #end
        };
        this.resetForm("form");
      },
      /** 搜索按钮操作 */
      handleQuery() {
        this.getList();
      },
      /** 重置按钮操作 */
      resetQuery() {
          #foreach ($column in $columns)
              #if($column.htmlType == "datetime" && $column.queryType == "BETWEEN")
                  #set($AttrName=$column.javaField.substring(0,1).toUpperCase() + ${column.javaField.substring(1)})
                this.daterange${AttrName} = [];
              #end
          #end
        this.resetForm("queryForm");
        this.handleQuery();
      },
      /** 新增按钮操作 */
      handleAdd(row) {
        this.reset();
        this.getTreeselect();
        if (row != null && row.${treeCode}) {
          this.form.${treeParentCode} = row.${treeCode};
        } else {
          this.form.${treeParentCode} = 0;
        }
        this.open = true;
        this.title = "添加${functionName}";
      },
      /** 展开/折叠操作 */
      toggleExpandAll() {
        this.refreshTable = false;
        this.isExpandAll = !this.isExpandAll;
        this.$nextTick(() => {
          this.refreshTable = true;
        });
      },
      /** 修改按钮操作 */
      handleUpdate(row) {
        this.reset();
        this.getTreeselect();
        if (row != null) {
          this.form.${treeParentCode} = row.${treeCode};
        }
        get${BusinessName}(row.${pkColumn.javaField}).then(response => {
          this.form = response.data;
            #foreach ($column in $columns)
                #if($column.htmlType == "checkbox")
                  this.form.$column.javaField = this.form.${column.javaField}.split(",");
                #end
            #end
          this.open = true;
          this.title = "修改${functionName}";
        });
      },
      /** 提交按钮 */
      submitForm() {
        this.
      #
        [[$]]
        #refs["form"].validate(valid => {
          if (valid) {
              #foreach($column in $columns)
                  #if($column.htmlType == "checkbox")
                    this.form.$column.javaField = this.form.$
                    {
                      column.javaField
                    }
                  .
                    join(",");
                  #end
              #end
            if (this.form.${
              pkColumn.javaField
            }
          !=
            null
          )
            {
              update$
              {
                BusinessName
              }
              (this.form).then(response => {
                this.
              #
                [[$modal]]
              #.
                msgSuccess("修改成功");
                this.open = false;
                this.getList();
              });
            }
          else
            {
              add$
              {
                BusinessName
              }
              (this.form).then(response => {
                this.
              #
                [[$modal]]
              #.
                msgSuccess("新增成功");
                this.open = false;
                this.getList();
              });
            }
          }
        });
      },
      /** 删除按钮操作 */
      handleDelete(row) {
        this.
      #
        [[$modal]]
      #.
        confirm('是否确认删除${functionName}编号为"' + row.$
        {
          pkColumn.javaField
        }
        +'"的数据项？'
      ).
        then(function () {
          return del$
          {
            BusinessName
          }
          (row.$
          {
            pkColumn.javaField
          }
        )
          ;
        }).then(() => {
          this.getList();
          this.
        #
          [[$modal]]
        #.
          msgSuccess("删除成功");
        }).catch(() => {
        });
      }
    }
  };
</script>


--- File: play-generator/src/main/resources/vm/vue/index.vue.vm ---
<template>
  <div class="app-container">
    <!--搜索区-->
    <el-card class="filter-container" shadow="never">
      <div>
        <i class="el-icon-search"></i>
        <span>筛选搜索</span>
        <div style="margin-top: 15px">
          <el-form :model="queryParams" ref="queryForm" size="small" :inline="true" v-show="showSearch"
                   label-width="68px">
              #foreach($column in $columns)
                  #if($column.query)
                      #set($dictType=$column.dictType)
                      #set($AttrName=$column.javaField.substring(0,1).toUpperCase() + ${column.javaField.substring(1)})
                      #set($parentheseIndex=$column.columnComment.indexOf("（"))
                      #if($parentheseIndex != -1)
                          #set($comment=$column.columnComment.substring(0, $parentheseIndex))
                      #else
                          #set($comment=$column.columnComment)
                      #end
                      #if($column.htmlType == "input")
                        <el-form-item label="${comment}" prop="${column.javaField}">
                          <el-input
                              v-model="queryParams.${column.javaField}"
                              placeholder="请输入${comment}"
                              clearable
                              @keyup.enter.native="handleQuery"
                          />
                        </el-form-item>
                      #elseif(($column.htmlType == "select" || $column.htmlType == "radio") && "" != $dictType)
                        <el-form-item label="${comment}" prop="${column.javaField}">
                          <el-select v-model="queryParams.${column.javaField}" placeholder="请选择${comment}" clearable>
                            <el-option
                                v-for="dict in dict.type.${dictType}"
                                :key="dict.value"
                                :label="dict.label"
                                :value="dict.value"
                            />
                          </el-select>
                        </el-form-item>
                      #elseif(($column.htmlType == "select" || $column.htmlType == "radio") && $dictType)
                        <el-form-item label="${comment}" prop="${column.javaField}">
                          <el-select v-model="queryParams.${column.javaField}" placeholder="请选择${comment}" clearable>
                            <el-option label="请选择字典生成" value=""/>
                          </el-select>
                        </el-form-item>
                      #elseif($column.htmlType == "datetime" && $column.queryType != "BETWEEN")
                        <el-form-item label="${comment}" prop="${column.javaField}">
                          <el-date-picker clearable
                                          v-model="queryParams.${column.javaField}"
                                          type="date"
                                          value-format="yyyy-MM-dd"
                                          placeholder="请选择${comment}">
                          </el-date-picker>
                        </el-form-item>
                      #elseif($column.htmlType == "datetime" && $column.queryType == "BETWEEN")
                        <el-form-item label="${comment}">
                          <el-date-picker
                              v-model="daterange${AttrName}"
                              style="width: 240px"
                              value-format="yyyy-MM-dd"
                              type="daterange"
                              range-separator="-"
                              start-placeholder="开始日期"
                              end-placeholder="结束日期"
                          ></el-date-picker>
                        </el-form-item>
                      #end
                  #end
              #end
            <el-form-item>
              <el-button type="primary" icon="el-icon-search" @click="handleQuery()" size="small">搜索</el-button>
              <el-button icon="el-icon-refresh" @click="resetQuery()" size="small">重置</el-button>
            </el-form-item>
          </el-form>
        </div>
      </div>
    </el-card>

    <!--操作按钮区-->
    <el-card class="operate-container" shadow="never">
      <i class="el-icon-tickets"></i>
      <span>数据列表</span>
      <el-button style="margin-left: 20px" type="primary" plain icon="el-icon-circle-plus" size="mini"
                 @click="handleAdd" v-hasPermi="['${moduleName}:${businessName}:create']">新增
      </el-button>
      <el-button type="success" plain icon="el-icon-edit-outline" size="mini" :disabled="single" @click="handleUpdate"
                 v-hasPermi="['${moduleName}:${businessName}:edit']">修改
      </el-button>
      <el-button type="danger" plain icon="el-icon-delete" size="mini" :disabled="multiple" @click="handleDelete"
                 v-hasPermi="['${moduleName}:${businessName}:remove']">删除
      </el-button>
      <el-button type="warning" plain icon="el-icon-download" size="mini" @click="handleExport"
                 v-hasPermi="['${moduleName}:${businessName}:export']">导出
      </el-button>
    </el-card>

    <!--数据列表区-->
    <div class="table-container">
      <el-table v-loading="loading" :data="${businessName}List" @selection-change="handleSelectionChange" border>
        <el-table-column type="selection" width="55" align="center"/>
          #foreach($column in $columns)
              #set($javaField=$column.javaField)
              #set($parentheseIndex=$column.columnComment.indexOf("（"))
              #if($parentheseIndex != -1)
                  #set($comment=$column.columnComment.substring(0, $parentheseIndex))
              #else
                  #set($comment=$column.columnComment)
              #end
              #if($column.pk)
                <el-table-column label="${comment}" align="center" prop="${javaField}"/>
              #elseif($column.list && $column.htmlType == "datetime")
                <el-table-column label="${comment}" align="center" prop="${javaField}" width="180">
                  <template slot-scope="scope">
                    <span>{{ parseTime(scope.row.${javaField}, '{y}-{m}-{d}') }}</span>
                  </template>
                </el-table-column>
              #elseif($column.list && $column.htmlType == "imageUpload")
                <el-table-column label="${comment}" align="center" prop="${javaField}" width="100">
                  <template slot-scope="scope">
                    <image-preview :src="scope.row.${javaField}" :width="50" :height="50"/>
                  </template>
                </el-table-column>
              #elseif($column.list && "" != $column.dictType)
                <el-table-column label="${comment}" align="center" prop="${javaField}">
                  <template slot-scope="scope">
                      #if($column.htmlType == "checkbox")
                        <dict-tag :options="dict.type.${column.dictType}"
                                  :value="scope.row.${javaField} ? scope.row.${javaField}.split(',') : []"/>
                      #else
                        <dict-tag :options="dict.type.${column.dictType}" :value="scope.row.${javaField}"/>
                      #end
                  </template>
                </el-table-column>
              #elseif($column.list && "" != $javaField)
                <el-table-column label="${comment}" align="center" prop="${javaField}"/>
              #end
          #end
        <el-table-column label="操作" align="center" class-name="small-padding fixed-width">
          <template slot-scope="scope">
            <el-button
                size="mini"
                type="text"
                icon="el-icon-edit"
                @click="handleUpdate(scope.row)"
                v-hasPermi="['${moduleName}:${businessName}:edit']"
            >修改
            </el-button>
            <el-button
                size="mini"
                type="text"
                icon="el-icon-delete"
                @click="handleDelete(scope.row)"
                v-hasPermi="['${moduleName}:${businessName}:remove']"
            >删除
            </el-button>
          </template>
        </el-table-column>
      </el-table>
    </div>

    <!--分页区-->
    <div class="pagination-container">
      <el-pagination
          layout="total, sizes,prev, pager, next,jumper"
          :current-page="queryParams.pageNum"
          :page-size="queryParams.pageSize"
          :page-sizes="[10,15,20]"
          :total="total"
          @size-change="handleSizeChange"
          @current-change="handleCurrentChange"
      />
    </div>

    <!-- 添加或修改${functionName}对话框 -->
    <el-dialog :title="title" :visible.sync="open" width="500px" append-to-body>
      <el-form ref="form" :model="form" :rules="rules" label-width="80px">
          #foreach($column in $columns)
              #set($field=$column.javaField)
              #if($column.insert && !$column.pk)
                  #if(($column.usableColumn) || (!$column.superColumn))
                      #set($parentheseIndex=$column.columnComment.indexOf("（"))
                      #if($parentheseIndex != -1)
                          #set($comment=$column.columnComment.substring(0, $parentheseIndex))
                      #else
                          #set($comment=$column.columnComment)
                      #end
                      #set($dictType=$column.dictType)
                      #if($column.htmlType == "input")
                        <el-form-item label="${comment}" prop="${field}">
                          <el-input v-model="form.${field}" placeholder="请输入${comment}"/>
                        </el-form-item>
                      #elseif($column.htmlType == "imageUpload")
                        <el-form-item label="${comment}">
                          <image-upload v-model="form.${field}"/>
                        </el-form-item>
                      #elseif($column.htmlType == "fileUpload")
                        <el-form-item label="${comment}">
                          <file-upload v-model="form.${field}"/>
                        </el-form-item>
                      #elseif($column.htmlType == "editor")
                        <el-form-item label="${comment}">
                          <editor v-model="form.${field}" :min-height="192"/>
                        </el-form-item>
                      #elseif($column.htmlType == "select" && "" != $dictType)
                        <el-form-item label="${comment}" prop="${field}">
                          <el-select v-model="form.${field}" placeholder="请选择${comment}">
                            <el-option
                                v-for="dict in dict.type.${dictType}"
                                :key="dict.value"
                                :label="dict.label"
                                #if($column.javaType == "Integer" || $column.javaType ==
                                    "Long"):value="parseInt(dict.value)"#else:value="dict.value"#end

                            ></el-option>
                          </el-select>
                        </el-form-item>
                      #elseif($column.htmlType == "select" && $dictType)
                        <el-form-item label="${comment}" prop="${field}">
                          <el-select v-model="form.${field}" placeholder="请选择${comment}">
                            <el-option label="请选择字典生成" value=""/>
                          </el-select>
                        </el-form-item>
                      #elseif($column.htmlType == "checkbox" && "" != $dictType)
                        <el-form-item label="${comment}">
                          <el-checkbox-group v-model="form.${field}">
                            <el-checkbox
                                v-for="dict in dict.type.${dictType}"
                                :key="dict.value"
                                :label="dict.value">
                              {{dict.label}}
                            </el-checkbox>
                          </el-checkbox-group>
                        </el-form-item>
                      #elseif($column.htmlType == "checkbox" && $dictType)
                        <el-form-item label="${comment}">
                          <el-checkbox-group v-model="form.${field}">
                            <el-checkbox>请选择字典生成</el-checkbox>
                          </el-checkbox-group>
                        </el-form-item>
                      #elseif($column.htmlType == "radio" && "" != $dictType)
                        <el-form-item label="${comment}">
                          <el-radio-group v-model="form.${field}">
                            <el-radio
                                v-for="dict in dict.type.${dictType}"
                                :key="dict.value"
                                #if($column.javaType == "Integer" || $column.javaType ==
                                    "Long"):label="parseInt(dict.value)"#else:label="dict.value"#end

                            >{{dict.label}}
                            </el-radio>
                          </el-radio-group>
                        </el-form-item>
                      #elseif($column.htmlType == "radio" && $dictType)
                        <el-form-item label="${comment}">
                          <el-radio-group v-model="form.${field}">
                            <el-radio label="1">请选择字典生成</el-radio>
                          </el-radio-group>
                        </el-form-item>
                      #elseif($column.htmlType == "datetime")
                        <el-form-item label="${comment}" prop="${field}">
                          <el-date-picker clearable
                                          v-model="form.${field}"
                                          type="date"
                                          value-format="yyyy-MM-dd"
                                          placeholder="请选择${comment}">
                          </el-date-picker>
                        </el-form-item>
                      #elseif($column.htmlType == "textarea")
                        <el-form-item label="${comment}" prop="${field}">
                          <el-input v-model="form.${field}" type="textarea" placeholder="请输入内容"/>
                        </el-form-item>
                      #end
                  #end
              #end
          #end
          #if($table.sub)
            <el-divider content-position="center">${subTable.functionName}信息</el-divider>
            <el-row :gutter="10" class="mb8">
              <el-col :span="1.5">
                <el-button type="primary" icon="el-icon-plus" size="mini" @click="handleAdd${subClassName}">添加
                </el-button>
              </el-col>
              <el-col :span="1.5">
                <el-button type="danger" icon="el-icon-delete" size="mini" @click="handleDelete${subClassName}">删除
                </el-button>
              </el-col>
            </el-row>
            <el-table :data="${subclassName}List" :row-class-name="row${subClassName}Index"
                      @selection-change="handle${subClassName}SelectionChange" ref="${subclassName}">
              <el-table-column type="selection" width="50" align="center"/>
              <el-table-column label="序号" align="center" prop="index" width="50"/>
                #foreach($column in $subTable.columns)
                    #set($javaField=$column.javaField)
                    #set($parentheseIndex=$column.columnComment.indexOf("（"))
                    #if($parentheseIndex != -1)
                        #set($comment=$column.columnComment.substring(0, $parentheseIndex))
                    #else
                        #set($comment=$column.columnComment)
                    #end
                    #if($column.pk || $javaField == ${subTableFkclassName})
                    #elseif($column.list && $column.htmlType == "input")
                      <el-table-column label="$comment" prop="${javaField}" width="150">
                        <template slot-scope="scope">
                          <el-input v-model="scope.row.$javaField" placeholder="请输入$comment"/>
                        </template>
                      </el-table-column>
                    #elseif($column.list && $column.htmlType == "datetime")
                      <el-table-column label="$comment" prop="${javaField}" width="240">
                        <template slot-scope="scope">
                          <el-date-picker clearable v-model="scope.row.$javaField" type="date" value-format="yyyy-MM-dd"
                                          placeholder="请选择$comment"/>
                        </template>
                      </el-table-column>
                    #elseif($column.list && ($column.htmlType == "select" || $column.htmlType == "radio") &&
                        "" != $column.dictType)
                      <el-table-column label="$comment" prop="${javaField}" width="150">
                        <template slot-scope="scope">
                          <el-select v-model="scope.row.$javaField" placeholder="请选择$comment">
                            <el-option
                                v-for="dict in dict.type.$column.dictType"
                                :key="dict.value"
                                :label="dict.label"
                                :value="dict.value"
                            ></el-option>
                          </el-select>
                        </template>
                      </el-table-column>
                    #elseif($column.list && ($column.htmlType == "select" || $column.htmlType == "radio") &&
                        "" == $column.dictType)
                      <el-table-column label="$comment" prop="${javaField}" width="150">
                        <template slot-scope="scope">
                          <el-select v-model="scope.row.$javaField" placeholder="请选择$comment">
                            <el-option label="请选择字典生成" value=""/>
                          </el-select>
                        </template>
                      </el-table-column>
                    #end
                #end
            </el-table>
          #end
      </el-form>
      <div slot="footer" class="dialog-footer">
        <el-button type="primary" @click="submitForm">确 定</el-button>
        <el-button @click="cancel">取 消</el-button>
      </div>
    </el-dialog>
  </div>
</template>

<script>
  import {list${BusinessName}} from "@/api/";

  export default {
    name: "${BusinessName}",
      #if(${dicts} != '')
        dicts: [${dicts}],
      #end
    data() {
      return {
        // 遮罩层
        loading: true,
        // 选中数组
        ids: [],
          #if($table.sub)
            // 子表选中数据
            checked${subClassName}: [],
          #end
        // 非单个禁用
        single: true,
        // 非多个禁用
        multiple: true,
        // 显示搜索条件
        showSearch: true,
        // 总条数
        total: 0,
        // ${functionName}表格数据
              ${businessName}List: [],
          #if($table.sub)
            // ${subTable.functionName}表格数据
                  ${subclassName}List: [],
          #end
        // 弹出层标题
        title: "",
        // 是否显示弹出层
        open: false,
          #foreach ($column in $columns)
              #if($column.htmlType == "datetime" && $column.queryType == "BETWEEN")
                  #set($AttrName=$column.javaField.substring(0,1).toUpperCase() + ${column.javaField.substring(1)})
                // $comment时间范围
                daterange${AttrName}: [],
              #end
          #end
        // 查询参数
        queryParams: {
          pageNum: 1,
          pageSize: 10,
            #foreach ($column in $columns)
                #if($column.query)
                        $column.javaField: null#if($foreach.count != $columns.size()),#end
                #end
            #end
        },
        // 表单参数
        form: {},
        // 表单校验
        rules: {
            #foreach ($column in $columns)
                #if($column.required)
                    #set($parentheseIndex=$column.columnComment.indexOf("（"))
                    #if($parentheseIndex != -1)
                        #set($comment=$column.columnComment.substring(0, $parentheseIndex))
                    #else
                        #set($comment=$column.columnComment)
                    #end
                        $column.javaField: [
                    {
                      required: true,
                      message: "$comment不能为空", trigger: #if($column.htmlType == "select")"change"
                        #else"blur"#end }
                  ]#if($foreach.count != $columns.size()),#end
                #end
            #end
        }
      };
    },
    created() {
      this.getList();
    },
  methods: {
    /** 查询${functionName}列表 */
    getList() {
      this.loading = true;
        #foreach ($column in $columns)
            #if($column.htmlType == "datetime" && $column.queryType == "BETWEEN")
              this.queryParams.params = {};
                #break
            #end
        #end
        #foreach ($column in $columns)
            #if($column.htmlType == "datetime" && $column.queryType == "BETWEEN")
                #set($AttrName=$column.javaField.substring(0,1).toUpperCase() + ${column.javaField.substring(1)})
              if (null != this.daterange${AttrName} && '' != this.daterange${AttrName}) {
                this.queryParams.params["begin${AttrName}"] = this.daterange${AttrName}[0];
                this.queryParams.params["end${AttrName}"] = this.daterange${AttrName}[1];
              }
            #end
        #end
      list${BusinessName}(this.queryParams).then(response => {
        this.${businessName}List = response.rows;
        this.total = response.total;
        this.loading = false;
      });
    },
    // 取消按钮
    cancel() {
      this.open = false;
      this.reset();
    },
    // 表单重置
    reset() {
      this.form = {
          #foreach ($column in $columns)
              #if($column.htmlType == "radio")
                  $column.javaField: #if($column.javaType == "Integer" || $column.javaType == "Long")0
                  #else"0"#end#if($foreach.count != $columns.size()),#end
              #elseif($column.htmlType == "checkbox")
                      $column.javaField: []#if($foreach.count != $columns.size()),#end
              #else
                      $column.javaField: null#if($foreach.count != $columns.size()),#end
              #end
          #end
      };
        #if($table.sub)
          this.${subclassName}List = [];
        #end
      this.resetForm("form");
    },
    /** 搜索按钮操作 */
    handleQuery() {
      this.queryParams.pageNum = 1;
      this.getList();
    },
    /** 重置按钮操作 */
    resetQuery() {
        #foreach ($column in $columns)
            #if($column.htmlType == "datetime" && $column.queryType == "BETWEEN")
                #set($AttrName=$column.javaField.substring(0,1).toUpperCase() + ${column.javaField.substring(1)})
              this.daterange${AttrName} = [];
            #end
        #end
      this.resetForm("queryForm");
      this.handleQuery();
    },
    // 多选框选中数据
    handleSelectionChange(selection) {
      this.ids = selection.map(item => item.${pkColumn.javaField})
      this.single = selection.length !== 1
      this.multiple = !selection.length
    },
    /** 新增按钮操作 */
    handleAdd() {
      this.reset();
      this.open = true;
      this.title = "添加${functionName}";
    },
    /** 修改按钮操作 */
    handleUpdate(row) {
      this.reset();
      const ${pkColumn.javaField} = row.${pkColumn.javaField} || this.ids
      get${BusinessName}(${pkColumn.javaField}).then(response => {
        this.form = response.data;
          #foreach ($column in $columns)
              #if($column.htmlType == "checkbox")
                this.form.$column.javaField = this.form.${column.javaField}.split(",");
              #end
          #end
          #if($table.sub)
            this.${subclassName}List = response.data.${subclassName}List;
          #end
        this.open = true;
        this.title = "修改${functionName}";
      });
    },
    /** 提交按钮 */
    submitForm() {
      this.
    #
      [[$]]
      #refs["form"].validate(valid => {
        if (valid) {
            #foreach($column in $columns)
                #if($column.htmlType == "checkbox")
                  this.form.$column.javaField = this.form.$
                  {
                    column.javaField
                  }
                .
                  join(",");
                #end
            #end
            #if($table.sub)
              this.form.$
              {
                subclassName
              }
              List = this.$
              {
                subclassName
              }
              List;
            #end
          if (this.form.${
            pkColumn.javaField
          }
        !=
          null
        )
          {
            update$
            {
              BusinessName
            }
            (this.form.$
            {
              pkColumn.javaField
            }
          ,
            this.form
          ).
            then(response => {
              this.
            #
              [[$modal]]
            #.
              msgSuccess("修改成功");
              this.open = false;
              this.getList();
            });
          }
        else
          {
            add$
            {
              BusinessName
            }
            (this.form).then(response => {
              this.
            #
              [[$modal]]
            #.
              msgSuccess("新增成功");
              this.open = false;
              this.getList();
            });
          }
        }
      });
    },
    /** 删除按钮操作 */
    handleDelete(row) {
      const ${pkColumn.javaField}s = row.$
      {
        pkColumn.javaField
      }
    ||
      this.ids;
      this.
    #
      [[$modal]]
    #.
      confirm('是否确认删除${functionName}编号为"' + ${pkColumn.javaField}s + '"的数据项？').then(function () {
        return del$
        {
          BusinessName
        }
        (${pkColumn.javaField}s);
      }).then(() => {
        this.getList();
        this.
      #
        [[$modal]]
      #.
        msgSuccess("删除成功");
      }).catch(() => {
      });
    },
      #if($table.sub
      )
        /** ${subTable.functionName}序号 */
        row$
      {
        subClassName
      }
      Index({row, rowIndex})
      {
        row.index = rowIndex + 1;
      }
      ,
      /** ${subTable.functionName}添加按钮操作 */
      handleAdd$
      {
        subClassName
      }
      ()
      {
        let obj = {};
          #foreach($column in $subTable.columns)
              #if($column.pk || $column.javaField == ${subTableFkclassName})
              #elseif($column.list && "" != $javaField)
                obj.$column.javaField = "";
              #end
          #end
        this.$
        {
          subclassName
        }
        List.push(obj);
      }
      ,
      /** ${subTable.functionName}删除按钮操作 */
      handleDelete$
      {
        subClassName
      }
      ()
      {
        if (this.checked${
          subClassName
        }
      .
        length == 0
      )
        {
          this.
        #
          [[$modal]]
        #.
          msgError("请先选择要删除的${subTable.functionName}数据");
        }
      else
        {
          const ${subclassName}List = this.$
          {
            subclassName
          }
          List;
          const checked$
          {
            subClassName
          }
          = this.checked$
          {
            subClassName
          }
          ;
          this.$
          {
            subclassName
          }
          List = ${subclassName}List.filter(function (item) {
            return checked$
            {
              subClassName
            }
          .
            indexOf(item.index) == -1
          });
        }
      }
      ,
      /** 复选框选中数据 */
      handle$
      {
        subClassName
      }
      SelectionChange(selection)
      {
        this.checked$
        {
          subClassName
        }
        = selection.map(item => item.index)
      }
      ,
      #end
  /** 导出按钮操作 */
  handleExport()
  {
    this.download('${moduleName}/${businessName}/export', {
      ...this.queryParams
    }, `${businessName}_#[[${new Date().getTime()}]]#.xlsx`)
  }
  ,
  handleSizeChange(val)
  {
    this.queryParams.pageNum = 1;
    this.queryParams.pageSize = val;
    this.getList();
  }
  ,
  handleCurrentChange(val)
  {
    this.queryParams.pageNum = val;
    this.getList();
  }
  ,
  }
  }
  ;
</script>


--- File: play-generator/src/main/resources/vm/xml/mapper.xml.vm ---
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="${packageName}.mapper.${ClassName}Mapper">

    <resultMap type="${packageName}.entity.${ClassName}" id="${ClassName}Result">
        #foreach ($column in $columns)
            <result property="${column.javaField}" column="${column.columnName}"/>
        #end
    </resultMap>
    #if($table.sub)

        <resultMap id="${ClassName}${subClassName}Result" type="${ClassName}" extends="${ClassName}Result">
            <collection property="${subclassName}List" notNullColumn="sub_${subTable.pkColumn.columnName}"
                        javaType="java.util.List" resultMap="${subClassName}Result"/>
        </resultMap>

        <resultMap type="${subClassName}" id="${subClassName}Result">
            #foreach ($column in $subTable.columns)
                <result property="${column.javaField}" column="sub_${column.columnName}"/>
            #end
        </resultMap>
    #end

    <sql id="select${ClassName}Vo">
        select#foreach($column in $columns) $column.columnName#if($foreach.count != $columns.size()),#end#end
        from ${tableName}
    </sql>

    <select id="select${ClassName}List" resultMap="${ClassName}Result">
        <include refid="select${ClassName}Vo"/>
        <where>
            deleted = 0
            #foreach($column in $columns)
                #set($queryType=$column.queryType)
                #set($javaField=$column.javaField)
                #set($javaType=$column.javaType)
                #set($columnName=$column.columnName)
                #set($AttrName=$column.javaField.substring(0,1).toUpperCase() + ${column.javaField.substring(1)})
                #if($column.query)
                    #if($column.queryType == "EQ")
                        <if test="$javaField != null #if($javaType == 'String' ) and $javaField.trim() != ''#end">
                            and $columnName = #{$javaField}
                        </if>
                    #elseif($queryType == "NE")
                        <if test="$javaField != null #if($javaType == 'String' ) and $javaField.trim() != ''#end">
                            and $columnName != #{$javaField}
                        </if>
                    #elseif($queryType == "GT")
                        <if test="$javaField != null #if($javaType == 'String' ) and $javaField.trim() != ''#end">
                            and $columnName &gt; #{$javaField}
                        </if>
                    #elseif($queryType == "GTE")
                        <if test="$javaField != null #if($javaType == 'String' ) and $javaField.trim() != ''#end">
                            and $columnName &gt;= #{$javaField}
                        </if>
                    #elseif($queryType == "LT")
                        <if test="$javaField != null #if($javaType == 'String' ) and $javaField.trim() != ''#end">
                            and $columnName &lt; #{$javaField}
                        </if>
                    #elseif($queryType == "LTE")
                        <if test="$javaField != null #if($javaType == 'String' ) and $javaField.trim() != ''#end">
                            and $columnName &lt;= #{$javaField}
                        </if>
                    #elseif($queryType == "LIKE")
                        <if test="$javaField != null #if($javaType == 'String' ) and $javaField.trim() != ''#end">
                            and $columnName like concat('%', #{$javaField}, '%')
                        </if>
                    #elseif($queryType == "BETWEEN")
                        <if test="params.begin$AttrName != null and params.begin$AttrName != '' and params.end$AttrName != null and params.end$AttrName != ''">
                            and $columnName between #{params.begin$AttrName} and #{params.end$AttrName}
                        </if>
                    #end
                #end
            #end
        </where>
    </select>

    <select id="select${ClassName}By${pkColumn.capJavaField}" parameterType="${pkColumn.javaType}"
            resultMap="#if($table.sub)${ClassName}${subClassName}Result#else${ClassName}Result#end">
        #if($table.crud || $table.tree)
            <include refid="select${ClassName}Vo"/>
            where ${pkColumn.columnName} = #{${pkColumn.javaField}}
        #elseif($table.sub)
            select#foreach($column in $columns) a.$column.columnName#if($foreach.count != $columns.size()),#end#end,
            #foreach($column in $subTable.columns) b.$column.columnName as
                sub_$column.columnName#if($foreach.count != $subTable.columns.size()),#end#end

            from ${tableName} a
            left join ${subTableName} b on b.${subTableFkName} = a.${pkColumn.columnName}
            where a.${pkColumn.columnName} = #{${pkColumn.javaField}}
        #end
    </select>


    #if($table.sub)

        <delete id="delete${subClassName}By${subTableFkClassName}s" parameterType="String">
            delete from ${subTableName} where ${subTableFkName} in
            <foreach item="${subTableFkclassName}" collection="array" open="(" separator="," close=")">
                #{${subTableFkclassName}}
            </foreach>
        </delete>

        <delete id="delete${subClassName}By${subTableFkClassName}" parameterType="${pkColumn.javaType}">
            delete
            from ${subTableName} where ${subTableFkName} = #{${subTableFkclassName}}
        </delete>

        <insert id="batch${subClassName}">
            insert into ${subTableName}
            (#foreach($column in $subTable.columns) $column.columnName#if($foreach.count != $subTable.columns.size())
            ,#end#end) values
            <foreach item="item" index="index" collection="list" separator=",">
                (#foreach($column in $subTable.columns) #{item.$column.javaField
                }#if($foreach.count != $subTable.columns.size()),#end#end)
            </foreach>
        </insert>
    #end
</mapper>

--- File: pom.xml ---
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xmlns="http://maven.apache.org/POM/4.0.0"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.5.4</version>
        <relativePath/>
    </parent>

    <groupId>com.starry</groupId>
    <artifactId>play-with</artifactId>
    <version>1.0</version>
    <packaging>pom</packaging>

    <modules>
        <module>play-admin</module>
        <module>play-common</module>
        <module>play-generator</module>
    </modules>

    <!--定义相关属性，使用${属性名}的方式引用该属性-->
    <properties>
        <java.version>11</java.version>
        <!--mybatis-plus-->
        <mybatis-plus.version>3.5.3.2</mybatis-plus.version>
        <mybatis-plus-join.version>1.4.10</mybatis-plus-join.version>
        <!-- swagger -->
        <swagger.version>2.0.7</swagger.version>
        <!--JWT(Json Web Token)登录支持-->
        <jwt.version>0.9.0</jwt.version>
        <!-- transmittable -->
        <transmittable-thread-local.version>2.14.5</transmittable-thread-local.version>
        <!-- hutool 工具库-->
        <hutool.version>5.8.26</hutool.version>
        <!-- druid连接池 -->
        <druid-version>1.2.21</druid-version>
        <!-- io常用工具类 -->
        <commom-io.version>2.15.1</commom-io.version>
        <!-- 阿里JSON解析器 -->
        <fastjson2-version>2.0.47</fastjson2-version>
        <!-- velocity 模板引擎 -->
        <velocity-version>2.1</velocity-version>
        <!-- 获取系统信息 -->
        <oshi-version>6.5.0</oshi-version>
        <!-- easyexcel -->
        <easyexcel.version>2.2.11</easyexcel.version>
        <!--        weichat-->
        <weixin-java.version>4.6.0</weixin-java.version>
        <ws-schild.version>3.1.1</ws-schild.version>
        <!-- Lombok -->
        <lombok.version>1.18.30</lombok.version>
        <!-- Flyway -->
        <flyway.version>7.15.0</flyway.version>

        <!--腾讯云 COS 对象存储-->
        <cos-version>5.6.205</cos-version>
        <!--阿里云 COS 对象存储-->
        <aliyun-oss.version>3.17.4</aliyun-oss.version>
        <!--腾讯云 COS 临时密钥-->
        <cos_sts-version>3.1.1</cos_sts-version>
        <!--   gson     -->
        <gson-version>2.10.1</gson-version>
        <redisson.version>3.24.3</redisson.version>
        <lock4j.version>2.2.5</lock4j.version>
    </properties>

    <!-- 依赖声明 -->
    <dependencyManagement>
        <dependencies>
            <!--mybatis-plus-->
            <dependency>
                <groupId>com.baomidou</groupId>
                <artifactId>mybatis-plus-boot-starter</artifactId>
                <version>${mybatis-plus.version}</version>
            </dependency>
            <dependency>
                <groupId>com.github.yulichang</groupId>
                <artifactId>mybatis-plus-join-boot-starter</artifactId>
                <version>${mybatis-plus-join.version}</version>
            </dependency>

            <!-- hutool 工具类库 -->
            <dependency>
                <groupId>cn.hutool</groupId>
                <artifactId>hutool-all</artifactId>
                <version>${hutool.version}</version>
            </dependency>
            <!-- druid连接池 -->
            <dependency>
                <groupId>com.alibaba</groupId>
                <artifactId>druid-spring-boot-starter</artifactId>
                <version>${druid-version}</version>
            </dependency>
            <!--swagger-->
            <dependency>
                <groupId>com.github.xiaoymin</groupId>
                <artifactId>knife4j-spring-boot-starter</artifactId>
                <version>${swagger.version}</version>
            </dependency>
            <!--JWT(Json Web Token)登录支持-->
            <dependency>
                <groupId>io.jsonwebtoken</groupId>
                <artifactId>jjwt</artifactId>
                <version>${jwt.version}</version>
            </dependency>
            <!-- 线程传递值 -->
            <dependency>
                <groupId>com.alibaba</groupId>
                <artifactId>transmittable-thread-local</artifactId>
                <version>${transmittable-thread-local.version}</version>
            </dependency>
            <!-- io常用工具类 -->
            <dependency>
                <groupId>commons-io</groupId>
                <artifactId>commons-io</artifactId>
                <version>${commom-io.version}</version>
            </dependency>
            <!-- 阿里JSON解析器 -->
            <dependency>
                <groupId>com.alibaba.fastjson2</groupId>
                <artifactId>fastjson2</artifactId>
                <version>${fastjson2-version}</version>
            </dependency>
            <!-- velocity 模板引擎, Mybatis Plus 代码生成器需要 -->
            <dependency>
                <groupId>org.apache.velocity</groupId>
                <artifactId>velocity-engine-core</artifactId>
                <version>${velocity-version}</version>
            </dependency>
            <!-- 获取系统信息 -->
            <dependency>
                <groupId>com.github.oshi</groupId>
                <artifactId>oshi-core</artifactId>
                <version>${oshi-version}</version>
            </dependency>
            <!-- easyexcel -->
            <dependency>
                <groupId>com.alibaba</groupId>
                <artifactId>easyexcel</artifactId>
                <version>${easyexcel.version}</version>
            </dependency>

            <dependency>
                <groupId>com.google.code.gson</groupId>
                <artifactId>gson</artifactId>
                <version>${gson-version}</version>
            </dependency>

            <dependency>
                <groupId>com.github.gavlyukovskiy</groupId>
                <artifactId>p6spy-spring-boot-starter</artifactId>
                <version>1.9.1</version>
            </dependency>


            <!--通用模块-->
            <dependency>
                <groupId>com.starry</groupId>
                <artifactId>play-common</artifactId>
                <version>1.0</version>
            </dependency>
            <!--代码生成模块-->
            <dependency>
                <groupId>com.starry</groupId>
                <artifactId>play-generator</artifactId>
                <version>1.0</version>
            </dependency>
            <!--服务模块-->
            <dependency>
                <groupId>com.starry</groupId>
                <artifactId>play-admin</artifactId>
                <version>1.0</version>
            </dependency>
            <dependency>
                <groupId>com.aliyun.oss</groupId>
                <artifactId>aliyun-sdk-oss</artifactId>
                <version>${aliyun-oss.version}</version>
            </dependency>

            <!--weixin-java-common-->

            <dependency>
                <groupId>com.github.binarywang</groupId>
                <artifactId>wx-java-mp-spring-boot-starter</artifactId>
                <version>${weixin-java.version}</version>
            </dependency>

            <dependency>
                <groupId>com.github.wxpay</groupId>
                <artifactId>wxpay-sdk</artifactId>
                <version>0.0.3</version>
            </dependency>


            <dependency>
                <groupId>org.redisson</groupId>
                <artifactId>redisson-spring-boot-starter</artifactId>
                <version>${redisson.version}</version>
            </dependency>

            <dependency>
                <groupId>com.baomidou</groupId>
                <artifactId>lock4j-redisson-spring-boot-starter</artifactId>
                <version>${lock4j.version}</version>
            </dependency>

            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <version>2.7.9</version>
            </dependency>

            <dependency>
                <groupId>ws.schild</groupId>
                <artifactId>jave-core</artifactId>
                <version>${ws-schild.version}</version>
            </dependency>

            <dependency>
                <groupId>ws.schild</groupId>
                <artifactId>jave-nativebin-linux64</artifactId>
                <version>${ws-schild.version}</version>
            </dependency>
            <dependency>
                <groupId>com.squareup.okio</groupId>
                <artifactId>okio</artifactId>
                <version>3.4.0</version>
            </dependency>

            <!-- https://mvnrepository.com/artifact/org.lionsoul/ip2region -->
            <dependency>
                <groupId>org.lionsoul</groupId>
                <artifactId>ip2region</artifactId>
                <version>1.7.2</version>
            </dependency>

            <!-- Flyway -->
            <dependency>
                <groupId>org.flywaydb</groupId>
                <artifactId>flyway-core</artifactId>
                <version>${flyway.version}</version>
            </dependency>

        </dependencies>
    </dependencyManagement>

    <build>
        <plugins>
            <!-- Spotless Maven Plugin for Code Formatting -->
            <plugin>
                <groupId>com.diffplug.spotless</groupId>
                <artifactId>spotless-maven-plugin</artifactId>
                <version>2.43.0</version>
                <configuration>
                    <java>
                        <!-- Basic formatting only - compatible with Java 11 -->
                        <trimTrailingWhitespace/>
                        <endWithNewline/>
                        <indent>
                            <spaces>true</spaces>
                            <spacesPerTab>4</spacesPerTab>
                        </indent>
                        <!-- Basic import organization without removeUnusedImports -->
                        <importOrder/>
                        <!-- Target all Java files -->
                        <includes>
                            <include>src/main/java/**/*.java</include>
                            <include>src/test/java/**/*.java</include>
                        </includes>
                    </java>
                </configuration>
                <executions>
                    <execution>
                        <goals>
                            <goal>check</goal>
                        </goals>
                        <phase>validate</phase>
                    </execution>
                </executions>
            </plugin>

            <!-- Checkstyle Maven Plugin for Code Style - Disabled to avoid conflicts with Spotless -->
            <!--
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-checkstyle-plugin</artifactId>
                <version>3.3.1</version>
                <configuration>
                    <configLocation>sun_checks.xml</configLocation>
                    <encoding>UTF-8</encoding>
                    <consoleOutput>true</consoleOutput>
                    <failsOnError>true</failsOnError>
                    <linkXRef>false</linkXRef>
                </configuration>
                <executions>
                    <execution>
                        <id>validate</id>
                        <phase>validate</phase>
                        <goals>
                            <goal>check</goal>
                        </goals>
                    </execution>
                </executions>
                <dependencies>
                    <dependency>
                        <groupId>com.puppycrawl.tools</groupId>
                        <artifactId>checkstyle</artifactId>
                        <version>10.12.4</version>
                    </dependency>
                </dependencies>
            </plugin>
            -->
        </plugins>
    </build>
</project>

--- File: todo.md ---



[] DB migration
[] Domain migration

