Compare commits
97 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| f42b4a1036 | |||
| 7d3d71e0e3 | |||
| 6a44d27fd3 | |||
| a90a081973 | |||
| 4736273902 | |||
| fba01f4909 | |||
| 42aa934493 | |||
| fe73fdd5bd | |||
| c112f02fcc | |||
| 4d2467ae36 | |||
| 0b6e6914fd | |||
| 0f76b61152 | |||
| 6cfff15c62 | |||
| 6d3887ec06 | |||
| 5af0b07775 | |||
| 8ae10dd2c3 | |||
| 17c1ce2cc6 | |||
| a209dc8080 | |||
| 1d4fff13a1 | |||
| a811e36635 | |||
| d8a0dd5d31 | |||
| 0ff979917c | |||
| fc5615a9c7 | |||
| cdcd9eeffa | |||
| c0da89735c | |||
| fc34d83692 | |||
| 8ceb252d79 | |||
| 8da4563435 | |||
| 045e6ab3df | |||
| 5591e17446 | |||
| 8f783fd765 | |||
| 6b41e46f30 | |||
| ff89fdf5e9 | |||
| 402c425575 | |||
| ee2785f972 | |||
| bbb0271a5e | |||
| 75ff4bb0a4 | |||
| 776f0ed029 | |||
| eef56291eb | |||
| 8e159edf1d | |||
| 1793e4b2aa | |||
| a793ed541b | |||
| 09ed1bd427 | |||
| ff666975da | |||
| b4403cedd9 | |||
| 98d2eb8a2a | |||
| ae5f56c16f | |||
| d374034694 | |||
| b0f399c814 | |||
| 8489ef35cb | |||
| 7d8c2d4e37 | |||
| ec60eee8fe | |||
| dac15250a2 | |||
| fec0198537 | |||
| f5ac4d10c0 | |||
| ca07a6cea5 | |||
| b5d89aef72 | |||
| d151d45e99 | |||
| 3525af81bf | |||
| 01c86ce0a3 | |||
| e5e619a241 | |||
| d9b10c1621 | |||
| c6e72e5b79 | |||
| 980effc420 | |||
| e4040a27e7 | |||
| 23170dcc3f | |||
| 64c92857a6 | |||
| 4346bfebb7 | |||
| c24271c075 | |||
| 526982c431 | |||
| c161bc55e5 | |||
| eb79ad260c | |||
| ce8e59902c | |||
| b8ed400d12 | |||
| 5f48980d31 | |||
| b81e7b8b1b | |||
| a468c0919d | |||
| 1ae5b46523 | |||
| ecac45a74a | |||
| 51c4cb7767 | |||
| ae7cfebb44 | |||
| e4ccbbcbd1 | |||
| bdfcd1cedb | |||
| 6a7b465944 | |||
| 41ac96630c | |||
| 3a8fbc3e1b | |||
| e3e57ee154 | |||
| 0883c1318b | |||
| fe79d04343 | |||
| 2857283558 | |||
| 981779126c | |||
| 39ce5882cb | |||
| e51f6c6544 | |||
| 3a7f510e19 | |||
| 1c9e72e28d | |||
| 9d79b2585e | |||
| 1fc9a39ffe |
27
.env.development
Normal file
27
.env.development
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
# 项目配置, 请根据实际情况修改
|
||||||
|
PROJECT_NAME=newshop
|
||||||
|
|
||||||
|
# ThinkPHP 6.x 配置, 请根据实际情况修改
|
||||||
|
APP_ENV=development
|
||||||
|
|
||||||
|
# PHP/PHP-FPM 配置
|
||||||
|
PHP_VERSION=7.4
|
||||||
|
PHP_FPM_VERSION=7.4-fpm
|
||||||
|
PHP_FPM_PORT=9105
|
||||||
|
XDEBUG_POST=9108
|
||||||
|
|
||||||
|
# 数据库配置
|
||||||
|
MYSQL_ROOT_HOST=%
|
||||||
|
MYSQL_DATABASE=shop_mallnew
|
||||||
|
MYSQL_USER=shop_mallnew
|
||||||
|
MYSQL_PASSWORD=shop_mallnew
|
||||||
|
MYSQL_PORT=3326
|
||||||
|
|
||||||
|
# Redis 绑定端口及密码
|
||||||
|
REDIS_PASSWORD=luckyshop123!@#
|
||||||
|
REDIS_PORT=6499
|
||||||
|
|
||||||
|
# Nginx 暴漏端口
|
||||||
|
NGINX_PORT=8050
|
||||||
|
NGINX_SSL_PORT=8052
|
||||||
|
|
||||||
16
.env.example
16
.env.example
@@ -1,24 +1,26 @@
|
|||||||
# 项目配置, 请根据实际情况修改
|
# 项目配置, 请根据实际情况修改
|
||||||
PROJECT_NAME=newshop
|
PROJECT_NAME=newshop
|
||||||
|
|
||||||
|
# ThinkPHP 6.x 配置, 请根据实际情况修改
|
||||||
|
APP_ENV=development
|
||||||
|
|
||||||
# PHP/PHP-FPM 配置
|
# PHP/PHP-FPM 配置
|
||||||
PHP_VERSION=7.4
|
PHP_VERSION=7.4
|
||||||
PHP_FPM_VERSION=7.4-fpm
|
PHP_FPM_VERSION=7.4-fpm
|
||||||
PHP_FPM_PORT=9000
|
PHP_FPM_PORT=9100
|
||||||
XDEBUG_POST=9003
|
XDEBUG_POST=9103
|
||||||
|
|
||||||
# 数据库配置
|
# 数据库配置
|
||||||
MYSQL_ROOT_HOST=%
|
MYSQL_ROOT_HOST=%
|
||||||
MYSQL_DATABASE=shop_mallnew
|
MYSQL_DATABASE=shop_mallnew
|
||||||
MYSQL_USER=shop_mallnew
|
MYSQL_USER=shop_mallnew
|
||||||
MYSQL_PASSWORD=shop_mallnew
|
MYSQL_PASSWORD=shop_mallnew
|
||||||
MYSQL_PORT=3306
|
MYSQL_PORT=3316
|
||||||
|
|
||||||
# Redis 配置
|
# Redis 配置
|
||||||
REDIS_PASSWORD=luckyshop123!@#
|
REDIS_PASSWORD=luckyshop123!@#
|
||||||
REDIS_PORT=6379
|
REDIS_PORT=6399
|
||||||
|
|
||||||
# Nginx 配置
|
# Nginx 配置
|
||||||
NGINX_PORT=80
|
NGINX_PORT=8010
|
||||||
NGINX_SSL_PORT=443
|
NGINX_SSL_PORT=8012
|
||||||
|
|
||||||
|
|||||||
27
.env.local
Normal file
27
.env.local
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
# 项目配置, 请根据实际情况修改
|
||||||
|
PROJECT_NAME=newshop
|
||||||
|
|
||||||
|
# ThinkPHP 6.x 配置, 请根据实际情况修改
|
||||||
|
APP_ENV=local
|
||||||
|
|
||||||
|
# PHP/PHP-FPM 配置
|
||||||
|
PHP_VERSION=7.4
|
||||||
|
PHP_FPM_VERSION=7.4-fpm
|
||||||
|
PHP_FPM_PORT=9100
|
||||||
|
XDEBUG_POST=9103
|
||||||
|
|
||||||
|
# 数据库配置
|
||||||
|
MYSQL_ROOT_HOST=%
|
||||||
|
MYSQL_DATABASE=shop_dev
|
||||||
|
MYSQL_USER=shop_mallnew
|
||||||
|
MYSQL_PASSWORD=shop_mallnew
|
||||||
|
MYSQL_PORT=3316
|
||||||
|
|
||||||
|
# Redis 配置
|
||||||
|
REDIS_PASSWORD=luckyshop123!@#
|
||||||
|
REDIS_PORT=6399
|
||||||
|
|
||||||
|
# Nginx 配置
|
||||||
|
NGINX_PORT=8010
|
||||||
|
NGINX_SSL_PORT=8012
|
||||||
|
|
||||||
3
.gitignore
vendored
3
.gitignore
vendored
@@ -18,6 +18,9 @@ __pycache__
|
|||||||
.idea
|
.idea
|
||||||
.vscode
|
.vscode
|
||||||
|
|
||||||
|
# 环境变量
|
||||||
|
.env
|
||||||
|
|
||||||
# 源码结构
|
# 源码结构
|
||||||
debug.txt
|
debug.txt
|
||||||
.travis.yml
|
.travis.yml
|
||||||
|
|||||||
43
README.md
Normal file
43
README.md
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
# 在线商城PHP项目
|
||||||
|
|
||||||
|
|
||||||
|
## Docker 部署
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cp .env.example .env.development
|
||||||
|
```
|
||||||
|
|
||||||
|
**注意**
|
||||||
|
|
||||||
|
- 在同一目录下面,执行 `docker-compose` 命令时,需要指定项目名称。用来区分不同的环境。如 `shop_local`、`shop_dev` 等。
|
||||||
|
- 本地部署时,需要将 `APP_ENV` 设置为 `local`。
|
||||||
|
- 开发环境部署时,需要将 `APP_ENV` 设置为 `development`。
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## 环境变量
|
||||||
|
|
||||||
|
- `APP_ENV`: 应用环境,默认值为 `development`。
|
||||||
|
|
||||||
|
## 开发环境-local 部署
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 本地部署时,需要将 APP_ENV 设置为 local, 并指定 docker-compose.local.yml 文件
|
||||||
|
docker-compose --env-file .env.local -f docker-compose.local.yml up -d
|
||||||
|
# docker-compose --project-name shop_local --env-file .env.local -f docker-compose.local.yml up -d
|
||||||
|
|
||||||
|
# docker-compose down 命令,用来停止并删除容器
|
||||||
|
docker-compose -f docker-compose.local.yml down -v
|
||||||
|
# docker-compose --project-name shop_local down -v
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
## 开发环境-development 部署
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 默认使用 docker-compose.yml 文件
|
||||||
|
docker-compose --project-name shop_development --env-file .env.development up -d
|
||||||
|
|
||||||
|
# docker-compose down 命令,用来停止并删除容器
|
||||||
|
docker-compose --project-name shop_development down -v
|
||||||
|
```
|
||||||
126
docker-compose.local.yml
Normal file
126
docker-compose.local.yml
Normal file
@@ -0,0 +1,126 @@
|
|||||||
|
# 特别说明,本地local环境,方便操作,所以未使用的统一的docker-compose.yml文件,只保留了local环境的配置
|
||||||
|
|
||||||
|
x-shared-env: &shared-api-env
|
||||||
|
MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD:-rootpassword}
|
||||||
|
MYSQL_ROOT_HOST: ${MYSQL_ROOT_HOST:-'%'} # 允许root从任何主机连接
|
||||||
|
MYSQL_DATABASE: ${MYSQL_DATABASE:-shop_mallnew}
|
||||||
|
MYSQL_USER: ${MYSQL_USER:-shop_mallnew}
|
||||||
|
MYSQL_PASSWORD: ${MYSQL_PASSWORD:-shop_mallnew}
|
||||||
|
|
||||||
|
REDIS_PASSWORD: ${REDIS_PASSWORD:-luckyshop123!@#}
|
||||||
|
REDIS_PORT: ${REDIS_PORT:-6379}
|
||||||
|
|
||||||
|
# 将服务归类到目录 A 中
|
||||||
|
services:
|
||||||
|
php-fpm:
|
||||||
|
build:
|
||||||
|
context: ./docker/php
|
||||||
|
dockerfile: Dockerfile
|
||||||
|
container_name: ${PROJECT_NAME}_php
|
||||||
|
restart: always
|
||||||
|
extra_hosts:
|
||||||
|
- "host.docker.internal:host-gateway" # 支持主机名解析
|
||||||
|
environment:
|
||||||
|
PHP_ENV: ${PHP_ENV:-development}
|
||||||
|
# 环境变量, APP_ENV 应用于 ThinkPHP 6.x 框架, .env.local 要想启用,需要在项目根目录下创建 .env.local 文件,并将 APP_ENV 设置为 local
|
||||||
|
# 同理,如果要启用开发环境,则将 APP_ENV 设置为 development,如果要启用生产环境,则将 APP_ENV 设置为 production
|
||||||
|
# 不然,ThinkPHP 6.x 系列,会只加载 .env 文件,而不会加载 .env.local 文件,导致 .env.local 文件中的配置不会生效
|
||||||
|
APP_ENV: ${APP_ENV:-local}
|
||||||
|
APP_DEBUG: ${APP_DEBUG:-true}
|
||||||
|
XDEBUG_CONFIG: ${XDEBUG_CONFIG:-client_host=host.docker.internal client_port=9003}
|
||||||
|
PHP_IDE_CONFIG: serverName=docker-php
|
||||||
|
ports:
|
||||||
|
- "${PHP_FPM_PORT:-9000}:9000" # PHP-FPM
|
||||||
|
- "${XDEBUG_POST:-9003}:9003" # Xdebug
|
||||||
|
volumes:
|
||||||
|
- ./:/var/www/all_source
|
||||||
|
- ./src:/var/www/html
|
||||||
|
# 更新下载源列表以加速apt-get
|
||||||
|
- ./docker/debian/sources.list:/etc/apt/sources.list:ro
|
||||||
|
- ./docker/php/php.ini:/usr/local/etc/php/php.ini:ro
|
||||||
|
- ./docker/php/xdebug.ini:/usr/local/etc/php/conf.d/xdebug.ini
|
||||||
|
- xdebug_logs:/tmp # Xdebug 日志目录
|
||||||
|
depends_on:
|
||||||
|
- db
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD", "bash", "-c", "curl -f http://localhost:9000/status && ps aux | grep '[p]hp think cron:schedule'"]
|
||||||
|
interval: 30s
|
||||||
|
timeout: 10s
|
||||||
|
retries: 3
|
||||||
|
start_period: 60s
|
||||||
|
networks:
|
||||||
|
- sass-platform-net
|
||||||
|
labels:
|
||||||
|
- "com.docker.compose.project.working_dir=${PROJECT_NAME}"
|
||||||
|
|
||||||
|
nginx:
|
||||||
|
build:
|
||||||
|
context: ./docker/nginx
|
||||||
|
dockerfile: Dockerfile
|
||||||
|
container_name: ${PROJECT_NAME}_nginx
|
||||||
|
restart: always
|
||||||
|
ports:
|
||||||
|
- "${NGINX_PORT:-80}:80"
|
||||||
|
- "${NGINX_SSL_PORT:-443}:443"
|
||||||
|
volumes:
|
||||||
|
# 挂载项目代码到 Nginx 容器中
|
||||||
|
- ./src:/var/www/html:rw
|
||||||
|
# 更新下载源列表以加速apt-get
|
||||||
|
- ./docker/debian/sources.list:/etc/apt/sources.list:ro
|
||||||
|
# 创建临时目录
|
||||||
|
- /var/www/server/nginx/proxy_temp_dir
|
||||||
|
- /var/www/server/nginx/proxy_cache_dir
|
||||||
|
depends_on:
|
||||||
|
- php-fpm
|
||||||
|
networks:
|
||||||
|
- sass-platform-net
|
||||||
|
labels:
|
||||||
|
- "com.docker.compose.project.working_dir=${PROJECT_NAME}"
|
||||||
|
|
||||||
|
db:
|
||||||
|
image: mysql:5.7.44
|
||||||
|
container_name: ${PROJECT_NAME}_mysql
|
||||||
|
environment:
|
||||||
|
<<: *shared-api-env
|
||||||
|
volumes:
|
||||||
|
- mysql_db_data:/var/lib/mysql
|
||||||
|
- ./docker/mysql/init:/docker-entrypoint-initdb.d
|
||||||
|
- ./docker/mysql/my.cnf:/etc/mysql/conf.d/custom.cnf
|
||||||
|
ports:
|
||||||
|
- ${MYSQL_PORT:-3306}:3306
|
||||||
|
networks:
|
||||||
|
- sass-platform-net
|
||||||
|
restart: unless-stopped
|
||||||
|
command:
|
||||||
|
- --character-set-server=utf8mb4
|
||||||
|
- --collation-server=utf8mb4_unicode_ci
|
||||||
|
- --innodb_buffer_pool_size=256M
|
||||||
|
labels:
|
||||||
|
- "com.docker.compose.project.working_dir=${PROJECT_NAME}"
|
||||||
|
|
||||||
|
# Redis 服务(可选)
|
||||||
|
redis:
|
||||||
|
image: redis:8.2
|
||||||
|
container_name: ${PROJECT_NAME}_redis
|
||||||
|
environment:
|
||||||
|
REDIS_PASSWORD: ${REDIS_PASSWORD:-luckyshop123!@#}
|
||||||
|
REDISCLI_AUTH: ${REDIS_PASSWORD:-luckyshop123!@#}
|
||||||
|
ports:
|
||||||
|
- "${REDIS_PORT:-6379}:6379"
|
||||||
|
volumes:
|
||||||
|
- redis_data:/data
|
||||||
|
- ./docker/redis/redis.conf:/etc/redis/redis.conf
|
||||||
|
networks:
|
||||||
|
- sass-platform-net
|
||||||
|
restart: unless-stopped
|
||||||
|
labels:
|
||||||
|
- "com.docker.compose.project.working_dir=${PROJECT_NAME}"
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
mysql_db_data:
|
||||||
|
redis_data:
|
||||||
|
xdebug_logs:
|
||||||
|
|
||||||
|
networks:
|
||||||
|
sass-platform-net:
|
||||||
|
driver: bridge
|
||||||
@@ -14,7 +14,7 @@ services:
|
|||||||
build:
|
build:
|
||||||
context: ./docker/php
|
context: ./docker/php
|
||||||
dockerfile: Dockerfile
|
dockerfile: Dockerfile
|
||||||
container_name: ${PROJECT_NAME}_php
|
container_name: ${PROJECT_NAME}_${APP_ENV}_php
|
||||||
restart: always
|
restart: always
|
||||||
extra_hosts:
|
extra_hosts:
|
||||||
- "host.docker.internal:host-gateway" # 支持主机名解析
|
- "host.docker.internal:host-gateway" # 支持主机名解析
|
||||||
@@ -49,18 +49,19 @@ services:
|
|||||||
networks:
|
networks:
|
||||||
- sass-platform-net
|
- sass-platform-net
|
||||||
labels:
|
labels:
|
||||||
- "com.docker.compose.project.working_dir=${PROJECT_NAME}"
|
- "com.docker.compose.project.working_dir=${PROJECT_NAME}_${APP_ENV}"
|
||||||
|
|
||||||
nginx:
|
nginx:
|
||||||
build:
|
build:
|
||||||
context: ./docker/nginx
|
context: ./docker/nginx
|
||||||
dockerfile: Dockerfile
|
dockerfile: Dockerfile
|
||||||
container_name: ${PROJECT_NAME}_nginx
|
container_name: ${PROJECT_NAME}_${APP_ENV}_nginx
|
||||||
restart: always
|
restart: always
|
||||||
ports:
|
ports:
|
||||||
- "${NGINX_PORT:-80}:80"
|
- "${NGINX_PORT:-80}:80"
|
||||||
- "${NGINX_SSL_PORT:-443}:443"
|
- "${NGINX_SSL_PORT:-443}:443"
|
||||||
volumes:
|
volumes:
|
||||||
|
# 挂载项目代码到 Nginx 容器中
|
||||||
- ./src:/var/www/html:rw
|
- ./src:/var/www/html:rw
|
||||||
# 更新下载源列表以加速apt-get
|
# 更新下载源列表以加速apt-get
|
||||||
- ./docker/debian/sources.list:/etc/apt/sources.list:ro
|
- ./docker/debian/sources.list:/etc/apt/sources.list:ro
|
||||||
@@ -72,11 +73,11 @@ services:
|
|||||||
networks:
|
networks:
|
||||||
- sass-platform-net
|
- sass-platform-net
|
||||||
labels:
|
labels:
|
||||||
- "com.docker.compose.project.working_dir=${PROJECT_NAME}"
|
- "com.docker.compose.project.working_dir=${PROJECT_NAME}_${APP_ENV}"
|
||||||
|
|
||||||
db:
|
db:
|
||||||
image: mysql:5.7.44
|
image: mysql:5.7.44
|
||||||
container_name: ${PROJECT_NAME}_mysql
|
container_name: ${PROJECT_NAME}_${APP_ENV}_mysql
|
||||||
environment:
|
environment:
|
||||||
<<: *shared-api-env
|
<<: *shared-api-env
|
||||||
volumes:
|
volumes:
|
||||||
@@ -93,12 +94,12 @@ services:
|
|||||||
- --collation-server=utf8mb4_unicode_ci
|
- --collation-server=utf8mb4_unicode_ci
|
||||||
- --innodb_buffer_pool_size=256M
|
- --innodb_buffer_pool_size=256M
|
||||||
labels:
|
labels:
|
||||||
- "com.docker.compose.project.working_dir=${PROJECT_NAME}"
|
- "com.docker.compose.project.working_dir=${PROJECT_NAME}_${APP_ENV}"
|
||||||
|
|
||||||
# Redis 服务(可选)
|
# Redis 服务(可选)
|
||||||
redis:
|
redis:
|
||||||
image: redis:8.2
|
image: redis:8.2
|
||||||
container_name: ${PROJECT_NAME}_redis
|
container_name: ${PROJECT_NAME}_${APP_ENV}_redis
|
||||||
environment:
|
environment:
|
||||||
REDIS_PASSWORD: ${REDIS_PASSWORD:-luckyshop123!@#}
|
REDIS_PASSWORD: ${REDIS_PASSWORD:-luckyshop123!@#}
|
||||||
REDISCLI_AUTH: ${REDIS_PASSWORD:-luckyshop123!@#}
|
REDISCLI_AUTH: ${REDIS_PASSWORD:-luckyshop123!@#}
|
||||||
@@ -111,13 +112,32 @@ services:
|
|||||||
- sass-platform-net
|
- sass-platform-net
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
labels:
|
labels:
|
||||||
- "com.docker.compose.project.working_dir=${PROJECT_NAME}"
|
- "com.docker.compose.project.working_dir=${PROJECT_NAME}_${APP_ENV}"
|
||||||
|
|
||||||
volumes:
|
volumes:
|
||||||
mysql_db_data:
|
mysql_db_data:
|
||||||
|
name: ${PROJECT_NAME}_${APP_ENV}_mysql_db_data
|
||||||
|
driver: local
|
||||||
|
driver_opts:
|
||||||
|
type: none
|
||||||
|
o: bind
|
||||||
|
device: ./docker/mysql_db_data/${APP_ENV}
|
||||||
redis_data:
|
redis_data:
|
||||||
|
name: ${PROJECT_NAME}_${APP_ENV}_redis_data
|
||||||
|
driver: local
|
||||||
|
driver_opts:
|
||||||
|
type: none
|
||||||
|
o: bind
|
||||||
|
device: ./docker/redis_data/${APP_ENV}
|
||||||
xdebug_logs:
|
xdebug_logs:
|
||||||
|
name: ${PROJECT_NAME}_${APP_ENV}_xdebug_logs
|
||||||
|
driver: local
|
||||||
|
driver_opts:
|
||||||
|
type: none
|
||||||
|
o: bind
|
||||||
|
device: ./docker/xdebug_logs/${APP_ENV}
|
||||||
|
|
||||||
networks:
|
networks:
|
||||||
sass-platform-net:
|
sass-platform-net:
|
||||||
|
name: ${PROJECT_NAME}_${APP_ENV}_net
|
||||||
driver: bridge
|
driver: bridge
|
||||||
File diff suppressed because one or more lines are too long
10
docker/mysql_db_data/.gitignore
vendored
Normal file
10
docker/mysql_db_data/.gitignore
vendored
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
# 忽略目录下所有文件和子目录
|
||||||
|
*
|
||||||
|
# 忽略所有子目录
|
||||||
|
*/
|
||||||
|
# 但不忽略 .gitkeep 文件
|
||||||
|
!.gitkeep
|
||||||
|
# 不忽略 .gitignore 文件自身
|
||||||
|
!.gitignore
|
||||||
|
# 不忽略 development/.gitkeep 文件
|
||||||
|
!development/.gitkeep
|
||||||
0
docker/mysql_db_data/.gitkeep
Normal file
0
docker/mysql_db_data/.gitkeep
Normal file
@@ -27,6 +27,7 @@ RUN apt-get update && apt-get install -y \
|
|||||||
libfreetype6-dev \
|
libfreetype6-dev \
|
||||||
libjpeg62-turbo-dev \
|
libjpeg62-turbo-dev \
|
||||||
libpng-dev \
|
libpng-dev \
|
||||||
|
iputils-ping \
|
||||||
&& rm -rf /var/lib/apt/lists/*
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
# 安装 PHP 扩展
|
# 安装 PHP 扩展
|
||||||
@@ -66,21 +67,9 @@ RUN echo "zend_extension=xdebug.so" > /usr/local/etc/php/conf.d/xdebug.ini
|
|||||||
# RUN composer config -g repo.packagist composer https://mirrors.aliyun.com/composer/
|
# RUN composer config -g repo.packagist composer https://mirrors.aliyun.com/composer/
|
||||||
# RUN composer install --no-dev --optimize-autoloader --working-dir=/var/www/html
|
# RUN composer install --no-dev --optimize-autoloader --working-dir=/var/www/html
|
||||||
|
|
||||||
# # 创建非 root 用户
|
|
||||||
# RUN useradd -m -u 1000 phpuser && chown -R phpuser:phpuser /var/www/html
|
|
||||||
|
|
||||||
# 设置权限, 防止以下目录无法写入的问题
|
|
||||||
RUN chmod -R a+rw /var/www/html/runtime
|
|
||||||
RUN chmod -R a+rw /var/www/html/uploads
|
|
||||||
RUN chmod -R a+rw /var/www/html/tmp
|
|
||||||
RUN chmod -R a+rw /var/www/html/temp
|
|
||||||
|
|
||||||
# USER phpuser
|
|
||||||
|
|
||||||
# 暴露端口
|
# 暴露端口
|
||||||
EXPOSE 9000 9003
|
EXPOSE 9000 9003
|
||||||
|
|
||||||
|
|
||||||
############ 查看 cron 进程
|
############ 查看 cron 进程
|
||||||
## 查看 cron 进程
|
## 查看 cron 进程
|
||||||
# ps aux | grep "think cron:schedule"
|
# ps aux | grep "think cron:schedule"
|
||||||
@@ -95,4 +84,10 @@ EXPOSE 9000 9003
|
|||||||
#######################################
|
#######################################
|
||||||
|
|
||||||
# 启动Supervisor
|
# 启动Supervisor
|
||||||
|
# 添加在Dockerfile末尾,CMD命令之前
|
||||||
|
COPY ./entrypoint.sh /usr/local/bin/
|
||||||
|
RUN chmod +x /usr/local/bin/entrypoint.sh
|
||||||
|
ENTRYPOINT ["/usr/local/bin/entrypoint.sh"]
|
||||||
|
|
||||||
|
# 修改CMD命令
|
||||||
CMD ["/usr/bin/supervisord", "-c", "/etc/supervisor/conf.d/supervisord.conf"]
|
CMD ["/usr/bin/supervisord", "-c", "/etc/supervisor/conf.d/supervisord.conf"]
|
||||||
59
docker/php/entrypoint.sh
Normal file
59
docker/php/entrypoint.sh
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
set -e
|
||||||
|
|
||||||
|
# 设置全局umask
|
||||||
|
umask 0002
|
||||||
|
|
||||||
|
echo "=== ThinkPHP Docker权限初始化 ==="
|
||||||
|
|
||||||
|
# 修复目录所有权和权限
|
||||||
|
fix_directory_permissions() {
|
||||||
|
local dir=$1
|
||||||
|
echo "修复目录权限: $dir"
|
||||||
|
|
||||||
|
# 确保目录存在
|
||||||
|
mkdir -p "$dir"
|
||||||
|
|
||||||
|
# 设置所有权
|
||||||
|
chown -R www-data:www-data "$dir"
|
||||||
|
|
||||||
|
# 设置权限
|
||||||
|
chmod -R 775 "$dir"
|
||||||
|
|
||||||
|
# 设置setgid权限
|
||||||
|
chmod g+s "$dir"
|
||||||
|
|
||||||
|
# 尝试设置ACL(如果支持)
|
||||||
|
if command -v setfacl >/dev/null 2>&1; then
|
||||||
|
setfacl -d -m u:www-data:rwx -m u:root:rwx "$dir" 2>/dev/null || true
|
||||||
|
setfacl -Rm u:www-data:rwx "$dir" 2>/dev/null || true
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "✅ $dir 权限设置完成"
|
||||||
|
}
|
||||||
|
|
||||||
|
# 处理所有需要权限的目录
|
||||||
|
directories=("runtime" "upload")
|
||||||
|
for dir in "${directories[@]}"; do
|
||||||
|
fix_directory_permissions "/var/www/html/$dir"
|
||||||
|
done
|
||||||
|
|
||||||
|
# 验证权限
|
||||||
|
echo "=== 权限验证 ==="
|
||||||
|
echo "当前用户: $(whoami)"
|
||||||
|
echo "当前UID: $(id -u), GID: $(id -g)"
|
||||||
|
echo "当前umask: $(umask)"
|
||||||
|
|
||||||
|
# 测试写入权限
|
||||||
|
sudo -u www-data mkdir -p /var/www/html/runtime/test_dir 2>/dev/null && \
|
||||||
|
echo "✅ runtime目录新建子目录测试通过" || \
|
||||||
|
echo "❌ runtime目录新建子目录失败"
|
||||||
|
|
||||||
|
sudo -u www-data mkdir -p /var/www/html/upload/test_dir 2>/dev/null && \
|
||||||
|
echo "✅ upload目录新建子目录测试通过" || \
|
||||||
|
echo "❌ upload目录新建子目录失败"
|
||||||
|
|
||||||
|
echo "=== 启动应用 ==="
|
||||||
|
|
||||||
|
# 执行原有的启动命令
|
||||||
|
exec "$@"
|
||||||
@@ -6,25 +6,40 @@ logfile_backups=10
|
|||||||
loglevel=info
|
loglevel=info
|
||||||
pidfile=/var/run/supervisord.pid
|
pidfile=/var/run/supervisord.pid
|
||||||
|
|
||||||
|
[program:chmod]
|
||||||
|
command=/bin/bash -c "while true; do chmod -R 775 /var/www/html/runtime/ /var/www/html/upload/ 2>/dev/null || true; sleep 30; done"
|
||||||
|
autostart=true
|
||||||
|
autorestart=true
|
||||||
|
stopasgroup=true
|
||||||
|
killasgroup=true
|
||||||
|
|
||||||
[program:php-fpm]
|
[program:php-fpm]
|
||||||
command=php-fpm
|
command=php-fpm
|
||||||
autostart=true
|
autostart=true
|
||||||
autorestart=true
|
autorestart=true
|
||||||
startretries=3
|
startretries=3
|
||||||
startsecs=1
|
startsecs=1
|
||||||
stdout_logfile=/dev/stdout
|
stopasgroup=true
|
||||||
stdout_logfile_maxbytes=0
|
killasgroup=true
|
||||||
stderr_logfile=/dev/stderr
|
stdout_logfile=/var/log/supervisor/php-fpm.log
|
||||||
stderr_logfile_maxbytes=0
|
stdout_logfile_maxbytes=10MB
|
||||||
|
stdout_logfile_backups=10
|
||||||
|
stderr_logfile=/var/log/supervisor/php-fpm-error.log
|
||||||
|
stderr_logfile_maxbytes=10MB
|
||||||
|
stderr_logfile_backups=10
|
||||||
|
|
||||||
[program:think-cron]
|
[program:think-cron]
|
||||||
command=php /var/www/html/think cron:schedule
|
command=php /var/www/html/think cron:schedule
|
||||||
environment=APP_ENV=local
|
process_name=%(program_name)s_%(process_num)02d
|
||||||
|
numprocs=1
|
||||||
autostart=true
|
autostart=true
|
||||||
autorestart=true
|
autorestart=true
|
||||||
startretries=5
|
startretries=3
|
||||||
startsecs=2
|
stdout_logfile=/var/log/supervisor/think-cron.log
|
||||||
stdout_logfile=/dev/stdout
|
stdout_logfile_maxbytes=10MB
|
||||||
stdout_logfile_maxbytes=0
|
stdout_logfile_backups=10
|
||||||
stderr_logfile=/dev/stderr
|
stderr_logfile=/var/log/supervisor/think-cron-error.log
|
||||||
stderr_logfile_maxbytes=0
|
stderr_logfile_maxbytes=10MB
|
||||||
|
stderr_logfile_backups=10
|
||||||
|
startsecs=3
|
||||||
|
stopwaitsecs=10
|
||||||
10
docker/redis_data/.gitignore
vendored
Normal file
10
docker/redis_data/.gitignore
vendored
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
# 忽略目录下所有文件和子目录
|
||||||
|
*
|
||||||
|
# 忽略所有子目录
|
||||||
|
*/
|
||||||
|
# 但不忽略 .gitkeep 文件
|
||||||
|
!.gitkeep
|
||||||
|
# 不忽略 .gitignore 文件自身
|
||||||
|
!.gitignore
|
||||||
|
# 不忽略 development/.gitkeep 文件
|
||||||
|
!development/.gitkeep
|
||||||
0
docker/redis_data/.gitkeep
Normal file
0
docker/redis_data/.gitkeep
Normal file
10
docker/xdebug_logs/.gitignore
vendored
Normal file
10
docker/xdebug_logs/.gitignore
vendored
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
# 忽略目录下所有文件和子目录
|
||||||
|
*
|
||||||
|
# 忽略所有子目录
|
||||||
|
*/
|
||||||
|
# 但不忽略 .gitkeep 文件
|
||||||
|
!.gitkeep
|
||||||
|
# 不忽略 .gitignore 文件自身
|
||||||
|
!.gitignore
|
||||||
|
# 不忽略 development/.gitkeep 文件
|
||||||
|
!development/.gitkeep
|
||||||
0
docker/xdebug_logs/.gitkeep
Normal file
0
docker/xdebug_logs/.gitkeep
Normal file
128
docs/GIT_REALEASE.md
Normal file
128
docs/GIT_REALEASE.md
Normal file
@@ -0,0 +1,128 @@
|
|||||||
|
适用于中大型团队的方案主要有两种:
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## ✅ 推荐方案一:**GitFlow(适合版本发布节奏明确的项目)**
|
||||||
|
|
||||||
|
### 📌 核心分支说明
|
||||||
|
|
||||||
|
| 分支 | 作用 | 生命周期 | 是否长期存在 |
|
||||||
|
|------|------|--------|------------|
|
||||||
|
| `main`(或 `master`) | **生产环境代码**,每个 commit 对应一个可发布版本 | 永久 | ✅ |
|
||||||
|
| `develop` | **集成开发分支**,最新开发成果,用于测试环境部署 | 永久 | ✅ |
|
||||||
|
| `feature/*` | 功能开发分支(如 `feature/user-auth`) | 临时 | ❌ |
|
||||||
|
| `release/*` | 发布准备分支(如 `release/v1.2.0`) | 临时 | ❌ |
|
||||||
|
| `hotfix/*` | 紧急线上修复分支(如 `hotfix/login-bug`) | 临时 | ❌ |
|
||||||
|
|
||||||
|
### 🔁 典型流程
|
||||||
|
|
||||||
|
1. **日常开发**
|
||||||
|
- 从 `develop` 拉出 `feature/xxx`
|
||||||
|
- 开发完成后,**PR/MR 合并回 `develop`**
|
||||||
|
|
||||||
|
2. **准备发布**
|
||||||
|
- 从 `develop` 拉出 `release/vX.Y.Z`
|
||||||
|
- 在此分支修复 bug、更新版本号、生成 changelog
|
||||||
|
- 测试通过后:
|
||||||
|
- 合并到 `main`(打 tag,如 `v1.2.0`)
|
||||||
|
- 合并回 `develop`(同步修复)
|
||||||
|
|
||||||
|
3. **紧急修复**
|
||||||
|
- 从 `main`(或对应 tag)拉出 `hotfix/xxx`
|
||||||
|
- 修复后:
|
||||||
|
- 合并到 `main`(打新 patch tag,如 `v1.2.1`)
|
||||||
|
- 合并到 `develop`
|
||||||
|
|
||||||
|
### ✅ 优点
|
||||||
|
- 版本清晰,适合有明确发布周期的系统(如每月发版)
|
||||||
|
- 支持并行开发与紧急修复
|
||||||
|
- `main` 始终代表线上状态
|
||||||
|
|
||||||
|
### ⚠️ 缺点
|
||||||
|
- 分支较多,对小型团队略显复杂
|
||||||
|
- 不适合持续部署(CI/CD 频繁上线)场景
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## ✅ 推荐方案二:**Trunk-Based Development + Release Branches(适合 DevOps / 持续交付)**
|
||||||
|
|
||||||
|
> 越来越多互联网公司(如 Google、Facebook、Netflix)采用此模式,尤其适合**高频发布、自动化 CI/CD** 的后台服务。
|
||||||
|
|
||||||
|
### 📌 核心分支说明
|
||||||
|
|
||||||
|
| 分支 | 作用 |
|
||||||
|
|------|------|
|
||||||
|
| `main`(或 `trunk`) | **唯一主干分支**,所有开发直接或间接流向这里,保持可随时发布状态 |
|
||||||
|
| `release/*`(可选) | 仅在需要维护多个线上版本时使用(如 `release/v1.3`) |
|
||||||
|
| `feature/*`(短生命周期) | 功能分支,**必须短(<1天~2天)**,通过 PR 快速合并到 `main` |
|
||||||
|
|
||||||
|
> 💡 实践中常配合 **Feature Toggle(功能开关)**,即使未完成的功能也可合入 `main`,但默认关闭。
|
||||||
|
|
||||||
|
### 🔁 典型流程
|
||||||
|
|
||||||
|
1. 开发者从 `main` 拉出短命 `feature/xxx`
|
||||||
|
2. 提交 PR → 自动化测试(单元、集成、安全扫描)→ Code Review
|
||||||
|
3. 合并到 `main`
|
||||||
|
4. **CI/CD 自动部署到测试/预发环境**
|
||||||
|
5. 人工验证后,**一键发布到生产**(或自动金丝雀发布)
|
||||||
|
6. 若需回滚,直接回退 `main` 的 commit 或触发回滚流程
|
||||||
|
|
||||||
|
### ✅ 优点
|
||||||
|
- 极简分支模型,减少合并冲突
|
||||||
|
- 支持每天多次发布
|
||||||
|
- 与现代 CI/CD 工具链(Jenkins, GitLab CI, ArgoCD 等)天然契合
|
||||||
|
|
||||||
|
### ⚠️ 要求
|
||||||
|
- 强大的自动化测试覆盖(>80%)
|
||||||
|
- 快速 Code Review 文化
|
||||||
|
- 监控与快速回滚能力
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🏆 企业后台服务推荐选择
|
||||||
|
|
||||||
|
| 场景 | 推荐策略 |
|
||||||
|
|------|--------|
|
||||||
|
| **传统企业、月度/季度发版、强合规要求** | ✅ **GitFlow** |
|
||||||
|
| **互联网公司、SaaS 服务、每日/每小时发布** | ✅ **Trunk-Based + Short-lived Feature Branches** |
|
||||||
|
| **混合模式(主干开发 + 定期大版本)** | 主干开发为主,大版本前切 `release` 分支 |
|
||||||
|
|
||||||
|
> 🔔 **当前趋势**:越来越多企业后台服务(尤其是微服务架构)倾向于 **Trunk-Based**,因为:
|
||||||
|
> - 后台服务通常无“客户端版本”约束
|
||||||
|
> - 可独立部署、灰度发布
|
||||||
|
> - 自动化程度高
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🔐 补充建议(无论哪种策略)
|
||||||
|
|
||||||
|
1. **保护关键分支**
|
||||||
|
- 在 GitHub/GitLab 中设置:
|
||||||
|
- `main` / `develop` 为 **protected branch**
|
||||||
|
- 要求 **PR/MR + 至少1人审批 + CI 通过**
|
||||||
|
- (可选)要求 **GPG 签名提交**
|
||||||
|
|
||||||
|
2. **标准化 Commit & PR 模板**
|
||||||
|
- 使用 Conventional Commits(如 `feat:`, `fix:`, `chore:`)
|
||||||
|
- 自动生成 changelog 和版本号(配合 semantic-release)
|
||||||
|
|
||||||
|
3. **Tag 语义化版本**
|
||||||
|
- 所有生产发布必须打 tag:`v1.2.3`
|
||||||
|
- 格式遵循 [SemVer](https://semver.org/)
|
||||||
|
|
||||||
|
4. **禁止直接 push 到主干**
|
||||||
|
- 所有代码必须通过 PR/MR 合并
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📌 总结:最优实践(推荐)
|
||||||
|
|
||||||
|
> **对于大多数现代企业后台服务,采用:**
|
||||||
|
> **✅ Trunk-Based Development(主干开发) + 短生命周期 feature 分支 + 自动化 CI/CD**
|
||||||
|
> 是最高效、可扩展、符合 DevOps 理念的方式。
|
||||||
|
|
||||||
|
只有在**强版本管控、多客户定制、无法频繁上线**等特殊场景下,才考虑 GitFlow。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
如果你能提供更多信息(如团队规模、发布频率、是否微服务、CI/CD 成熟度),我可以给出更定制化的建议!
|
||||||
26
docs/addon/huaweipay/README.md
Normal file
26
docs/addon/huaweipay/README.md
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
# 华为支付插件
|
||||||
|
|
||||||
|
|
||||||
|
## Demo
|
||||||
|
|
||||||
|
```
|
||||||
|
安卓快应用ID:115644647
|
||||||
|
安卓快应用包名:com.jieganfsj.fivegshop
|
||||||
|
安卓快应用名称:秸秆粉碎机
|
||||||
|
|
||||||
|
|
||||||
|
商户名称:徐州明文机械有限公司
|
||||||
|
商户号:102751500028
|
||||||
|
|
||||||
|
开发者ID:10086000901972225
|
||||||
|
支付ID:10086000901972225
|
||||||
|
公钥:
|
||||||
|
MIIBojANBgkqhkiG9w0BAQEFAAOCAY8AMIIBigKCAYEA9g1+QcqvC4f1pUiwJ1um1iBUlNn6hRDJrNdv5zB77l5DNo6S6hE4w7VyhkMnkIk89i8kTej1m1ByjRpo7B5OPqafNqI9JBQyQ26A1Zp71zSfe/UicAFiMtF4lWNnAHBYH06sUTvybwYllDVybpi6lL2i8VAGIN8YgoK36lPaYsxWZ911lPCegy7B3kDj1xhBe41cNHgu8wYmjqLU7njleY5Pseherx+Kb58aQvB5xQr8w7KgAyMrsfRH30Btpg/ZWRn8qOXd/DW6eEla3djah4ug8jKdi0qUkA24FLDdOZST4vb5qhgQDVXpqJhYmBIU14YOHsCX9Olu6b7DDjQo/dvOaY3vzWROfV+sV60fUVIps8Vy1EpS/UXeHUxg6r37U8WAxUbSV8d6e4VylLuiIgbX5JpSC1s7jq/cwUwXfSJmKzaCj+C+LJ958IM17FYxIz5xWJtZEzWsPAH7WVCP3b1m4MHU/UwGuMu/Gfdzusnr+Qtan6Wqn9AqUyJP/JfrAgMBAAE=
|
||||||
|
|
||||||
|
支付私钥:
|
||||||
|
MIIG/QIBADANBgkqhkiG9w0BAQEFAASCBucwggbjAgEAAoIBgQD2DX5Byq8Lh/WlSLAnW6bWIFSU2fqFEMms12/nMHvuXkM2jpLqETjDtXKGQyeQiTz2LyRN6PWbUHKNGmjsHk4+pp82oj0kFDJDboDVmnvXNJ979SJwAWIy0XiVY2cAcFgfTqxRO/JvBiWUNXJumLqUvaLxUAYg3xiCgrfqU9pizFZn3XWU8J6DLsHeQOPXGEF7jVw0eC7zBiaOotTueOV5jk+x6F6vH4pvnxpC8HnFCvzDsqADIyux9EffQG2mD9lZGfyo5d38Nbp4SVrd2NqHi6DyMp2LSpSQDbgUsN05lJPi9vmqGBANVemomFiYEhTXhg4ewJf06W7pvsMONCj9285pje/NZE59X6xXrR9RUimzxXLUSlL9Rd4dTGDqvftTxYDFRtJXx3p7hXKUu6IiBtfkmlILWzuOr9zBTBd9ImYrNoKP4L4sn3nwgzXsVjEjPnFYm1kTNaw8AftZUI/dvWbgwdT9TAa4y78Z93O6yev5C1qfpaqf0CpTIk/8l+sCAwEAAQKCAYAVlJFiS9iWdlJBMOLiUNONLEC+3W9vhE1r72lNKZ91BKd4fYC9Ls1/vMZSqEksEB1cqj3Q54HDIYcqgQp6yx2puQt1yzz5kRvndiWulmIOOftS7+kZUcW/F0gwMguyqifQdyH97fgRbMSW/ykOMi8LJKbJ627eKzMHH1fqIXih+bIKYg4SBhihANTYHXDeSK5Vm8xefbwAbKWtFPMAB3J4+tZakDrduTJ3H8k53cWQVqpcr6oBHHCUpww2tHvpeLI3a/FXyHYBqrx8ErnXCjkVBHBtwQf+43H+buyDjrYUwJUi3RSJgeefcyyJoO0I9GwGb6nY8kK5kZ0aeIIkfiVOexMYS9w11FVl96LjC7HjJQrNY4jOLI/X76xyEwBFy9LNogRTafjZVZmVRj/9Kembx6+/eDxvyEv5FnelsHqfFbv1KPkR6e7FvwpDbYgJBpfKTE6SICqo52bHxewpSL7KHRlTpp0IfMM/IOOs8CCwI37ixKQun6W4en457j/tP4ECgcEA+0pa3omCKJfiVR4PpyXY+t9FEqdcZkdYEDtCSX9vqu6yE4sdt7rNnZ6cIiD6ViC2xvQ5VLry1y57JvP57svt5OJYhmILuj3jJ7pdcfKRAVaV4W7C8vGMit6ssckEOnOSj/bvEazfHorjXKU9cn9uCymczRQC9azaRkN+1LLk2mBbczghxd7rSJyXtbt9NVNUi9Gr11AzJsrWN/Bqqna5BizBLIaGYthJ/04LYTl1AjWPEJXjXI72/WvMdQ/w6itxAoHBAPqqAi5mbvjRDitmLQchc/R3Yl329OaB3GSz3YtFqfk9lLJIyl91+MzxBg1ChHnRFC8MB1s1Z05ZkFmcy8AVqvJ6MSZhUCLNjq8HyVgp24CqQTr8ciomF03ncXnKKSsH7tidInkQ8R3e7qRRGQMnDvV+Hs3FjlOk8vJyZCcGkUCVA0kkwkk/95qIfrZsthtewAxJR/rY63FnG+MUayEBB1lwyJZ7xDi/GyX6SAnPBl6bLRA1BdBeTV8K0MEwwIqzGwKBwBqo+9UKT7XQz2FqbAy2tjt/fouJGAN95DjsoI69p3JCGsB6DPAWMIRddIEmcIi8tceL151GrEbqFoS+c7DDD/0timjPdCEROc1YN1vEeV/j+MjPAH3X5KpDD51ZD0rIQi9l6l08svtBjvegTFGedWVXx9v2GI5KBWpY9NbKF/+XI3yo4uRkTyAIBQxx1MnYimq/FvUj/BlMgceziQ2GxQCDtQbtSsqn2cntVMW+28wdNI106YdDX67pRerRgyTE8QKBwHAwLxG9XuWWC5V5AaYzXsaHuEr+ANY6QP4BUqLG5zBaU3cIBSt8jYKMTX0ZzFkJLtNvussjt7zlcSnqd3bdO8mSzvSykT9CaR4FiiQfd9K6YL+ZxS8AJWYEtFEiHhLYVho1Gfy9jG0mHgEFGwDCNnvBmt/WD8F4DhRdBl5BHjmdd/8AqMRIEPXlKXFUbp0JZ0MYeVLYS2hSEbUsqlX3M+bgB6bydfw/7FKvFhbtxZgKM70RPizoSBDFsnEE9OgfCQKBwQD4M4f2f678YScvrwQnV2J8SSnJhQbZqht1Rlb34zU0JIcfpwOKpoNvRyZz1BxvzPSm5S3TLDytK16uvAsMFCHVByyCajPozlWnLzeEqvE4DZXZcWeY+/+MZ4nsajLTTeJWos6UrazZcd8/2c301x4OUKfgZWW1tS65MA48X0+Y4uPyOuH365wQ0obBfg4aB85sZl71v7Rveq+COHBstp/BdDVAsdufWCr7Nkbeu/3qh886ZumpYe+89So1oDIuoys=
|
||||||
|
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
支付密钥用于交易过程中的签名认证,请妥善保管,谨防泄露,签名认证规范参考开发指南服务端开发。
|
||||||
665
docs/api_kefu.md
Normal file
665
docs/api_kefu.md
Normal file
@@ -0,0 +1,665 @@
|
|||||||
|
# 智能客服API接口文档
|
||||||
|
|
||||||
|
## 一、接口说明
|
||||||
|
|
||||||
|
本接口用于连接微信小程序与Dify聊天机器人,实现智能客服功能。所有接口都采用事件驱动架构,支持高并发和模块化扩展。
|
||||||
|
|
||||||
|
## 二、配置说明
|
||||||
|
|
||||||
|
### 1. 安装插件
|
||||||
|
|
||||||
|
在ThinkPHP后台的插件管理页面中,找到智能客服插件(aikefu)并点击安装按钮。
|
||||||
|
|
||||||
|
### 2. 配置插件
|
||||||
|
|
||||||
|
1. 进入智能客服配置页面
|
||||||
|
2. 输入从Dify平台获取的API密钥
|
||||||
|
3. 配置API基础地址(默认:https://api.dify.ai/v1)
|
||||||
|
4. 配置聊天接口端点(默认:/chat-messages)
|
||||||
|
5. 启用智能客服功能
|
||||||
|
|
||||||
|
### 3. 获取Dify API密钥
|
||||||
|
|
||||||
|
1. 登录Dify平台
|
||||||
|
2. 进入工作台
|
||||||
|
3. 选择您的聊天机器人项目
|
||||||
|
4. 点击"发布"按钮
|
||||||
|
5. 在API访问页面获取API密钥
|
||||||
|
|
||||||
|
## 三、接口列表
|
||||||
|
|
||||||
|
### 1. 系统健康检查
|
||||||
|
|
||||||
|
**接口地址**:`/api/kefu/health`
|
||||||
|
|
||||||
|
**请求方式**:GET
|
||||||
|
|
||||||
|
**请求参数**:
|
||||||
|
|
||||||
|
| 参数名 | 类型 | 必填 | 说明 |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| uniacid | int | 是 | 站点ID |
|
||||||
|
| check_type | string | 否 | 检查类型:full(完整)、basic(基础)、ai_service(AI服务),默认full |
|
||||||
|
|
||||||
|
**响应示例**:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"code": 0,
|
||||||
|
"message": "healthy",
|
||||||
|
"data": {
|
||||||
|
"status": "healthy",
|
||||||
|
"check_id": "health_63a8f9c1234abcd",
|
||||||
|
"timestamp": "2023-12-25 10:30:45",
|
||||||
|
"total_checks": 4,
|
||||||
|
"passed_checks": 4,
|
||||||
|
"failed_checks": 0,
|
||||||
|
"response_time_ms": 156.78,
|
||||||
|
"components": {
|
||||||
|
"database": {
|
||||||
|
"status": "healthy",
|
||||||
|
"message": "数据库连接正常",
|
||||||
|
"response_time_ms": 12.34,
|
||||||
|
"details": {
|
||||||
|
"connection": "success",
|
||||||
|
"query_test": "passed"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"ai_service_config": {
|
||||||
|
"status": "healthy",
|
||||||
|
"message": "AI服务配置正常",
|
||||||
|
"response_time_ms": 8.56,
|
||||||
|
"details": {
|
||||||
|
"configured": true,
|
||||||
|
"complete": true,
|
||||||
|
"enabled": true,
|
||||||
|
"base_url": "https://api.example.com"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"ai_service_connection": {
|
||||||
|
"status": "healthy",
|
||||||
|
"message": "AI服务连接正常",
|
||||||
|
"response_time_ms": 45.67,
|
||||||
|
"details": {
|
||||||
|
"http_status": 200,
|
||||||
|
"url": "https://api.example.com"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"system_resources": {
|
||||||
|
"status": "healthy",
|
||||||
|
"message": "系统资源正常",
|
||||||
|
"response_time_ms": 2.34,
|
||||||
|
"details": {
|
||||||
|
"php_version": "8.1.0",
|
||||||
|
"memory_usage": "45.67 MB",
|
||||||
|
"memory_limit": "512.00 MB",
|
||||||
|
"memory_usage_percent": "8.92%",
|
||||||
|
"max_execution_time": "30s"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"warnings": [],
|
||||||
|
"errors": []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. 获取服务配置信息
|
||||||
|
|
||||||
|
**接口地址**:`/api/kefu/info`
|
||||||
|
|
||||||
|
**请求方式**:GET
|
||||||
|
|
||||||
|
**请求参数**:
|
||||||
|
|
||||||
|
| 参数名 | 类型 | 必填 | 说明 |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| uniacid | int | 否 | 站点ID |
|
||||||
|
| member_id | int | 否 | 会员ID |
|
||||||
|
| token | string | 否 | 访问令牌 |
|
||||||
|
|
||||||
|
**响应示例**:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"code": 0,
|
||||||
|
"message": "success",
|
||||||
|
"data": {
|
||||||
|
"service_info": {
|
||||||
|
"name": "智能客服",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"enabled": true,
|
||||||
|
"status": "enabled"
|
||||||
|
},
|
||||||
|
"features": {
|
||||||
|
"chat": true,
|
||||||
|
"chat_stream": true,
|
||||||
|
"conversation_management": true,
|
||||||
|
"history_management": true
|
||||||
|
},
|
||||||
|
"limits": {
|
||||||
|
"max_message_length": 4000,
|
||||||
|
"max_conversation_history": 100,
|
||||||
|
"rate_limit": {
|
||||||
|
"requests_per_minute": 60,
|
||||||
|
"requests_per_hour": 1000
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"endpoints": {
|
||||||
|
"chat": "/api/kefu/chat",
|
||||||
|
"chat_stream": "/api/kefu/chatStream",
|
||||||
|
"create_conversation": "/api/kefu/createConversation",
|
||||||
|
"get_history": "/api/kefu/getHistory",
|
||||||
|
"clear_conversation": "/api/kefu/clearConversation",
|
||||||
|
"health": "/api/kefu/health",
|
||||||
|
"info": "/api/kefu/info"
|
||||||
|
},
|
||||||
|
"api_config": {
|
||||||
|
"base_url": "https://api.dify.ai/v1",
|
||||||
|
"chat_endpoint": "/chat-messages",
|
||||||
|
"supports_streaming": true,
|
||||||
|
"authentication": "bearer_token"
|
||||||
|
},
|
||||||
|
"client_info": {
|
||||||
|
"user_agent": "Mozilla/5.0...",
|
||||||
|
"ip": "192.168.1.100",
|
||||||
|
"timestamp": 1703505845
|
||||||
|
},
|
||||||
|
"server_info": {
|
||||||
|
"php_version": "8.1.0",
|
||||||
|
"server_time": "2023-12-25 10:30:45",
|
||||||
|
"timezone": "Asia/Shanghai"
|
||||||
|
},
|
||||||
|
"user_stats": {
|
||||||
|
"can_use_service": true,
|
||||||
|
"member_id": 123,
|
||||||
|
"site_id": 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. 智能客服聊天接口
|
||||||
|
|
||||||
|
**接口地址**:`/api/kefu/chat`
|
||||||
|
|
||||||
|
**请求方式**:POST
|
||||||
|
|
||||||
|
**请求参数**:
|
||||||
|
|
||||||
|
| 参数名 | 类型 | 必填 | 说明 |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| uniacid | int | 是 | 站点ID |
|
||||||
|
| message | string | 是 | 用户输入的消息内容 |
|
||||||
|
| user_id | string | 否 | 用户ID,默认使用当前登录会员ID |
|
||||||
|
| conversation_id | string | 否 | 会话ID,第一次聊天可不传,系统会自动创建 |
|
||||||
|
| stream | bool | 否 | 是否使用流式响应,默认false |
|
||||||
|
| member_id | int | 否 | 会员ID |
|
||||||
|
| token | string | 否 | 访问令牌 |
|
||||||
|
|
||||||
|
**响应示例**:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"code": 0,
|
||||||
|
"message": "success",
|
||||||
|
"data": {
|
||||||
|
"conversation_id": "conv_123456789",
|
||||||
|
"reply": "您好,我是智能客服,有什么可以帮助您的?",
|
||||||
|
"message_id": "msg_123456789",
|
||||||
|
"finish_reason": "stop",
|
||||||
|
"usage": {
|
||||||
|
"prompt_tokens": 10,
|
||||||
|
"completion_tokens": 20,
|
||||||
|
"total_tokens": 30
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4. 智能客服流式聊天接口
|
||||||
|
|
||||||
|
**接口地址**:`/api/kefu/chatStream`
|
||||||
|
|
||||||
|
**请求方式**:POST
|
||||||
|
|
||||||
|
**请求参数**:同 `/api/kefu/chat` 接口
|
||||||
|
|
||||||
|
**响应格式**:Server-Sent Events (SSE)
|
||||||
|
|
||||||
|
**响应示例**:
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
// 开始事件
|
||||||
|
event: start
|
||||||
|
data: {
|
||||||
|
"id": "unique_id",
|
||||||
|
"event": "start",
|
||||||
|
"timestamp": 1703505845,
|
||||||
|
"data": {
|
||||||
|
"request_id": "stream_123",
|
||||||
|
"message": "开始处理请求"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 内容块事件
|
||||||
|
event: message
|
||||||
|
data: {
|
||||||
|
"id": "unique_id",
|
||||||
|
"event": "message",
|
||||||
|
"timestamp": 1703505845,
|
||||||
|
"data": {
|
||||||
|
"content": "您",
|
||||||
|
"conversation_id": "conv_123",
|
||||||
|
"finished": false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 完成事件
|
||||||
|
event: complete
|
||||||
|
data: {
|
||||||
|
"id": "unique_id",
|
||||||
|
"event": "complete",
|
||||||
|
"timestamp": 1703505845,
|
||||||
|
"data": {
|
||||||
|
"conversation_id": "conv_123",
|
||||||
|
"message_id": "msg_456",
|
||||||
|
"usage": {},
|
||||||
|
"finish_reason": "stop"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 结束事件
|
||||||
|
event: end
|
||||||
|
data: {
|
||||||
|
"id": "unique_id",
|
||||||
|
"event": "end",
|
||||||
|
"timestamp": 1703505845,
|
||||||
|
"data": {
|
||||||
|
"request_id": "stream_123",
|
||||||
|
"status": "completed"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 5. 创建新会话
|
||||||
|
|
||||||
|
**接口地址**:`/api/kefu/createConversation`
|
||||||
|
|
||||||
|
**请求方式**:POST
|
||||||
|
|
||||||
|
**请求参数**:
|
||||||
|
|
||||||
|
| 参数名 | 类型 | 必填 | 说明 |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| uniacid | int | 是 | 站点ID |
|
||||||
|
| user_id | string | 否 | 用户ID,默认使用当前登录会员ID |
|
||||||
|
| member_id | int | 否 | 会员ID |
|
||||||
|
| token | string | 否 | 访问令牌 |
|
||||||
|
|
||||||
|
**响应示例**:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"code": 0,
|
||||||
|
"message": "success",
|
||||||
|
"data": {
|
||||||
|
"conversation_id": "conv_123456789",
|
||||||
|
"name": "智能客服会话",
|
||||||
|
"created_at": "2023-12-25 10:30:45"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 6. 获取会话历史
|
||||||
|
|
||||||
|
**接口地址**:`/api/kefu/getHistory`
|
||||||
|
|
||||||
|
**请求方式**:POST
|
||||||
|
|
||||||
|
**请求参数**:
|
||||||
|
|
||||||
|
| 参数名 | 类型 | 必填 | 说明 |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| uniacid | int | 是 | 站点ID |
|
||||||
|
| conversation_id | string | 是 | 会话ID |
|
||||||
|
| user_id | string | 否 | 用户ID,默认使用当前登录会员ID |
|
||||||
|
| limit | int | 否 | 每页条数,默认20 |
|
||||||
|
| offset | int | 否 | 偏移量,默认0 |
|
||||||
|
| member_id | int | 否 | 会员ID |
|
||||||
|
| token | string | 否 | 访问令牌 |
|
||||||
|
|
||||||
|
**响应示例**:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"code": 0,
|
||||||
|
"message": "success",
|
||||||
|
"data": {
|
||||||
|
"messages": [
|
||||||
|
{
|
||||||
|
"id": 1,
|
||||||
|
"role": "user",
|
||||||
|
"content": "你好",
|
||||||
|
"create_time": "2023-12-25 10:30:45"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 2,
|
||||||
|
"role": "assistant",
|
||||||
|
"content": "您好,我是智能客服,有什么可以帮助您的?",
|
||||||
|
"create_time": "2023-12-25 10:30:46"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"total": 2,
|
||||||
|
"limit": 20,
|
||||||
|
"offset": 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 7. 清除会话历史
|
||||||
|
|
||||||
|
**接口地址**:`/api/kefu/clearConversation`
|
||||||
|
|
||||||
|
**请求方式**:POST
|
||||||
|
|
||||||
|
**请求参数**:
|
||||||
|
|
||||||
|
| 参数名 | 类型 | 必填 | 说明 |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| uniacid | int | 是 | 站点ID |
|
||||||
|
| conversation_id | string | 否 | 会话ID(与user_id二选一) |
|
||||||
|
| user_id | string | 否 | 用户ID,用于清除该用户所有会话(与conversation_id二选一) |
|
||||||
|
| member_id | int | 否 | 会员ID |
|
||||||
|
| token | string | 否 | 访问令牌 |
|
||||||
|
|
||||||
|
**响应示例**:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"code": 0,
|
||||||
|
"message": "success",
|
||||||
|
"data": {
|
||||||
|
"deleted_messages": 15,
|
||||||
|
"deleted_conversations": 3
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 四、前端调用示例
|
||||||
|
|
||||||
|
### Uniapp调用示例
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
// 引入请求封装(根据项目实际情况调整)
|
||||||
|
import { request } from '@/utils/request';
|
||||||
|
|
||||||
|
// 1. 获取服务配置信息
|
||||||
|
async function getAIInfo() {
|
||||||
|
try {
|
||||||
|
const res = await request({
|
||||||
|
url: '/api/kefu/info',
|
||||||
|
method: 'GET',
|
||||||
|
data: {
|
||||||
|
uniacid: 1
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (res.code === 0) {
|
||||||
|
console.log('AI服务状态:', res.data.service_info);
|
||||||
|
console.log('可用功能:', res.data.features);
|
||||||
|
return res.data;
|
||||||
|
} else {
|
||||||
|
console.error('获取配置失败:', res.message);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('获取配置请求失败:', error);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. 智能客服聊天(普通模式)
|
||||||
|
async function chatWithAI(message, conversationId = '') {
|
||||||
|
try {
|
||||||
|
const res = await request({
|
||||||
|
url: '/api/kefu/chat',
|
||||||
|
method: 'POST',
|
||||||
|
data: {
|
||||||
|
uniacid: 1,
|
||||||
|
message: message,
|
||||||
|
conversation_id: conversationId
|
||||||
|
// user_id: 'your-user-id', // 可选
|
||||||
|
// stream: false // 可选
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (res.code === 0) {
|
||||||
|
return res.data;
|
||||||
|
} else {
|
||||||
|
console.error('聊天失败:', res.message);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('聊天请求失败:', error);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. 智能客服聊天(流式模式)
|
||||||
|
async function chatWithAIStream(message, conversationId = '', onMessage, onComplete, onError) {
|
||||||
|
try {
|
||||||
|
const response = await uni.request({
|
||||||
|
url: '/api/kefu/chatStream',
|
||||||
|
method: 'POST',
|
||||||
|
data: {
|
||||||
|
uniacid: 1,
|
||||||
|
message: message,
|
||||||
|
conversation_id: conversationId
|
||||||
|
},
|
||||||
|
responseType: 'text'
|
||||||
|
});
|
||||||
|
|
||||||
|
// 处理流式响应
|
||||||
|
if (response.statusCode === 200) {
|
||||||
|
const lines = response.data.split('\n');
|
||||||
|
for (const line of lines) {
|
||||||
|
if (line.startsWith('data: ')) {
|
||||||
|
try {
|
||||||
|
const data = JSON.parse(line.substring(6));
|
||||||
|
if (data.event === 'message' && onMessage) {
|
||||||
|
onMessage(data.data);
|
||||||
|
} else if (data.event === 'complete' && onComplete) {
|
||||||
|
onComplete(data.data);
|
||||||
|
} else if (data.event === 'error' && onError) {
|
||||||
|
onError(data.data);
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.warn('解析流式数据失败:', e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('流式聊天请求失败:', error);
|
||||||
|
if (onError) onError({ error: error.message });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4. 创建新会话
|
||||||
|
async function createNewConversation() {
|
||||||
|
try {
|
||||||
|
const res = await request({
|
||||||
|
url: '/api/kefu/createConversation',
|
||||||
|
method: 'POST',
|
||||||
|
data: {
|
||||||
|
uniacid: 1
|
||||||
|
// user_id: 'your-user-id', // 可选
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (res.code === 0) {
|
||||||
|
return res.data.conversation_id;
|
||||||
|
} else {
|
||||||
|
console.error('创建会话失败:', res.message);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('创建会话请求失败:', error);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 5. 获取会话历史
|
||||||
|
async function getChatHistory(conversationId, limit = 20, offset = 0) {
|
||||||
|
try {
|
||||||
|
const res = await request({
|
||||||
|
url: '/api/kefu/getHistory',
|
||||||
|
method: 'POST',
|
||||||
|
data: {
|
||||||
|
uniacid: 1,
|
||||||
|
conversation_id: conversationId,
|
||||||
|
limit: limit,
|
||||||
|
offset: offset
|
||||||
|
// user_id: 'your-user-id', // 可选
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (res.code === 0) {
|
||||||
|
return res.data;
|
||||||
|
} else {
|
||||||
|
console.error('获取历史记录失败:', res.message);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('获取历史记录请求失败:', error);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 6. 清除会话历史
|
||||||
|
async function clearConversation(conversationId = '', userId = '') {
|
||||||
|
try {
|
||||||
|
const res = await request({
|
||||||
|
url: '/api/kefu/clearConversation',
|
||||||
|
method: 'POST',
|
||||||
|
data: {
|
||||||
|
uniacid: 1,
|
||||||
|
conversation_id: conversationId,
|
||||||
|
user_id: userId
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (res.code === 0) {
|
||||||
|
return res.data;
|
||||||
|
} else {
|
||||||
|
console.error('清除会话失败:', res.message);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('清除会话请求失败:', error);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 7. 健康检查
|
||||||
|
async function checkHealth(checkType = 'full') {
|
||||||
|
try {
|
||||||
|
const res = await request({
|
||||||
|
url: '/api/kefu/health',
|
||||||
|
method: 'GET',
|
||||||
|
data: {
|
||||||
|
uniacid: 1,
|
||||||
|
check_type: checkType
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (res.code === 0) {
|
||||||
|
return res.data;
|
||||||
|
} else {
|
||||||
|
console.error('健康检查失败:', res.message);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('健康检查请求失败:', error);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 五、使用流程
|
||||||
|
|
||||||
|
1. **初始化检查**:小程序端启动时,调用`health`和`info`接口检查服务状态
|
||||||
|
2. **创建会话**:进入客服页面时,调用`createConversation`接口创建新会话,或使用本地存储的会话ID
|
||||||
|
3. **发送消息**:用户输入消息后,调用`chat`或`chatStream`接口发送消息,获取机器人回复
|
||||||
|
4. **显示消息**:将用户消息和机器人回复显示在聊天界面
|
||||||
|
5. **加载历史记录**:需要时调用`getHistory`接口加载历史消息
|
||||||
|
6. **维护会话**:保持会话ID,用于后续消息交流
|
||||||
|
7. **清理数据**:根据用户需求调用`clearConversation`接口清理历史数据
|
||||||
|
|
||||||
|
## 六、注意事项
|
||||||
|
|
||||||
|
1. **必填参数**:所有接口都需要`uniacid`(站点ID)参数
|
||||||
|
2. **事件驱动**:后端采用事件驱动架构,所有业务逻辑通过事件处理器执行
|
||||||
|
3. **安全性**:请确保Dify API密钥的安全性,不要泄露给前端
|
||||||
|
4. **用户标识**:建议对用户ID进行加密处理,避免直接使用敏感信息
|
||||||
|
5. **流式支持**:推荐使用`chatStream`接口获得更好的用户体验
|
||||||
|
6. **会话管理**:建议实现会话管理机制,定期清理过期会话
|
||||||
|
7. **频率限制**:建议添加请求频率限制,防止恶意请求
|
||||||
|
8. **生产环境**:在生产环境中,建议关闭DEBUG模式
|
||||||
|
|
||||||
|
## 七、测试建议
|
||||||
|
|
||||||
|
1. **基础检查**:首先调用`health`接口检查系统状态
|
||||||
|
2. **配置验证**:调用`info`接口验证配置信息
|
||||||
|
3. **接口测试**:使用Postman或类似工具测试各个API接口
|
||||||
|
4. **流式测试**:测试`chatStream`接口的流式响应
|
||||||
|
5. **完整流程**:在小程序端集成并测试完整流程
|
||||||
|
6. **边界测试**:模拟不同场景下的用户输入,测试机器人回复效果
|
||||||
|
7. **压力测试**:测试接口在高并发情况下的表现
|
||||||
|
|
||||||
|
## 八、常见问题
|
||||||
|
|
||||||
|
### 1. 接口返回400错误
|
||||||
|
|
||||||
|
**原因**:缺少必填参数`uniacid`或参数格式错误
|
||||||
|
**解决方法**:确保请求中包含有效的站点ID
|
||||||
|
|
||||||
|
### 2. 健康检查返回503错误
|
||||||
|
|
||||||
|
**原因**:AI服务配置不完整或服务异常
|
||||||
|
**解决方法**:检查插件配置和Dify API服务状态
|
||||||
|
|
||||||
|
### 3. 接口返回401错误
|
||||||
|
|
||||||
|
**原因**:Dify API密钥无效或过期
|
||||||
|
**解决方法**:重新获取有效的API密钥并更新插件配置
|
||||||
|
|
||||||
|
### 4. 接口返回500错误
|
||||||
|
|
||||||
|
**原因**:后端服务器错误或Dify API服务异常
|
||||||
|
**解决方法**:查看服务器日志,检查Dify API服务状态
|
||||||
|
|
||||||
|
### 5. 机器人回复为空
|
||||||
|
|
||||||
|
**原因**:Dify聊天机器人配置问题或请求参数错误
|
||||||
|
**解决方法**:检查Dify机器人配置,验证请求参数是否正确
|
||||||
|
|
||||||
|
### 6. 流式响应无法解析
|
||||||
|
|
||||||
|
**原因**:客户端不支持SSE或解析方式错误
|
||||||
|
**解决方法**:使用正确的方式解析Server-Sent Events格式
|
||||||
|
|
||||||
|
### 7. 会话ID无效
|
||||||
|
|
||||||
|
**原因**:会话已过期或不存在
|
||||||
|
**解决方法**:创建新会话,获取新的会话ID
|
||||||
|
|
||||||
|
## 九、性能优化建议
|
||||||
|
|
||||||
|
1. **缓存配置**:可对`info`接口返回的配置信息进行客户端缓存
|
||||||
|
2. **连接复用**:HTTP请求使用连接池,减少建立连接的开销
|
||||||
|
3. **压缩传输**:启用gzip压缩减少传输数据量
|
||||||
|
4. **分页加载**:历史记录使用分页加载,避免一次性加载大量数据
|
||||||
|
5. **CDN加速**:静态资源使用CDN加速访问
|
||||||
|
6. **监控告警**:建立接口性能监控和告警机制
|
||||||
375
docs/db/compare_sql_tables.py
Normal file
375
docs/db/compare_sql_tables.py
Normal file
@@ -0,0 +1,375 @@
|
|||||||
|
import re
|
||||||
|
import os
|
||||||
|
|
||||||
|
# 解析SQL文件,提取表结构
|
||||||
|
def parse_sql_file(file_path, ignore_prefix=None):
|
||||||
|
tables = {}
|
||||||
|
|
||||||
|
# 读取文件内容
|
||||||
|
with open(file_path, 'r', encoding='utf-8') as f:
|
||||||
|
content = f.read()
|
||||||
|
|
||||||
|
# 匹配CREATE TABLE语句
|
||||||
|
table_pattern = re.compile(r'CREATE TABLE\s+`?([^`\s]+)`?\s*\(([^;]+)\)\s*(?:[^;]+);', re.DOTALL | re.IGNORECASE)
|
||||||
|
|
||||||
|
matches = table_pattern.findall(content)
|
||||||
|
|
||||||
|
for full_table_name, table_def in matches:
|
||||||
|
# 处理表名,忽略前缀
|
||||||
|
table_name = full_table_name
|
||||||
|
if ignore_prefix and table_name.startswith(ignore_prefix):
|
||||||
|
table_name = table_name[len(ignore_prefix):]
|
||||||
|
|
||||||
|
# 提取列定义
|
||||||
|
columns = []
|
||||||
|
# 匹配列定义,包括列名、类型、约束等
|
||||||
|
column_pattern = re.compile(r'\s*`?([^`\s,]+)`?\s+([^\s,]+)\s*([^,]+)(?:,|$)', re.DOTALL)
|
||||||
|
column_matches = column_pattern.findall(table_def)
|
||||||
|
|
||||||
|
for col_name, col_type, col_constraints in column_matches:
|
||||||
|
# 清理约束中的换行符和多余空格
|
||||||
|
col_constraints = re.sub(r'\s+', ' ', col_constraints.strip())
|
||||||
|
columns.append((col_name, col_type, col_constraints))
|
||||||
|
|
||||||
|
# 提取主键
|
||||||
|
primary_key = None
|
||||||
|
pk_pattern = re.compile(r'PRIMARY\s+KEY\s*\(`?([^`\s,]+)`?\)', re.IGNORECASE)
|
||||||
|
pk_match = pk_pattern.search(table_def)
|
||||||
|
if pk_match:
|
||||||
|
primary_key = pk_match.group(1)
|
||||||
|
|
||||||
|
# 提取索引
|
||||||
|
indexes = []
|
||||||
|
index_pattern = re.compile(r'INDEX\s+`?([^`\s]+)`?\s*\(([^)]+)\)', re.IGNORECASE)
|
||||||
|
index_matches = index_pattern.findall(table_def)
|
||||||
|
for index_name, index_cols in index_matches:
|
||||||
|
indexes.append((index_name, index_cols.strip()))
|
||||||
|
|
||||||
|
# 提取唯一约束
|
||||||
|
unique_constraints = []
|
||||||
|
unique_pattern = re.compile(r'UNIQUE\s+KEY\s*`?([^`\s]+)`?\s*\(([^)]+)\)', re.IGNORECASE)
|
||||||
|
unique_matches = unique_pattern.findall(table_def)
|
||||||
|
for unique_name, unique_cols in unique_matches:
|
||||||
|
unique_constraints.append((unique_name, unique_cols.strip()))
|
||||||
|
|
||||||
|
tables[table_name] = {
|
||||||
|
'full_name': full_table_name,
|
||||||
|
'columns': columns,
|
||||||
|
'primary_key': primary_key,
|
||||||
|
'indexes': indexes,
|
||||||
|
'unique_constraints': unique_constraints
|
||||||
|
}
|
||||||
|
|
||||||
|
return tables
|
||||||
|
|
||||||
|
# 比较两个数据库表结构
|
||||||
|
def compare_databases(db1, db2, db1_name, db2_name):
|
||||||
|
diffs = {
|
||||||
|
'only_in_db1': [],
|
||||||
|
'only_in_db2': [],
|
||||||
|
'column_diffs': {},
|
||||||
|
'primary_key_diffs': {},
|
||||||
|
'index_diffs': {},
|
||||||
|
'unique_constraint_diffs': {}
|
||||||
|
}
|
||||||
|
|
||||||
|
# 找出只在db1中存在的表
|
||||||
|
for table_name in db1:
|
||||||
|
if table_name not in db2:
|
||||||
|
diffs['only_in_db1'].append(table_name)
|
||||||
|
|
||||||
|
# 找出只在db2中存在的表
|
||||||
|
for table_name in db2:
|
||||||
|
if table_name not in db1:
|
||||||
|
diffs['only_in_db2'].append(table_name)
|
||||||
|
|
||||||
|
# 比较共同存在的表
|
||||||
|
common_tables = set(db1.keys()) & set(db2.keys())
|
||||||
|
for table_name in common_tables:
|
||||||
|
table1 = db1[table_name]
|
||||||
|
table2 = db2[table_name]
|
||||||
|
|
||||||
|
# 比较列
|
||||||
|
col_diffs = {
|
||||||
|
'only_in_db1': [],
|
||||||
|
'only_in_db2': [],
|
||||||
|
'type_diffs': [],
|
||||||
|
'constraint_diffs': []
|
||||||
|
}
|
||||||
|
|
||||||
|
# 列名集合
|
||||||
|
cols1 = {col[0]: col for col in table1['columns']}
|
||||||
|
cols2 = {col[0]: col for col in table2['columns']}
|
||||||
|
|
||||||
|
# 只在db1中的列
|
||||||
|
for col_name in cols1:
|
||||||
|
if col_name not in cols2:
|
||||||
|
col_diffs['only_in_db1'].append(col_name)
|
||||||
|
|
||||||
|
# 只在db2中的列
|
||||||
|
for col_name in cols2:
|
||||||
|
if col_name not in cols1:
|
||||||
|
col_diffs['only_in_db2'].append(col_name)
|
||||||
|
|
||||||
|
# 比较列类型和约束
|
||||||
|
common_cols = set(cols1.keys()) & set(cols2.keys())
|
||||||
|
for col_name in common_cols:
|
||||||
|
col1 = cols1[col_name]
|
||||||
|
col2 = cols2[col_name]
|
||||||
|
|
||||||
|
# 类型差异
|
||||||
|
if col1[1] != col2[1]:
|
||||||
|
col_diffs['type_diffs'].append((col_name, col1[1], col2[1]))
|
||||||
|
|
||||||
|
# 约束差异
|
||||||
|
if col1[2] != col2[2]:
|
||||||
|
col_diffs['constraint_diffs'].append((col_name, col1[2], col2[2]))
|
||||||
|
|
||||||
|
if any(col_diffs.values()):
|
||||||
|
diffs['column_diffs'][table_name] = col_diffs
|
||||||
|
|
||||||
|
# 比较主键
|
||||||
|
if table1['primary_key'] != table2['primary_key']:
|
||||||
|
diffs['primary_key_diffs'][table_name] = (table1['primary_key'], table2['primary_key'])
|
||||||
|
|
||||||
|
# 比较索引
|
||||||
|
index_diffs = {
|
||||||
|
'only_in_db1': [],
|
||||||
|
'only_in_db2': [],
|
||||||
|
'definition_diffs': []
|
||||||
|
}
|
||||||
|
|
||||||
|
indexes1 = {idx[0]: idx[1] for idx in table1['indexes']}
|
||||||
|
indexes2 = {idx[0]: idx[1] for idx in table2['indexes']}
|
||||||
|
|
||||||
|
# 只在db1中的索引
|
||||||
|
for idx_name in indexes1:
|
||||||
|
if idx_name not in indexes2:
|
||||||
|
index_diffs['only_in_db1'].append((idx_name, indexes1[idx_name]))
|
||||||
|
|
||||||
|
# 只在db2中的索引
|
||||||
|
for idx_name in indexes2:
|
||||||
|
if idx_name not in indexes1:
|
||||||
|
index_diffs['only_in_db2'].append((idx_name, indexes2[idx_name]))
|
||||||
|
|
||||||
|
# 比较索引定义
|
||||||
|
common_indexes = set(indexes1.keys()) & set(indexes2.keys())
|
||||||
|
for idx_name in common_indexes:
|
||||||
|
if indexes1[idx_name] != indexes2[idx_name]:
|
||||||
|
index_diffs['definition_diffs'].append((idx_name, indexes1[idx_name], indexes2[idx_name]))
|
||||||
|
|
||||||
|
if any(index_diffs.values()):
|
||||||
|
diffs['index_diffs'][table_name] = index_diffs
|
||||||
|
|
||||||
|
# 比较唯一约束
|
||||||
|
unique_diffs = {
|
||||||
|
'only_in_db1': [],
|
||||||
|
'only_in_db2': [],
|
||||||
|
'definition_diffs': []
|
||||||
|
}
|
||||||
|
|
||||||
|
unique1 = {uc[0]: uc[1] for uc in table1['unique_constraints']}
|
||||||
|
unique2 = {uc[0]: uc[1] for uc in table2['unique_constraints']}
|
||||||
|
|
||||||
|
# 只在db1中的唯一约束
|
||||||
|
for uc_name in unique1:
|
||||||
|
if uc_name not in unique2:
|
||||||
|
unique_diffs['only_in_db1'].append((uc_name, unique1[uc_name]))
|
||||||
|
|
||||||
|
# 只在db2中的唯一约束
|
||||||
|
for uc_name in unique2:
|
||||||
|
if uc_name not in unique1:
|
||||||
|
unique_diffs['only_in_db2'].append((uc_name, unique2[uc_name]))
|
||||||
|
|
||||||
|
# 比较唯一约束定义
|
||||||
|
common_unique = set(unique1.keys()) & set(unique2.keys())
|
||||||
|
for uc_name in common_unique:
|
||||||
|
if unique1[uc_name] != unique2[uc_name]:
|
||||||
|
unique_diffs['definition_diffs'].append((uc_name, unique1[uc_name], unique2[uc_name]))
|
||||||
|
|
||||||
|
if any(unique_diffs.values()):
|
||||||
|
diffs['unique_constraint_diffs'][table_name] = unique_diffs
|
||||||
|
|
||||||
|
return diffs
|
||||||
|
|
||||||
|
# 打印差异报告
|
||||||
|
# 生成Markdown格式的差异报告
|
||||||
|
def generate_markdown_report(diffs, db1_name, db2_name, db1_table_count, db2_table_count):
|
||||||
|
report = []
|
||||||
|
|
||||||
|
# 报告标题
|
||||||
|
report.append(f"# 数据库差异报告: {db1_name} vs {db2_name}")
|
||||||
|
report.append("\n## 1. 表数量统计")
|
||||||
|
report.append("| 数据库文件 | 表数量 |")
|
||||||
|
report.append("|------------|--------|")
|
||||||
|
report.append(f"| {db1_name} | {db1_table_count} |")
|
||||||
|
report.append(f"| {db2_name} | {db2_table_count} |")
|
||||||
|
|
||||||
|
# 表存在性差异
|
||||||
|
report.append("\n## 2. 表存在性差异")
|
||||||
|
|
||||||
|
# 仅在db1中的表
|
||||||
|
if diffs['only_in_db1']:
|
||||||
|
report.append(f"\n### 2.1 仅在 {db1_name} 中存在的表 ({len(diffs['only_in_db1'])} 个)")
|
||||||
|
report.append("| 表名 |")
|
||||||
|
report.append("|------|")
|
||||||
|
for table in sorted(diffs['only_in_db1']):
|
||||||
|
report.append(f"| {table} |")
|
||||||
|
|
||||||
|
# 仅在db2中的表
|
||||||
|
if diffs['only_in_db2']:
|
||||||
|
report.append(f"\n### 2.2 仅在 {db2_name} 中存在的表 ({len(diffs['only_in_db2'])} 个)")
|
||||||
|
report.append("| 表名 |")
|
||||||
|
report.append("|------|")
|
||||||
|
for table in sorted(diffs['only_in_db2']):
|
||||||
|
report.append(f"| {table} |")
|
||||||
|
|
||||||
|
# 列结构差异
|
||||||
|
if diffs['column_diffs']:
|
||||||
|
report.append(f"\n## 3. 列结构差异的表 ({len(diffs['column_diffs'])} 个)")
|
||||||
|
|
||||||
|
for table, col_diffs in diffs['column_diffs'].items():
|
||||||
|
report.append(f"\n### 3.1 表: {table}")
|
||||||
|
|
||||||
|
# 仅在db1中的列
|
||||||
|
if col_diffs['only_in_db1']:
|
||||||
|
report.append(f"\n#### 3.1.1 仅在 {db1_name} 中存在的列")
|
||||||
|
report.append("| 列名 |")
|
||||||
|
report.append("|------|")
|
||||||
|
for col in col_diffs['only_in_db1']:
|
||||||
|
report.append(f"| {col} |")
|
||||||
|
|
||||||
|
# 仅在db2中的列
|
||||||
|
if col_diffs['only_in_db2']:
|
||||||
|
report.append(f"\n#### 3.1.2 仅在 {db2_name} 中存在的列")
|
||||||
|
report.append("| 列名 |")
|
||||||
|
report.append("|------|")
|
||||||
|
for col in col_diffs['only_in_db2']:
|
||||||
|
report.append(f"| {col} |")
|
||||||
|
|
||||||
|
# 列类型差异
|
||||||
|
if col_diffs['type_diffs']:
|
||||||
|
report.append(f"\n#### 3.1.3 列类型差异")
|
||||||
|
report.append(f"| 列名 | {db1_name} | {db2_name} |")
|
||||||
|
report.append("|------|------------|------------|")
|
||||||
|
for col_name, type1, type2 in col_diffs['type_diffs']:
|
||||||
|
report.append(f"| {col_name} | {type1} | {type2} |")
|
||||||
|
|
||||||
|
# 列约束差异
|
||||||
|
if col_diffs['constraint_diffs']:
|
||||||
|
report.append(f"\n#### 3.1.4 列约束差异")
|
||||||
|
report.append(f"| 列名 | {db1_name} | {db2_name} |")
|
||||||
|
report.append("|------|------------|------------|")
|
||||||
|
for col_name, constraint1, constraint2 in col_diffs['constraint_diffs']:
|
||||||
|
report.append(f"| {col_name} | {constraint1} | {constraint2} |")
|
||||||
|
|
||||||
|
# 主键差异
|
||||||
|
if diffs['primary_key_diffs']:
|
||||||
|
report.append(f"\n## 4. 主键差异的表 ({len(diffs['primary_key_diffs'])} 个)")
|
||||||
|
report.append(f"| 表名 | {db1_name} | {db2_name} |")
|
||||||
|
report.append("|------|------------|------------|")
|
||||||
|
for table, (pk1, pk2) in diffs['primary_key_diffs'].items():
|
||||||
|
report.append(f"| {table} | {pk1} | {pk2} |")
|
||||||
|
|
||||||
|
# 索引差异
|
||||||
|
if diffs['index_diffs']:
|
||||||
|
report.append(f"\n## 5. 索引差异的表 ({len(diffs['index_diffs'])} 个)")
|
||||||
|
|
||||||
|
for table, idx_diffs in diffs['index_diffs'].items():
|
||||||
|
report.append(f"\n### 5.1 表: {table}")
|
||||||
|
|
||||||
|
# 仅在db1中的索引
|
||||||
|
if idx_diffs['only_in_db1']:
|
||||||
|
report.append(f"\n#### 5.1.1 仅在 {db1_name} 中存在的索引")
|
||||||
|
report.append("| 索引名 | 索引列 |")
|
||||||
|
report.append("|--------|--------|")
|
||||||
|
for idx_name, idx_cols in idx_diffs['only_in_db1']:
|
||||||
|
report.append(f"| {idx_name} | {idx_cols} |")
|
||||||
|
|
||||||
|
# 仅在db2中的索引
|
||||||
|
if idx_diffs['only_in_db2']:
|
||||||
|
report.append(f"\n#### 5.1.2 仅在 {db2_name} 中存在的索引")
|
||||||
|
report.append("| 索引名 | 索引列 |")
|
||||||
|
report.append("|--------|--------|")
|
||||||
|
for idx_name, idx_cols in idx_diffs['only_in_db2']:
|
||||||
|
report.append(f"| {idx_name} | {idx_cols} |")
|
||||||
|
|
||||||
|
# 索引定义差异
|
||||||
|
if idx_diffs['definition_diffs']:
|
||||||
|
report.append(f"\n#### 5.1.3 索引定义差异")
|
||||||
|
report.append(f"| 索引名 | {db1_name} | {db2_name} |")
|
||||||
|
report.append("|--------|------------|------------|")
|
||||||
|
for idx_name, idx1, idx2 in idx_diffs['definition_diffs']:
|
||||||
|
report.append(f"| {idx_name} | {idx1} | {idx2} |")
|
||||||
|
|
||||||
|
# 唯一约束差异
|
||||||
|
if diffs['unique_constraint_diffs']:
|
||||||
|
report.append(f"\n## 6. 唯一约束差异的表 ({len(diffs['unique_constraint_diffs'])} 个)")
|
||||||
|
|
||||||
|
for table, uc_diffs in diffs['unique_constraint_diffs'].items():
|
||||||
|
report.append(f"\n### 6.1 表: {table}")
|
||||||
|
|
||||||
|
# 仅在db1中的唯一约束
|
||||||
|
if uc_diffs['only_in_db1']:
|
||||||
|
report.append(f"\n#### 6.1.1 仅在 {db1_name} 中存在的唯一约束")
|
||||||
|
report.append("| 约束名 | 约束列 |")
|
||||||
|
report.append("|--------|--------|")
|
||||||
|
for uc_name, uc_cols in uc_diffs['only_in_db1']:
|
||||||
|
report.append(f"| {uc_name} | {uc_cols} |")
|
||||||
|
|
||||||
|
# 仅在db2中的唯一约束
|
||||||
|
if uc_diffs['only_in_db2']:
|
||||||
|
report.append(f"\n#### 6.1.2 仅在 {db2_name} 中存在的唯一约束")
|
||||||
|
report.append("| 约束名 | 约束列 |")
|
||||||
|
report.append("|--------|--------|")
|
||||||
|
for uc_name, uc_cols in uc_diffs['only_in_db2']:
|
||||||
|
report.append(f"| {uc_name} | {uc_cols} |")
|
||||||
|
|
||||||
|
# 唯一约束定义差异
|
||||||
|
if uc_diffs['definition_diffs']:
|
||||||
|
report.append(f"\n#### 6.1.3 唯一约束定义差异")
|
||||||
|
report.append(f"| 约束名 | {db1_name} | {db2_name} |")
|
||||||
|
report.append("|--------|------------|------------|")
|
||||||
|
for uc_name, uc1, uc2 in uc_diffs['definition_diffs']:
|
||||||
|
report.append(f"| {uc_name} | {uc1} | {uc2} |")
|
||||||
|
|
||||||
|
report.append("\n## 7. 总结")
|
||||||
|
report.append("差异比较完成!")
|
||||||
|
|
||||||
|
return '\n'.join(report)
|
||||||
|
|
||||||
|
# 主函数
|
||||||
|
def main():
|
||||||
|
# 文件路径
|
||||||
|
db1_path = r'D:\projects\shop-projects\backend\docs\db\niushop_database.sql'
|
||||||
|
db2_path = r'D:\projects\shop-projects\backend\docs\db\init_v2.0_with_data.sql'
|
||||||
|
report_path = r'D:\projects\shop-projects\backend\docs\db\database_diff_report.md'
|
||||||
|
|
||||||
|
# 解析数据库结构
|
||||||
|
print(f"正在解析 {db1_path}...")
|
||||||
|
db1 = parse_sql_file(db1_path)
|
||||||
|
db1_table_count = len(db1)
|
||||||
|
print(f"解析完成,共 {db1_table_count} 个表")
|
||||||
|
|
||||||
|
print(f"\n正在解析 {db2_path}...")
|
||||||
|
db2 = parse_sql_file(db2_path, ignore_prefix='lucky_')
|
||||||
|
db2_table_count = len(db2)
|
||||||
|
print(f"解析完成,共 {db2_table_count} 个表")
|
||||||
|
|
||||||
|
# 比较差异
|
||||||
|
print("\n正在比较数据库差异...")
|
||||||
|
diffs = compare_databases(db1, db2, 'niushop_database.sql', 'init_v2.0_with_data.sql')
|
||||||
|
|
||||||
|
# 生成Markdown差异报告
|
||||||
|
print("\n正在生成Markdown差异报告...")
|
||||||
|
report = generate_markdown_report(diffs, 'niushop_database.sql', 'init_v2.0_with_data.sql', db1_table_count, db2_table_count)
|
||||||
|
|
||||||
|
# 保存报告到文件
|
||||||
|
with open(report_path, 'w', encoding='utf-8') as f:
|
||||||
|
f.write(report)
|
||||||
|
|
||||||
|
print(f"\n差异报告已生成: {report_path}")
|
||||||
|
print("差异比较完成!")
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
5278
docs/db/database_diff_report.md
Normal file
5278
docs/db/database_diff_report.md
Normal file
File diff suppressed because it is too large
Load Diff
11578
docs/db/niushop_database.sql
Normal file
11578
docs/db/niushop_database.sql
Normal file
File diff suppressed because it is too large
Load Diff
117
docs/db/update_sql_comments.py
Normal file
117
docs/db/update_sql_comments.py
Normal file
@@ -0,0 +1,117 @@
|
|||||||
|
import re
|
||||||
|
import os
|
||||||
|
|
||||||
|
# 读取 SQL 文件内容
|
||||||
|
def read_sql_file(file_path):
|
||||||
|
with open(file_path, 'r', encoding='utf-8') as f:
|
||||||
|
return f.read()
|
||||||
|
|
||||||
|
# 解析 database.sql,提取表结构和注释
|
||||||
|
def parse_database_sql(sql_content):
|
||||||
|
tables = {}
|
||||||
|
|
||||||
|
# 匹配 CREATE TABLE 语句,更灵活的格式
|
||||||
|
# 匹配所有 CREATE TABLE 语句,不依赖于 ENGINE、CHARACTER SET 等子句的顺序
|
||||||
|
table_pattern = re.compile(r"CREATE TABLE\s+`?([^`\s]+)`?\s*\(([^;]+)\)\s*(?:[^;]+COMMENT\s*=\s*'([^']+)'[^;]*)?\s*;", re.DOTALL | re.IGNORECASE)
|
||||||
|
|
||||||
|
matches = table_pattern.findall(sql_content)
|
||||||
|
|
||||||
|
for table_name, table_def, table_comment in matches:
|
||||||
|
if not table_comment:
|
||||||
|
# 如果没有匹配到表注释,尝试从其他位置获取
|
||||||
|
comment_match = re.search(r"COMMENT\s*=\s*'([^']+)'", table_def, re.IGNORECASE)
|
||||||
|
if comment_match:
|
||||||
|
table_comment = comment_match.group(1)
|
||||||
|
else:
|
||||||
|
table_comment = ''
|
||||||
|
|
||||||
|
# 解析列定义和注释
|
||||||
|
columns = {}
|
||||||
|
# 匹配列定义,包括 COMMENT
|
||||||
|
column_lines = table_def.split('\n')
|
||||||
|
for line in column_lines:
|
||||||
|
# 匹配列名、类型和注释
|
||||||
|
column_match = re.search(r"\s*([^\s,]+)\s+([^\s,]+)\s*(?:[^,]+COMMENT\s*=\s*'([^']+)'[^,]*|[^,]*)", line)
|
||||||
|
if column_match:
|
||||||
|
column_name = column_match.group(1)
|
||||||
|
column_comment = column_match.group(3) or ''
|
||||||
|
if column_comment:
|
||||||
|
columns[column_name] = column_comment
|
||||||
|
|
||||||
|
tables[table_name] = {
|
||||||
|
'comment': table_comment,
|
||||||
|
'columns': columns
|
||||||
|
}
|
||||||
|
|
||||||
|
return tables
|
||||||
|
|
||||||
|
# 更新 init_v2.0.sql 文件中的注释
|
||||||
|
def update_init_sql(init_sql_path, database_tables):
|
||||||
|
# 读取 init_v2.0.sql 内容
|
||||||
|
init_content = read_sql_file(init_sql_path)
|
||||||
|
|
||||||
|
# 匹配 CREATE TABLE 语句,适应 init_v2.0.sql 的格式
|
||||||
|
table_pattern = re.compile(r"(create table if not exists lucky_([^\s]+)\s*\(([^;]+)\)\s*comment\s*=\s*'[^']*'\s*(.*?);)", re.DOTALL | re.IGNORECASE)
|
||||||
|
|
||||||
|
def replace_table(match):
|
||||||
|
full_match = match.group(0)
|
||||||
|
table_name = match.group(2)
|
||||||
|
table_def = match.group(3)
|
||||||
|
table_suffix = match.group(4)
|
||||||
|
|
||||||
|
if table_name in database_tables:
|
||||||
|
# 获取数据库表的注释和列注释
|
||||||
|
db_table = database_tables[table_name]
|
||||||
|
table_comment = db_table['comment']
|
||||||
|
columns = db_table['columns']
|
||||||
|
|
||||||
|
# 更新列注释
|
||||||
|
new_table_def = table_def
|
||||||
|
for column_name, column_comment in columns.items():
|
||||||
|
# 匹配列定义,替换注释
|
||||||
|
# 格式:列名 类型 default 默认值 not null comment '注释'
|
||||||
|
column_pattern = re.compile(r"(\s*" + column_name + r"\s+[^\s,]+\s*(?:default\s+[^\s,]+\s*)?(?:not null\s*)?comment\s*=\s*')([^']*)'([^,]*)", re.IGNORECASE)
|
||||||
|
new_table_def = column_pattern.sub(r"\1" + column_comment + r"'\3", new_table_def)
|
||||||
|
|
||||||
|
# 重新构建 CREATE TABLE 语句
|
||||||
|
new_full_match = f"create table if not exists lucky_{table_name} ({new_table_def}) comment = '{table_comment}' {table_suffix};"
|
||||||
|
return new_full_match
|
||||||
|
|
||||||
|
return full_match
|
||||||
|
|
||||||
|
# 替换所有表
|
||||||
|
updated_content = table_pattern.sub(replace_table, init_content)
|
||||||
|
|
||||||
|
# 写回文件
|
||||||
|
with open(init_sql_path, 'w', encoding='utf-8') as f:
|
||||||
|
f.write(updated_content)
|
||||||
|
|
||||||
|
print(f"Updated {init_sql_path}")
|
||||||
|
|
||||||
|
# 主函数
|
||||||
|
def main():
|
||||||
|
# 文件路径
|
||||||
|
database_sql_path = r'./niushop_database.sql'
|
||||||
|
init_v20_sql_path = r'./init_v2.0.sql'
|
||||||
|
init_v20_with_data_sql_path = r'./init_v2.0_with_data.sql'
|
||||||
|
|
||||||
|
# 解析 database.sql
|
||||||
|
print("Parsing database.sql...")
|
||||||
|
database_content = read_sql_file(database_sql_path)
|
||||||
|
database_tables = parse_database_sql(database_content)
|
||||||
|
print(f"Found {len(database_tables)} tables in database.sql")
|
||||||
|
|
||||||
|
# 更新 init_v2.0.sql
|
||||||
|
if os.path.exists(init_v20_sql_path):
|
||||||
|
print("Updating init_v2.0.sql...")
|
||||||
|
update_init_sql(init_v20_sql_path, database_tables)
|
||||||
|
|
||||||
|
# 更新 init_v2.0_with_data.sql
|
||||||
|
if os.path.exists(init_v20_with_data_sql_path):
|
||||||
|
print("Updating init_v2.0_with_data.sql...")
|
||||||
|
update_init_sql(init_v20_with_data_sql_path, database_tables)
|
||||||
|
|
||||||
|
print("All files updated successfully!")
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
170
replace_comments.py
Normal file
170
replace_comments.py
Normal file
@@ -0,0 +1,170 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
import os
|
||||||
|
import argparse
|
||||||
|
|
||||||
|
# 定义要查找和替换的注释内容
|
||||||
|
# 使用原始字符串数组,支持多个旧内容,避免Unicode转义问题
|
||||||
|
OLD_COMMENTS = [
|
||||||
|
r"""
|
||||||
|
<?php
|
||||||
|
/**
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
*/
|
||||||
|
""".strip(),
|
||||||
|
r"""
|
||||||
|
<?php
|
||||||
|
|
||||||
|
/**
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
*/
|
||||||
|
""".strip(),
|
||||||
|
# 格式1:标准注释格式
|
||||||
|
r"""
|
||||||
|
<?php
|
||||||
|
/**
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
*/
|
||||||
|
""".strip(),
|
||||||
|
r"""
|
||||||
|
<?php
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Niushop商城系统 - 团队十年电商经验汇集巨献!
|
||||||
|
* =========================================================
|
||||||
|
* Copy right 2019-2029 杭州牛之云科技有限公司, 保留所有权利。
|
||||||
|
* ----------------------------------------------
|
||||||
|
* 官方网址: https://www.niushop.com
|
||||||
|
* =========================================================
|
||||||
|
*/
|
||||||
|
""".strip(),
|
||||||
|
# 格式1:标准注释格式
|
||||||
|
r"""
|
||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Niushop商城系统 - 团队十年电商经验汇集巨献!
|
||||||
|
* =========================================================
|
||||||
|
* Copy right 2019-2029 杭州牛之云科技有限公司, 保留所有权利。
|
||||||
|
* ----------------------------------------------
|
||||||
|
* 官方网址: https://www.niushop.com
|
||||||
|
* =========================================================
|
||||||
|
*/""".strip(),
|
||||||
|
# 格式2:带有额外空行的注释格式
|
||||||
|
r"""
|
||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Niushop商城系统 - 团队十年电商经验汇集巨献!
|
||||||
|
* =========================================================
|
||||||
|
* Copy right 2019-2029 杭州牛之云科技有限公司, 保留所有权利。
|
||||||
|
* ----------------------------------------------
|
||||||
|
* 官方网址: https://www.niushop.com
|
||||||
|
|
||||||
|
* =========================================================
|
||||||
|
*/""".strip(),
|
||||||
|
]
|
||||||
|
|
||||||
|
NEW_COMMENT = r"""
|
||||||
|
<?php
|
||||||
|
""".strip()
|
||||||
|
|
||||||
|
# 定义要处理的文件类型
|
||||||
|
FILE_TYPES = [".php", ".js", ".css", ".html", ".vue", ".ts", ".tsx", ".jsx", ".scss", ".less"]
|
||||||
|
|
||||||
|
def replace_comments(file_path):
|
||||||
|
"""
|
||||||
|
替换文件中的注释
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
# 读取文件内容
|
||||||
|
with open(file_path, 'r', encoding='utf-8') as f:
|
||||||
|
content = f.read()
|
||||||
|
|
||||||
|
# 检查文件是否包含任何旧注释
|
||||||
|
has_old_comment = False
|
||||||
|
for old_comment in OLD_COMMENTS:
|
||||||
|
if old_comment in content:
|
||||||
|
has_old_comment = True
|
||||||
|
break
|
||||||
|
|
||||||
|
if has_old_comment:
|
||||||
|
# 替换所有旧注释
|
||||||
|
new_content = content
|
||||||
|
for old_comment in OLD_COMMENTS:
|
||||||
|
if old_comment in new_content:
|
||||||
|
new_content = new_content.replace(old_comment, NEW_COMMENT)
|
||||||
|
|
||||||
|
# 写回文件
|
||||||
|
with open(file_path, 'w', encoding='utf-8') as f:
|
||||||
|
f.write(new_content)
|
||||||
|
|
||||||
|
print(f"✓ 已处理: {file_path}")
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
except UnicodeDecodeError:
|
||||||
|
# 跳过二进制文件
|
||||||
|
print(f"✗ 跳过二进制文件: {file_path}")
|
||||||
|
return False
|
||||||
|
except PermissionError:
|
||||||
|
# 跳过没有权限的文件
|
||||||
|
print(f"✗ 权限不足: {file_path}")
|
||||||
|
return False
|
||||||
|
except Exception as e:
|
||||||
|
# 处理其他异常
|
||||||
|
print(f"✗ 处理失败 {file_path}: {str(e)}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
def main():
|
||||||
|
"""
|
||||||
|
主函数
|
||||||
|
"""
|
||||||
|
parser = argparse.ArgumentParser(description="替换Niushop商城系统的注释")
|
||||||
|
parser.add_argument("--path", type=str, default=".", help="要遍历的目录路径")
|
||||||
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
root_path = args.path
|
||||||
|
total_files = 0
|
||||||
|
processed_files = 0
|
||||||
|
|
||||||
|
print(f"开始遍历目录: {root_path}")
|
||||||
|
print(f"将处理的文件类型: {', '.join(FILE_TYPES)}")
|
||||||
|
print("=" * 60)
|
||||||
|
|
||||||
|
# 遍历所有文件
|
||||||
|
for root, dirs, files in os.walk(root_path):
|
||||||
|
# 跳过某些目录(如.git、vendor、node_modules等)
|
||||||
|
dirs[:] = [d for d in dirs if d not in ['.git', 'vendor', 'node_modules', 'runtime', 'upload', 'public', 'static']]
|
||||||
|
|
||||||
|
for file in files:
|
||||||
|
# 检查文件类型
|
||||||
|
if any(file.endswith(ext) for ext in FILE_TYPES):
|
||||||
|
total_files += 1
|
||||||
|
file_path = os.path.join(root, file)
|
||||||
|
if replace_comments(file_path):
|
||||||
|
processed_files += 1
|
||||||
|
|
||||||
|
print("=" * 60)
|
||||||
|
print(f"处理完成!")
|
||||||
|
print(f"总文件数: {total_files}")
|
||||||
|
print(f"已处理文件数: {processed_files}")
|
||||||
|
print(f"替换率: {processed_files / total_files * 100:.2f}%" if total_files > 0 else "未找到匹配的文件")
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
22
scripts/generate_key_pair/main.js
Normal file
22
scripts/generate_key_pair/main.js
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
const crypto = require('crypto');
|
||||||
|
// 生成密钥对
|
||||||
|
const { publicKey, privateKey } = crypto.generateKeyPairSync('rsa', {
|
||||||
|
modulusLength: 3072, // 密钥长度,不少于3072
|
||||||
|
publicKeyEncoding: {
|
||||||
|
type: 'spki', // 公钥编码格式
|
||||||
|
format: 'pem' // 公钥输出格式
|
||||||
|
},
|
||||||
|
privateKeyEncoding: {
|
||||||
|
type: 'pkcs8', // 私钥编码格式
|
||||||
|
format: 'pem' // 私钥输出格式
|
||||||
|
}
|
||||||
|
});
|
||||||
|
console.info('生成的公钥:');
|
||||||
|
console.info(publicKey);
|
||||||
|
console.info('生成的私钥:');
|
||||||
|
console.info(privateKey);
|
||||||
|
|
||||||
|
// 保存密钥对到文件
|
||||||
|
const fs = require('fs');
|
||||||
|
fs.writeFileSync('merchant_public_key.pem', publicKey);
|
||||||
|
fs.writeFileSync('merchant_private_key.pem', privateKey);
|
||||||
@@ -1,25 +1,22 @@
|
|||||||
APP_DEBUG = true
|
APP_DEBUG = true
|
||||||
APP_TRACE = true
|
APP_TRACE = true
|
||||||
|
|
||||||
[APP]
|
[APP]
|
||||||
DEFAULT_TIMEZONE = Asia/Shanghai
|
DEFAULT_TIMEZONE = Asia/Shanghai
|
||||||
ENV_MODE = development
|
|
||||||
|
|
||||||
[LANG]
|
[LANG]
|
||||||
default_lang = zh-cn
|
default_lang = zh-cn
|
||||||
|
|
||||||
[DATABASE]
|
[DATABASE]
|
||||||
TYPE = mysql
|
TYPE = mysql
|
||||||
HOSTNAME = 127.0.0.1
|
HOSTNAME = db
|
||||||
DATABASE = shop_mallnew_dev
|
DATABASE = shop_mallnew
|
||||||
USERNAME = root
|
USERNAME = shop_mallnew
|
||||||
PASSWORD = root
|
PASSWORD = shop_mallnew
|
||||||
HOSTPORT = 3306
|
HOSTPORT = 3306
|
||||||
CHARSET = utf8
|
CHARSET = utf8
|
||||||
DEBUG = true
|
DEBUG = true
|
||||||
|
[RRDATABASE]
|
||||||
|
HOSTNAME = host.docker.internal
|
||||||
[redis]
|
[redis]
|
||||||
HOST = 127.0.0.1
|
HOST = redis
|
||||||
PORT = 6379
|
PORT = 6379
|
||||||
PASSWORD = ''
|
PASSWORD = 'luckyshop123!@#'
|
||||||
EXPIRY = 604800
|
EXPIRY = 604800
|
||||||
@@ -6,7 +6,7 @@ DEFAULT_TIMEZONE = Asia/Shanghai
|
|||||||
default_lang = zh-cn
|
default_lang = zh-cn
|
||||||
[DATABASE]
|
[DATABASE]
|
||||||
TYPE = mysql
|
TYPE = mysql
|
||||||
HOSTNAME = newshop_mysql
|
HOSTNAME = db
|
||||||
DATABASE = shop_dev
|
DATABASE = shop_dev
|
||||||
USERNAME = shop_mallnew
|
USERNAME = shop_mallnew
|
||||||
PASSWORD = shop_mallnew
|
PASSWORD = shop_mallnew
|
||||||
@@ -14,9 +14,9 @@ HOSTPORT = 3306
|
|||||||
CHARSET = utf8
|
CHARSET = utf8
|
||||||
DEBUG = true
|
DEBUG = true
|
||||||
[RRDATABASE]
|
[RRDATABASE]
|
||||||
HOSTNAME = 192.168.2.64
|
HOSTNAME = redis
|
||||||
[redis]
|
[redis]
|
||||||
HOST = newshop_redis
|
HOST = redis
|
||||||
PORT = 6379
|
PORT = 6379
|
||||||
PASSWORD = 'luckyshop123!@#'
|
PASSWORD = 'luckyshop123!@#'
|
||||||
EXPIRY = 604800
|
EXPIRY = 604800
|
||||||
313
src/addon/aikefu/api/controller/Kefu.php
Normal file
313
src/addon/aikefu/api/controller/Kefu.php
Normal file
@@ -0,0 +1,313 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace addon\aikefu\api\controller;
|
||||||
|
|
||||||
|
use addon\aikefu\model\Config as KefuConfigModel;
|
||||||
|
use addon\aikefu\model\Conversation as KefuConversationModel;
|
||||||
|
use addon\aikefu\model\Message as KefuMessageModel;
|
||||||
|
use app\api\controller\BaseApi;
|
||||||
|
|
||||||
|
|
||||||
|
class Kefu extends BaseApi
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* 封装curl请求方法
|
||||||
|
* @param string $url 请求URL
|
||||||
|
* @param string $method 请求方法
|
||||||
|
* @param array $data 请求数据
|
||||||
|
* @param array $headers 请求头
|
||||||
|
* @return string 响应内容
|
||||||
|
*/
|
||||||
|
private function curlRequest($url, $method = 'GET', $data = [], $headers = [])
|
||||||
|
{
|
||||||
|
$ch = curl_init();
|
||||||
|
|
||||||
|
// 设置URL
|
||||||
|
curl_setopt($ch, CURLOPT_URL, $url);
|
||||||
|
|
||||||
|
// 设置请求方法
|
||||||
|
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, $method);
|
||||||
|
|
||||||
|
// 设置POST数据
|
||||||
|
if ($method === 'POST' && !empty($data)) {
|
||||||
|
curl_setopt($ch, CURLOPT_POSTFIELDS, is_array($data) ? json_encode($data) : $data);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 设置请求头
|
||||||
|
if (!empty($headers)) {
|
||||||
|
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 设置返回值
|
||||||
|
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
||||||
|
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
|
||||||
|
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
|
||||||
|
curl_setopt($ch, CURLOPT_TIMEOUT, 30);
|
||||||
|
|
||||||
|
// 执行请求
|
||||||
|
$response = curl_exec($ch);
|
||||||
|
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||||||
|
|
||||||
|
// 关闭连接
|
||||||
|
curl_close($ch);
|
||||||
|
|
||||||
|
if ($response === false) {
|
||||||
|
throw new \Exception('Curl请求失败');
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($httpCode >= 400) {
|
||||||
|
throw new \Exception('HTTP请求失败,状态码:' . $httpCode);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $response;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* 为事件调用初始化属性
|
||||||
|
* @param array $data 事件数据
|
||||||
|
*/
|
||||||
|
public function initializeForEvent($data)
|
||||||
|
{
|
||||||
|
if (!empty($data['site_id'])) {
|
||||||
|
$this->site_id = $data['site_id'] ?? 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!empty($data['member_id'])) {
|
||||||
|
$this->member_id = $data['member_id'] ?? 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!empty($data['token'])) {
|
||||||
|
$this->token = $data['token'] ?? '';
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->params = [
|
||||||
|
'message' => $data['message'] ?? '',
|
||||||
|
'user_id' => $data['user_id'] ?? '',
|
||||||
|
'conversation_id' => $data['conversation_id'] ?? '',
|
||||||
|
'stream' => $data['stream'] ?? false,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 智能客服聊天接口
|
||||||
|
* @return \think\response\Json
|
||||||
|
*/
|
||||||
|
public function chat()
|
||||||
|
{
|
||||||
|
// 获取请求参数
|
||||||
|
$message = $this->params['message'] ?? '';
|
||||||
|
$user_id = $this->params['user_id'] ?? $this->member_id;
|
||||||
|
$conversation_id = $this->params['conversation_id'] ?? '';
|
||||||
|
$stream = $this->params['stream'] ?? false;
|
||||||
|
|
||||||
|
// 验证参数
|
||||||
|
if (empty($message)) {
|
||||||
|
return $this->response($this->error('请输入消息内容'));
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
// 获取智能客服配置
|
||||||
|
$kefu_config_model = new KefuConfigModel();
|
||||||
|
$config_info = $kefu_config_model->getConfig($this->site_id)['data']['value'] ?? [];
|
||||||
|
|
||||||
|
if (empty($config_info) || $config_info['status'] != 1) {
|
||||||
|
return $this->response($this->error('智能客服暂未启用'));
|
||||||
|
}
|
||||||
|
|
||||||
|
$config = $config_info;
|
||||||
|
$apiKey = $config['api_key'];
|
||||||
|
$baseUrl = $config['base_url'];
|
||||||
|
$chatEndpoint = $config['chat_endpoint'];
|
||||||
|
|
||||||
|
// 构建请求数据
|
||||||
|
$requestData = [
|
||||||
|
'inputs' => [],
|
||||||
|
'query' => $message,
|
||||||
|
'response_mode' => $stream ? 'streaming' : 'blocking',
|
||||||
|
'user' => $user_id,
|
||||||
|
];
|
||||||
|
|
||||||
|
// 如果有会话ID,添加到请求中
|
||||||
|
if (!empty($conversation_id)) {
|
||||||
|
$requestData['conversation_id'] = $conversation_id;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 构建请求头
|
||||||
|
$headers = [
|
||||||
|
'Authorization: Bearer ' . $apiKey,
|
||||||
|
'Content-Type: application/json',
|
||||||
|
];
|
||||||
|
|
||||||
|
// 发送请求到Dify API
|
||||||
|
$url = $baseUrl . $chatEndpoint;
|
||||||
|
$response = $this->curlRequest($url, 'POST', $requestData, $headers);
|
||||||
|
|
||||||
|
// 解析响应
|
||||||
|
$result = json_decode($response, true);
|
||||||
|
|
||||||
|
if (json_last_error() !== JSON_ERROR_NONE) {
|
||||||
|
return $this->response($this->error('解析响应失败'));
|
||||||
|
}
|
||||||
|
|
||||||
|
// 保存消息记录
|
||||||
|
$kefu_message_model = new KefuMessageModel();
|
||||||
|
$kefu_conversation_model = new KefuConversationModel();
|
||||||
|
|
||||||
|
// 保存用户消息
|
||||||
|
$kefu_message_model->addMessage([
|
||||||
|
'site_id' => $this->site_id,
|
||||||
|
'user_id' => $user_id,
|
||||||
|
'conversation_id' => $result['conversation_id'] ?? $conversation_id,
|
||||||
|
'message_id' => $result['message_id'] ?? '',
|
||||||
|
'role' => 'user',
|
||||||
|
'content' => $message,
|
||||||
|
]);
|
||||||
|
|
||||||
|
// 保存机器人回复
|
||||||
|
$kefu_message_model->addMessage([
|
||||||
|
'site_id' => $this->site_id,
|
||||||
|
'user_id' => $user_id,
|
||||||
|
'conversation_id' => $result['conversation_id'] ?? $conversation_id,
|
||||||
|
'message_id' => $result['id'] ?? '',
|
||||||
|
'role' => 'assistant',
|
||||||
|
'content' => $result['answer'] ?? '',
|
||||||
|
]);
|
||||||
|
|
||||||
|
// 更新会话状态或创建新会话
|
||||||
|
$conversation_info = $kefu_conversation_model->getConversationInfo([
|
||||||
|
['site_id', '=', $this->site_id],
|
||||||
|
['conversation_id', '=', $result['conversation_id'] ?? $conversation_id],
|
||||||
|
]);
|
||||||
|
|
||||||
|
if (empty($conversation_info['data'])) {
|
||||||
|
// 创建新会话
|
||||||
|
$kefu_conversation_model->addConversation([
|
||||||
|
'site_id' => $this->site_id,
|
||||||
|
'user_id' => $user_id,
|
||||||
|
'conversation_id' => $result['conversation_id'] ?? '',
|
||||||
|
'name' => '智能客服会话',
|
||||||
|
]);
|
||||||
|
} else {
|
||||||
|
// 更新会话状态
|
||||||
|
$kefu_conversation_model->updateConversation([
|
||||||
|
'status' => 1,
|
||||||
|
], [
|
||||||
|
['id', '=', $conversation_info['data']['id']],
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 返回成功响应
|
||||||
|
return $this->response($this->success([
|
||||||
|
'conversation_id' => $result['conversation_id'] ?? '',
|
||||||
|
'reply' => $result['answer'] ?? '',
|
||||||
|
'message_id' => $result['message_id'] ?? '',
|
||||||
|
'finish_reason' => $result['finish_reason'] ?? '',
|
||||||
|
'usage' => $result['usage'] ?? [],
|
||||||
|
]));
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
return $this->response($this->error('请求失败:' . $e->getMessage()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取会话历史
|
||||||
|
* @return \think\response\Json
|
||||||
|
*/
|
||||||
|
public function getHistory()
|
||||||
|
{
|
||||||
|
// 获取请求参数
|
||||||
|
$conversation_id = $this->params['conversation_id'] ?? '';
|
||||||
|
$user_id = $this->params['user_id'] ?? $this->member_id;
|
||||||
|
$limit = $this->params['limit'] ?? 20;
|
||||||
|
$offset = $this->params['offset'] ?? 0;
|
||||||
|
|
||||||
|
// 验证参数
|
||||||
|
if (empty($conversation_id)) {
|
||||||
|
return $this->response($this->error('会话ID不能为空'));
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
// 获取会话历史记录
|
||||||
|
$kefu_message_model = new KefuMessageModel();
|
||||||
|
$message_list = $kefu_message_model->getMessageList([
|
||||||
|
['site_id', '=', $this->site_id],
|
||||||
|
['user_id', '=', $user_id],
|
||||||
|
['conversation_id', '=', $conversation_id],
|
||||||
|
], 'id, role, content, create_time', 'create_time asc', $limit, $offset);
|
||||||
|
|
||||||
|
// 返回成功响应
|
||||||
|
return $this->response($this->success([
|
||||||
|
'messages' => $message_list['data'] ?? [],
|
||||||
|
'total' => $message_list['total'] ?? 0,
|
||||||
|
'limit' => $limit,
|
||||||
|
'offset' => $offset,
|
||||||
|
]));
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
return $this->response($this->error('请求失败:' . $e->getMessage()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建新会话
|
||||||
|
* @return \think\response\Json
|
||||||
|
*/
|
||||||
|
public function createConversation()
|
||||||
|
{
|
||||||
|
// 获取请求参数
|
||||||
|
$user_id = $this->params['user_id'] ?? $this->member_id;
|
||||||
|
|
||||||
|
try {
|
||||||
|
// 获取智能客服配置
|
||||||
|
$kefu_config_model = new KefuConfigModel();
|
||||||
|
$config_info = $kefu_config_model->getConfig($this->site_id)['data']['value'] ?? [];
|
||||||
|
|
||||||
|
if (empty($config_info) || $config_info['status'] != 1) {
|
||||||
|
return $this->response($this->error('智能客服暂未启用'));
|
||||||
|
}
|
||||||
|
|
||||||
|
$config = $config_info;
|
||||||
|
$apiKey = $config['api_key'];
|
||||||
|
$baseUrl = $config['base_url'];
|
||||||
|
|
||||||
|
// 构建请求数据
|
||||||
|
$requestData = [
|
||||||
|
'name' => '智能客服会话',
|
||||||
|
'user' => $user_id,
|
||||||
|
];
|
||||||
|
|
||||||
|
// 构建请求头
|
||||||
|
$headers = [
|
||||||
|
'Authorization: Bearer ' . $apiKey,
|
||||||
|
'Content-Type: application/json',
|
||||||
|
];
|
||||||
|
|
||||||
|
// 发送请求到Dify API
|
||||||
|
$url = $baseUrl . '/conversations';
|
||||||
|
$response = $this->curlRequest($url, 'POST', $requestData, $headers);
|
||||||
|
|
||||||
|
// 解析响应
|
||||||
|
$result = json_decode($response, true);
|
||||||
|
|
||||||
|
if (json_last_error() !== JSON_ERROR_NONE) {
|
||||||
|
return $this->response($this->error('解析响应失败'));
|
||||||
|
}
|
||||||
|
|
||||||
|
// 保存会话记录
|
||||||
|
$kefu_conversation_model = new KefuConversationModel();
|
||||||
|
$kefu_conversation_model->addConversation([
|
||||||
|
'site_id' => $this->site_id,
|
||||||
|
'user_id' => $user_id,
|
||||||
|
'conversation_id' => $result['id'] ?? '',
|
||||||
|
'name' => $result['name'] ?? '智能客服会话',
|
||||||
|
]);
|
||||||
|
|
||||||
|
// 返回成功响应
|
||||||
|
return $this->response($this->success([
|
||||||
|
'conversation_id' => $result['id'] ?? '',
|
||||||
|
'name' => $result['name'] ?? '',
|
||||||
|
'created_at' => $result['created_at'] ?? '',
|
||||||
|
]));
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
return $this->response($this->error('请求失败:' . $e->getMessage()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
36
src/addon/aikefu/config/event.php
Normal file
36
src/addon/aikefu/config/event.php
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* 智能客服扩展事件配置
|
||||||
|
*/
|
||||||
|
return [
|
||||||
|
'bind' => [
|
||||||
|
|
||||||
|
],
|
||||||
|
|
||||||
|
'listen' => [
|
||||||
|
'KefuChat' => [
|
||||||
|
'addon\aikefu\event\KefuChat'
|
||||||
|
],
|
||||||
|
'KefuCreateConversation' => [
|
||||||
|
'addon\aikefu\event\KefuCreateConversation'
|
||||||
|
],
|
||||||
|
'KefuGetHistory' => [
|
||||||
|
'addon\aikefu\event\KefuGetHistory'
|
||||||
|
],
|
||||||
|
'KefuClearConversation' => [
|
||||||
|
'addon\aikefu\event\KefuClearConversation'
|
||||||
|
],
|
||||||
|
'KefuHealthCheck' => [
|
||||||
|
'addon\aikefu\event\KefuHealthCheck'
|
||||||
|
],
|
||||||
|
'KefuChatStream' => [
|
||||||
|
'addon\aikefu\event\KefuChatStream'
|
||||||
|
],
|
||||||
|
'KefuGetInfo' => [
|
||||||
|
'addon\aikefu\event\KefuGetInfo'
|
||||||
|
],
|
||||||
|
],
|
||||||
|
|
||||||
|
'subscribe' => [
|
||||||
|
],
|
||||||
|
];
|
||||||
13
src/addon/aikefu/config/info.php
Normal file
13
src/addon/aikefu/config/info.php
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
return [
|
||||||
|
'name' => 'aikefu',
|
||||||
|
'title' => '智能客服',
|
||||||
|
'description' => '基于Dify的智能客服系统',
|
||||||
|
'type' => 'system', //插件类型 system :系统插件(自动安装), business:业务插件 promotion:扩展营销插件 tool:工具插件
|
||||||
|
'status' => 1,
|
||||||
|
'author' => '',
|
||||||
|
'version' => '1.0.0',
|
||||||
|
'version_no' => '525231212001',
|
||||||
|
'content' => '',
|
||||||
|
];
|
||||||
42
src/addon/aikefu/data/install.sql
Normal file
42
src/addon/aikefu/data/install.sql
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
-- 智能客服插件安装脚本
|
||||||
|
-- 1. 智能客服插件使用系统配置表存储配置信息,无需创建独立数据表
|
||||||
|
-- 2. 会话和消息数据存储在独立数据表中
|
||||||
|
|
||||||
|
-- 创建智能客服会话表
|
||||||
|
CREATE TABLE IF NOT EXISTS `lucky_aikefu_conversation` (
|
||||||
|
`id` int(11) NOT NULL AUTO_INCREMENT,
|
||||||
|
`site_id` int(11) NOT NULL COMMENT '站点ID',
|
||||||
|
`user_id` varchar(50) NOT NULL COMMENT '用户ID',
|
||||||
|
`conversation_id` varchar(100) NOT NULL COMMENT 'Dify会话ID',
|
||||||
|
`name` varchar(255) NOT NULL COMMENT '会话名称',
|
||||||
|
`status` tinyint(1) NOT NULL DEFAULT 1 COMMENT '状态:1活跃,0结束',
|
||||||
|
`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
|
||||||
|
`update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
|
||||||
|
PRIMARY KEY (`id`),
|
||||||
|
KEY `site_id` (`site_id`),
|
||||||
|
KEY `user_id` (`user_id`),
|
||||||
|
KEY `conversation_id` (`conversation_id`)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4_unicode_ci COMMENT='智能客服会话表';
|
||||||
|
|
||||||
|
-- 创建智能客服消息表
|
||||||
|
CREATE TABLE IF NOT EXISTS `lucky_aikefu_message` (
|
||||||
|
`id` int(11) NOT NULL AUTO_INCREMENT,
|
||||||
|
`site_id` int(11) NOT NULL COMMENT '站点ID',
|
||||||
|
`user_id` varchar(50) NOT NULL COMMENT '用户ID',
|
||||||
|
`conversation_id` varchar(100) NOT NULL COMMENT '会话ID',
|
||||||
|
`message_id` varchar(100) NOT NULL COMMENT '消息ID',
|
||||||
|
`role` varchar(20) NOT NULL COMMENT '角色:user用户,assistant助手',
|
||||||
|
`content` text NOT NULL COMMENT '消息内容',
|
||||||
|
`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
|
||||||
|
PRIMARY KEY (`id`),
|
||||||
|
KEY `site_id` (`site_id`),
|
||||||
|
KEY `user_id` (`user_id`),
|
||||||
|
KEY `conversation_id` (`conversation_id`),
|
||||||
|
KEY `message_id` (`message_id`)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4_unicode_ci COMMENT='智能客服消息表';
|
||||||
|
|
||||||
|
-- 修改表字符集,utf8mb4_unicode_ci, 兼容emoji表情
|
||||||
|
|
||||||
|
ALTER TABLE lucky_aikefu_message MODIFY content TEXT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
|
||||||
|
ALTER TABLE lucky_aikefu_conversation CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
|
||||||
|
ALTER TABLE lucky_aikefu_message CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
|
||||||
4
src/addon/aikefu/data/uninstall.sql
Normal file
4
src/addon/aikefu/data/uninstall.sql
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
-- 智能客服插件卸载脚本
|
||||||
|
-- 删除智能客服相关表(配置信息存储在系统配置表中,无需单独删除)
|
||||||
|
DROP TABLE IF EXISTS `lucky_aikefu_message`;
|
||||||
|
DROP TABLE IF EXISTS `lucky_aikefu_conversation`;
|
||||||
50
src/addon/aikefu/event/Install.php
Normal file
50
src/addon/aikefu/event/Install.php
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace addon\aikefu\event;
|
||||||
|
|
||||||
|
use app\model\system\Addon as AddonModel;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 智能客服插件安装
|
||||||
|
*/
|
||||||
|
class Install
|
||||||
|
{
|
||||||
|
public function handle()
|
||||||
|
{
|
||||||
|
$addon_model = new AddonModel();
|
||||||
|
$info = $addon_model->getAddonInfo(['name' => 'aikefu']);
|
||||||
|
|
||||||
|
if (empty($info['data'])) {
|
||||||
|
// 插件未安装,执行安装逻辑
|
||||||
|
$addon_model->addAddon([
|
||||||
|
'name' => 'aikefu',
|
||||||
|
'title' => '智能客服',
|
||||||
|
'description' => '基于Dify的智能客服系统',
|
||||||
|
'author' => 'admin',
|
||||||
|
'version' => '1.0.0',
|
||||||
|
'scene' => 'web',
|
||||||
|
'state' => 0,
|
||||||
|
'category' => 'business',
|
||||||
|
'need_install' => 1,
|
||||||
|
'need_cache' => 1,
|
||||||
|
'create_time' => time(),
|
||||||
|
'update_time' => time()
|
||||||
|
]);
|
||||||
|
} else {
|
||||||
|
// 插件已存在,更新插件信息
|
||||||
|
$addon_model->updateAddon([
|
||||||
|
'title' => '智能客服',
|
||||||
|
'description' => '基于Dify的智能客服系统',
|
||||||
|
'author' => 'admin',
|
||||||
|
'version' => '1.0.0',
|
||||||
|
'scene' => 'web',
|
||||||
|
'category' => 'business',
|
||||||
|
'need_install' => 1,
|
||||||
|
'need_cache' => 1,
|
||||||
|
'update_time' => time()
|
||||||
|
], ['name' => 'aikefu']);
|
||||||
|
}
|
||||||
|
|
||||||
|
return success(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
39
src/addon/aikefu/event/KefuChat.php
Normal file
39
src/addon/aikefu/event/KefuChat.php
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace addon\aikefu\event;
|
||||||
|
|
||||||
|
use addon\aikefu\api\controller\Kefu as KefuApi;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 客服聊天
|
||||||
|
*/
|
||||||
|
class KefuChat
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* 处理智能客服聊天事件
|
||||||
|
* @param array $data 事件数据
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public function handle($data)
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
// 创建addon的KefuApi实例
|
||||||
|
$kefu_api = new KefuApi();
|
||||||
|
|
||||||
|
// 调用初始化方法设置属性
|
||||||
|
$kefu_api->initializeForEvent($data);
|
||||||
|
|
||||||
|
// 调用addon的chat方法
|
||||||
|
$response = $kefu_api->chat();
|
||||||
|
|
||||||
|
// 返回响应数据
|
||||||
|
return json_decode($response->getContent(), true);
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
return [
|
||||||
|
'code' => -1,
|
||||||
|
'message' => '聊天失败:' . $e->getMessage(),
|
||||||
|
'data' => []
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
235
src/addon/aikefu/event/KefuChatStream.php
Normal file
235
src/addon/aikefu/event/KefuChatStream.php
Normal file
@@ -0,0 +1,235 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace addon\aikefu\event;
|
||||||
|
|
||||||
|
use addon\aikefu\model\Config as KefuConfigModel;
|
||||||
|
use addon\aikefu\model\Conversation as KefuConversationModel;
|
||||||
|
use addon\aikefu\model\Message as KefuMessageModel;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 智能客服流式聊天
|
||||||
|
*/
|
||||||
|
class KefuChatStream
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* 处理智能客服流式聊天事件
|
||||||
|
* @param array $data 事件数据
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public function handle($data)
|
||||||
|
{
|
||||||
|
$message = $data['message'] ?? '';
|
||||||
|
$user_id = $data['user_id'] ?? '';
|
||||||
|
$conversation_id = $data['conversation_id'] ?? '';
|
||||||
|
$site_id = $data['site_id'] ?? 0;
|
||||||
|
$request_id = $data['request_id'] ?? '';
|
||||||
|
|
||||||
|
try {
|
||||||
|
// 验证参数
|
||||||
|
if (empty($message)) {
|
||||||
|
return [
|
||||||
|
[
|
||||||
|
'type' => 'error',
|
||||||
|
'message' => '消息内容不能为空'
|
||||||
|
]
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取智能客服配置
|
||||||
|
$kefu_config_model = new KefuConfigModel();
|
||||||
|
$config_info = $kefu_config_model->getConfig($site_id)['data']['value'] ?? [];
|
||||||
|
|
||||||
|
if (empty($config_info) || $config_info['status'] != 1) {
|
||||||
|
return [
|
||||||
|
[
|
||||||
|
'type' => 'error',
|
||||||
|
'message' => '智能客服暂未启用'
|
||||||
|
]
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
$config = $config_info;
|
||||||
|
$apiKey = $config['api_key'];
|
||||||
|
$baseUrl = $config['base_url'];
|
||||||
|
$chatEndpoint = $config['chat_endpoint'];
|
||||||
|
|
||||||
|
// 构建请求数据
|
||||||
|
$requestData = [
|
||||||
|
'inputs' => [],
|
||||||
|
'query' => $message,
|
||||||
|
'response_mode' => 'streaming',
|
||||||
|
'user' => $user_id,
|
||||||
|
];
|
||||||
|
|
||||||
|
if (!empty($conversation_id)) {
|
||||||
|
$requestData['conversation_id'] = $conversation_id;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 构建请求头
|
||||||
|
$headers = [
|
||||||
|
'Authorization: Bearer ' . $apiKey,
|
||||||
|
'Content-Type: application/json',
|
||||||
|
'Accept: text/event-stream',
|
||||||
|
];
|
||||||
|
|
||||||
|
// 发送流式请求到Dify API
|
||||||
|
$url = $baseUrl . $chatEndpoint;
|
||||||
|
$result = $this->executeStreamRequest($url, $requestData, $headers);
|
||||||
|
|
||||||
|
return $result;
|
||||||
|
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
return [
|
||||||
|
[
|
||||||
|
'type' => 'error',
|
||||||
|
'message' => '请求失败:' . $e->getMessage()
|
||||||
|
]
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 执行流式请求
|
||||||
|
*/
|
||||||
|
private function executeStreamRequest($url, $requestData, $headers)
|
||||||
|
{
|
||||||
|
$ch = curl_init();
|
||||||
|
|
||||||
|
// 设置curl选项
|
||||||
|
curl_setopt($ch, CURLOPT_URL, $url);
|
||||||
|
curl_setopt($ch, CURLOPT_POST, true);
|
||||||
|
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($requestData));
|
||||||
|
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
|
||||||
|
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
||||||
|
curl_setopt($ch, CURLOPT_WRITEFUNCTION, [$this, 'streamCallback']);
|
||||||
|
curl_setopt($ch, CURLOPT_TIMEOUT, 120); // 设置较长的超时时间
|
||||||
|
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
|
||||||
|
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
|
||||||
|
|
||||||
|
// 执行请求
|
||||||
|
$result = [];
|
||||||
|
$this->stream_buffer = '';
|
||||||
|
$this->conversation_id = '';
|
||||||
|
$this->current_message_id = '';
|
||||||
|
|
||||||
|
$response = curl_exec($ch);
|
||||||
|
$http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||||||
|
$error = curl_error($ch);
|
||||||
|
curl_close($ch);
|
||||||
|
|
||||||
|
if ($response === false || !empty($error)) {
|
||||||
|
return [
|
||||||
|
[
|
||||||
|
'type' => 'error',
|
||||||
|
'message' => '请求失败:' . $error
|
||||||
|
]
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($http_code >= 400) {
|
||||||
|
return [
|
||||||
|
[
|
||||||
|
'type' => 'error',
|
||||||
|
'message' => '服务端错误,HTTP状态码:' . $http_code
|
||||||
|
]
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->parseStreamResponse($this->stream_buffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 流式回调函数
|
||||||
|
*/
|
||||||
|
private function streamCallback($ch, $data)
|
||||||
|
{
|
||||||
|
$this->stream_buffer .= $data;
|
||||||
|
return strlen($data);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 解析流式响应
|
||||||
|
*/
|
||||||
|
private function parseStreamResponse($response)
|
||||||
|
{
|
||||||
|
$result = [];
|
||||||
|
$lines = explode("\n", $response);
|
||||||
|
$conversation_id = '';
|
||||||
|
$message_id = '';
|
||||||
|
$buffer = '';
|
||||||
|
|
||||||
|
foreach ($lines as $line) {
|
||||||
|
$line = trim($line);
|
||||||
|
|
||||||
|
if (empty($line)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (strpos($line, 'data: ') === 0) {
|
||||||
|
$data_str = substr($line, 6);
|
||||||
|
|
||||||
|
if ($data_str === '[DONE]') {
|
||||||
|
// 流结束,发送完成事件
|
||||||
|
$result[] = [
|
||||||
|
'type' => 'complete',
|
||||||
|
'conversation_id' => $conversation_id,
|
||||||
|
'message_id' => $message_id,
|
||||||
|
'content' => $buffer,
|
||||||
|
'finished' => true
|
||||||
|
];
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
$data = json_decode($data_str, true);
|
||||||
|
if (json_last_error() === JSON_ERROR_NONE && isset($data)) {
|
||||||
|
// 提取会话ID和消息ID
|
||||||
|
if (isset($data['conversation_id'])) {
|
||||||
|
$conversation_id = $data['conversation_id'];
|
||||||
|
}
|
||||||
|
if (isset($data['message_id'])) {
|
||||||
|
$message_id = $data['message_id'];
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理内容块
|
||||||
|
if (isset($data['answer'])) {
|
||||||
|
$buffer .= $data['answer'];
|
||||||
|
$result[] = [
|
||||||
|
'type' => 'chunk',
|
||||||
|
'content' => $data['answer'],
|
||||||
|
'conversation_id' => $conversation_id,
|
||||||
|
'message_id' => $message_id,
|
||||||
|
'finished' => false
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查是否结束
|
||||||
|
if (isset($data['finish_reason']) && $data['finish_reason'] !== 'null') {
|
||||||
|
$result[] = [
|
||||||
|
'type' => 'complete',
|
||||||
|
'conversation_id' => $conversation_id,
|
||||||
|
'message_id' => $message_id,
|
||||||
|
'content' => $buffer,
|
||||||
|
'finish_reason' => $data['finish_reason'],
|
||||||
|
'usage' => $data['usage'] ?? [],
|
||||||
|
'finished' => true
|
||||||
|
];
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果没有收到[DONE]信号,确保发送完成事件
|
||||||
|
if (empty($result) || end($result)['type'] !== 'complete') {
|
||||||
|
$result[] = [
|
||||||
|
'type' => 'complete',
|
||||||
|
'conversation_id' => $conversation_id,
|
||||||
|
'message_id' => $message_id,
|
||||||
|
'content' => $buffer,
|
||||||
|
'finished' => true
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
return $result;
|
||||||
|
}
|
||||||
|
}
|
||||||
118
src/addon/aikefu/event/KefuClearConversation.php
Normal file
118
src/addon/aikefu/event/KefuClearConversation.php
Normal file
@@ -0,0 +1,118 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace addon\aikefu\event;
|
||||||
|
|
||||||
|
use addon\aikefu\model\Conversation as KefuConversationModel;
|
||||||
|
use addon\aikefu\model\Message as KefuMessageModel;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 清除客服会话历史
|
||||||
|
*/
|
||||||
|
class KefuClearConversation
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* 处理清除会话历史事件
|
||||||
|
* @param array $data 事件数据
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public function handle($data)
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
$conversation_id = $data['conversation_id'] ?? '';
|
||||||
|
$user_id = $data['user_id'] ?? '';
|
||||||
|
$site_id = $data['site_id'] ?? 0;
|
||||||
|
|
||||||
|
// 验证参数
|
||||||
|
if (empty($conversation_id) && empty($user_id)) {
|
||||||
|
return [
|
||||||
|
'code' => -1,
|
||||||
|
'message' => '会话ID或用户ID不能为空',
|
||||||
|
'data' => []
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
$conversation_model = new KefuConversationModel();
|
||||||
|
$message_model = new KefuMessageModel();
|
||||||
|
|
||||||
|
$deleted_messages = 0;
|
||||||
|
$deleted_conversations = 0;
|
||||||
|
|
||||||
|
if (!empty($conversation_id)) {
|
||||||
|
// 删除指定会话的消息和会话记录
|
||||||
|
|
||||||
|
// 先删除该会话的所有消息
|
||||||
|
$message_condition = [
|
||||||
|
['site_id', '=', $site_id],
|
||||||
|
['conversation_id', '=', $conversation_id]
|
||||||
|
];
|
||||||
|
|
||||||
|
$message_result = $message_model->deleteMessage($message_condition);
|
||||||
|
if ($message_result['code'] >= 0) {
|
||||||
|
$deleted_messages = $message_result['data']['result'] ?? 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 再删除会话记录
|
||||||
|
$conversation_condition = [
|
||||||
|
['site_id', '=', $site_id],
|
||||||
|
['conversation_id', '=', $conversation_id]
|
||||||
|
];
|
||||||
|
|
||||||
|
$conversation_result = $conversation_model->deleteConversation($conversation_condition);
|
||||||
|
if ($conversation_result['code'] >= 0) {
|
||||||
|
$deleted_conversations = $conversation_result['data']['result'] ?? 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
} else if (!empty($user_id)) {
|
||||||
|
// 删除指定用户的所有会话和消息
|
||||||
|
|
||||||
|
// 先获取该用户的所有会话ID
|
||||||
|
$conversation_list = $conversation_model->getConversationList([
|
||||||
|
['site_id', '=', $site_id],
|
||||||
|
['user_id', '=', $user_id]
|
||||||
|
], 'conversation_id');
|
||||||
|
|
||||||
|
$conversation_ids = array_column($conversation_list['data'], 'conversation_id');
|
||||||
|
|
||||||
|
if (!empty($conversation_ids)) {
|
||||||
|
// 删除所有会话的消息
|
||||||
|
$message_condition = [
|
||||||
|
['site_id', '=', $site_id],
|
||||||
|
['conversation_id', 'in', $conversation_ids]
|
||||||
|
];
|
||||||
|
|
||||||
|
$message_result = $message_model->deleteMessage($message_condition);
|
||||||
|
if ($message_result['code'] >= 0) {
|
||||||
|
$deleted_messages = $message_result['data']['result'] ?? 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 删除所有会话记录
|
||||||
|
$conversation_condition = [
|
||||||
|
['site_id', '=', $site_id],
|
||||||
|
['user_id', '=', $user_id]
|
||||||
|
];
|
||||||
|
|
||||||
|
$conversation_result = $conversation_model->deleteConversation($conversation_condition);
|
||||||
|
if ($conversation_result['code'] >= 0) {
|
||||||
|
$deleted_conversations = $conversation_result['data']['result'] ?? 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return [
|
||||||
|
'code' => 0,
|
||||||
|
'message' => '清除成功',
|
||||||
|
'data' => [
|
||||||
|
'deleted_messages' => $deleted_messages,
|
||||||
|
'deleted_conversations' => $deleted_conversations
|
||||||
|
]
|
||||||
|
];
|
||||||
|
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
return [
|
||||||
|
'code' => -1,
|
||||||
|
'message' => '清除失败:' . $e->getMessage(),
|
||||||
|
'data' => []
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
81
src/addon/aikefu/event/KefuCreateConversation.php
Normal file
81
src/addon/aikefu/event/KefuCreateConversation.php
Normal file
@@ -0,0 +1,81 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace addon\aikefu\event;
|
||||||
|
|
||||||
|
use addon\aikefu\api\controller\Kefu as KefuApi;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 处理智能客服创建会话事件
|
||||||
|
*/
|
||||||
|
class KefuCreateConversation
|
||||||
|
{
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 处理智能客服创建会话事件
|
||||||
|
* @param array $data 事件数据
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public function handle($data)
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
// 创建addon的KefuApi实例
|
||||||
|
$kefu_api = new KefuApi();
|
||||||
|
|
||||||
|
// 调用初始化方法设置属性
|
||||||
|
$kefu_api->initializeForEvent($data);
|
||||||
|
|
||||||
|
// 调用addon的createConversation方法
|
||||||
|
$response = $kefu_api->createConversation();
|
||||||
|
|
||||||
|
// 返回响应数据
|
||||||
|
return json_decode($response->getContent(), true);
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
return [
|
||||||
|
'code' => -1,
|
||||||
|
'message' => '创建会话失败:' . $e->getMessage(),
|
||||||
|
'data' => []
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 处理智能客服获取历史消息事件
|
||||||
|
* @param array $data 事件数据
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public function handleKefuGetHistory($data)
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
// 创建addon的KefuApi实例
|
||||||
|
$kefu_api = new KefuApi();
|
||||||
|
|
||||||
|
// 调用初始化方法设置属性
|
||||||
|
$kefu_api->initializeForEvent($data);
|
||||||
|
|
||||||
|
// 调用addon的getHistory方法
|
||||||
|
$response = $kefu_api->getHistory();
|
||||||
|
|
||||||
|
// 返回响应数据
|
||||||
|
return json_decode($response->getContent(), true);
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
return [
|
||||||
|
'code' => -1,
|
||||||
|
'message' => '获取历史消息失败:' . $e->getMessage(),
|
||||||
|
'data' => []
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 事件监听映射
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public function subscribe()
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'KefuChat' => 'handleKefuChat',
|
||||||
|
'KefuCreateConversation' => 'handleKefuCreateConversation',
|
||||||
|
'KefuGetHistory' => 'handleKefuGetHistory',
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
39
src/addon/aikefu/event/KefuGetHistory.php
Normal file
39
src/addon/aikefu/event/KefuGetHistory.php
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace addon\aikefu\event;
|
||||||
|
|
||||||
|
use addon\aikefu\api\controller\Kefu as KefuApi;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 处理智能客服获取历史消息事件
|
||||||
|
*/
|
||||||
|
class KefuGetHistory
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* 处理智能客服获取历史消息事件
|
||||||
|
* @param array $data 事件数据
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public function handle($data)
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
// 创建addon的KefuApi实例
|
||||||
|
$kefu_api = new KefuApi();
|
||||||
|
|
||||||
|
// 调用初始化方法设置属性
|
||||||
|
$kefu_api->initializeForEvent($data);
|
||||||
|
|
||||||
|
// 调用addon的getHistory方法
|
||||||
|
$response = $kefu_api->getHistory();
|
||||||
|
|
||||||
|
// 返回响应数据
|
||||||
|
return json_decode($response->getContent(), true);
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
return [
|
||||||
|
'code' => -1,
|
||||||
|
'message' => '获取历史消息失败:' . $e->getMessage(),
|
||||||
|
'data' => []
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
127
src/addon/aikefu/event/KefuGetInfo.php
Normal file
127
src/addon/aikefu/event/KefuGetInfo.php
Normal file
@@ -0,0 +1,127 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace addon\aikefu\event;
|
||||||
|
|
||||||
|
use addon\aikefu\model\Config as KefuConfigModel;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取智能客服配置信息
|
||||||
|
*/
|
||||||
|
class KefuGetInfo
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* 处理获取配置信息事件
|
||||||
|
* @param array $data 事件数据
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public function handle($data)
|
||||||
|
{
|
||||||
|
$site_id = $data['site_id'] ?? 0;
|
||||||
|
$member_id = $data['member_id'] ?? 0;
|
||||||
|
$client_info = $data['client_info'] ?? [];
|
||||||
|
|
||||||
|
try {
|
||||||
|
// 获取智能客服配置
|
||||||
|
$kefu_config_model = new KefuConfigModel();
|
||||||
|
$config_info = $kefu_config_model->getConfig($site_id);
|
||||||
|
|
||||||
|
$response_data = [
|
||||||
|
'service_info' => [
|
||||||
|
'name' => '智能客服',
|
||||||
|
'version' => '1.0.0',
|
||||||
|
'enabled' => false,
|
||||||
|
'status' => 'disabled'
|
||||||
|
],
|
||||||
|
'features' => [],
|
||||||
|
'limits' => [
|
||||||
|
'max_message_length' => 4000,
|
||||||
|
'max_conversation_history' => 100,
|
||||||
|
'rate_limit' => [
|
||||||
|
'requests_per_minute' => 60,
|
||||||
|
'requests_per_hour' => 1000
|
||||||
|
]
|
||||||
|
],
|
||||||
|
'endpoints' => [
|
||||||
|
'chat' => '/api/kefu/chat',
|
||||||
|
'chat_stream' => '/api/kefu/chatStream',
|
||||||
|
'create_conversation' => '/api/kefu/createConversation',
|
||||||
|
'get_history' => '/api/kefu/getHistory',
|
||||||
|
'clear_conversation' => '/api/kefu/clearConversation',
|
||||||
|
'health' => '/api/kefu/health',
|
||||||
|
'info' => '/api/kefu/info'
|
||||||
|
],
|
||||||
|
'client_info' => $client_info,
|
||||||
|
'server_info' => [
|
||||||
|
'php_version' => PHP_VERSION,
|
||||||
|
'server_time' => date('Y-m-d H:i:s'),
|
||||||
|
'timezone' => date_default_timezone_get()
|
||||||
|
]
|
||||||
|
];
|
||||||
|
|
||||||
|
// 处理配置信息
|
||||||
|
if (!empty($config_info['data']['value'])) {
|
||||||
|
$config = $config_info['data']['value'];
|
||||||
|
|
||||||
|
// 服务状态
|
||||||
|
$response_data['service_info']['enabled'] = $config['status'] == 1;
|
||||||
|
$response_data['service_info']['status'] = $config['status'] == 1 ? 'enabled' : 'disabled';
|
||||||
|
|
||||||
|
// 可用功能
|
||||||
|
if ($config['status'] == 1) {
|
||||||
|
$response_data['features'] = [
|
||||||
|
'chat' => true,
|
||||||
|
'chat_stream' => true,
|
||||||
|
'conversation_management' => true,
|
||||||
|
'history_management' => true
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
// API端点信息(仅在启用时显示详细配置)
|
||||||
|
if ($config['status'] == 1) {
|
||||||
|
$response_data['api_config'] = [
|
||||||
|
'base_url' => $config['base_url'] ?? '',
|
||||||
|
'chat_endpoint' => $config['chat_endpoint'] ?? '',
|
||||||
|
'supports_streaming' => true,
|
||||||
|
'authentication' => 'bearer_token'
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
// 限制配置(如果有的话)
|
||||||
|
if (isset($config['max_message_length'])) {
|
||||||
|
$response_data['limits']['max_message_length'] = intval($config['max_message_length']);
|
||||||
|
}
|
||||||
|
if (isset($config['rate_limit_per_minute'])) {
|
||||||
|
$response_data['limits']['rate_limit']['requests_per_minute'] = intval($config['rate_limit_per_minute']);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 添加使用统计信息(如果需要的话)
|
||||||
|
if ($member_id > 0) {
|
||||||
|
$response_data['user_stats'] = [
|
||||||
|
'can_use_service' => $response_data['service_info']['enabled'],
|
||||||
|
'member_id' => $member_id,
|
||||||
|
'site_id' => $site_id
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
return [
|
||||||
|
'code' => 0,
|
||||||
|
'message' => '获取配置信息成功',
|
||||||
|
'data' => $response_data
|
||||||
|
];
|
||||||
|
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
return [
|
||||||
|
'code' => -1,
|
||||||
|
'message' => '获取配置信息失败:' . $e->getMessage(),
|
||||||
|
'data' => [
|
||||||
|
'service_info' => [
|
||||||
|
'name' => '智能客服',
|
||||||
|
'status' => 'error'
|
||||||
|
],
|
||||||
|
'error' => $e->getMessage()
|
||||||
|
]
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
349
src/addon/aikefu/event/KefuHealthCheck.php
Normal file
349
src/addon/aikefu/event/KefuHealthCheck.php
Normal file
@@ -0,0 +1,349 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace addon\aikefu\event;
|
||||||
|
|
||||||
|
use addon\aikefu\model\Config as KefuConfigModel;
|
||||||
|
use think\facade\Db;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 智能客服健康检查
|
||||||
|
*/
|
||||||
|
class KefuHealthCheck
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* 处理健康检查事件
|
||||||
|
* @param array $data 事件数据
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public function handle($data)
|
||||||
|
{
|
||||||
|
$check_results = [];
|
||||||
|
$site_id = $data['site_id'] ?? 0;
|
||||||
|
$check_type = $data['check_type'] ?? 'full';
|
||||||
|
|
||||||
|
try {
|
||||||
|
// 1. 数据库连接检查
|
||||||
|
if (in_array($check_type, ['full', 'basic'])) {
|
||||||
|
$check_results[] = $this->checkDatabase();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. AI服务配置检查
|
||||||
|
if (in_array($check_type, ['full', 'ai_service'])) {
|
||||||
|
$check_results[] = $this->checkAIServiceConfig($site_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. AI服务连接检查
|
||||||
|
if (in_array($check_type, ['full', 'ai_service'])) {
|
||||||
|
$check_results[] = $this->checkAIServiceConnection($site_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4. 系统资源检查
|
||||||
|
if (in_array($check_type, ['full'])) {
|
||||||
|
$check_results[] = $this->checkSystemResources();
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
$check_results[] = [
|
||||||
|
'component' => 'health_check_error',
|
||||||
|
'status' => 'error',
|
||||||
|
'message' => '健康检查过程异常:' . $e->getMessage(),
|
||||||
|
'response_time_ms' => 0
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
return $check_results;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检查数据库连接
|
||||||
|
*/
|
||||||
|
private function checkDatabase()
|
||||||
|
{
|
||||||
|
$start_time = microtime(true);
|
||||||
|
|
||||||
|
try {
|
||||||
|
// 测试数据库连接
|
||||||
|
$result = Db::query('SELECT 1 as test');
|
||||||
|
|
||||||
|
if (!empty($result) && $result[0]['test'] == 1) {
|
||||||
|
return [
|
||||||
|
'component' => 'database',
|
||||||
|
'status' => 'healthy',
|
||||||
|
'message' => '数据库连接正常',
|
||||||
|
'response_time_ms' => round((microtime(true) - $start_time) * 1000, 2),
|
||||||
|
'details' => [
|
||||||
|
'connection' => 'success',
|
||||||
|
'query_test' => 'passed'
|
||||||
|
]
|
||||||
|
];
|
||||||
|
} else {
|
||||||
|
return [
|
||||||
|
'component' => 'database',
|
||||||
|
'status' => 'error',
|
||||||
|
'message' => '数据库查询测试失败',
|
||||||
|
'response_time_ms' => round((microtime(true) - $start_time) * 1000, 2)
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
return [
|
||||||
|
'component' => 'database',
|
||||||
|
'status' => 'error',
|
||||||
|
'message' => '数据库连接失败:' . $e->getMessage(),
|
||||||
|
'response_time_ms' => round((microtime(true) - $start_time) * 1000, 2)
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检查AI服务配置
|
||||||
|
*/
|
||||||
|
private function checkAIServiceConfig($site_id)
|
||||||
|
{
|
||||||
|
$start_time = microtime(true);
|
||||||
|
|
||||||
|
try {
|
||||||
|
$config_model = new KefuConfigModel();
|
||||||
|
$config_info = $config_model->getConfig($site_id);
|
||||||
|
|
||||||
|
if (empty($config_info['data']['value'])) {
|
||||||
|
return [
|
||||||
|
'component' => 'ai_service_config',
|
||||||
|
'status' => 'warning',
|
||||||
|
'message' => '智能客服配置未设置',
|
||||||
|
'response_time_ms' => round((microtime(true) - $start_time) * 1000, 2),
|
||||||
|
'details' => [
|
||||||
|
'configured' => false,
|
||||||
|
'required_fields' => ['api_key', 'base_url', 'chat_endpoint']
|
||||||
|
]
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
$config = $config_info['data']['value'];
|
||||||
|
$required_fields = ['api_key', 'base_url', 'chat_endpoint'];
|
||||||
|
$missing_fields = [];
|
||||||
|
|
||||||
|
foreach ($required_fields as $field) {
|
||||||
|
if (empty($config[$field])) {
|
||||||
|
$missing_fields[] = $field;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!empty($missing_fields)) {
|
||||||
|
return [
|
||||||
|
'component' => 'ai_service_config',
|
||||||
|
'status' => 'warning',
|
||||||
|
'message' => 'AI服务配置不完整,缺少字段:' . implode(', ', $missing_fields),
|
||||||
|
'response_time_ms' => round((microtime(true) - $start_time) * 1000, 2),
|
||||||
|
'details' => [
|
||||||
|
'configured' => true,
|
||||||
|
'complete' => false,
|
||||||
|
'missing_fields' => $missing_fields
|
||||||
|
]
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($config['status'] != 1) {
|
||||||
|
return [
|
||||||
|
'component' => 'ai_service_config',
|
||||||
|
'status' => 'warning',
|
||||||
|
'message' => '智能服务已禁用',
|
||||||
|
'response_time_ms' => round((microtime(true) - $start_time) * 1000, 2),
|
||||||
|
'details' => [
|
||||||
|
'configured' => true,
|
||||||
|
'complete' => true,
|
||||||
|
'enabled' => false
|
||||||
|
]
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
return [
|
||||||
|
'component' => 'ai_service_config',
|
||||||
|
'status' => 'healthy',
|
||||||
|
'message' => 'AI服务配置正常',
|
||||||
|
'response_time_ms' => round((microtime(true) - $start_time) * 1000, 2),
|
||||||
|
'details' => [
|
||||||
|
'configured' => true,
|
||||||
|
'complete' => true,
|
||||||
|
'enabled' => true,
|
||||||
|
'base_url' => $config['base_url']
|
||||||
|
]
|
||||||
|
];
|
||||||
|
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
return [
|
||||||
|
'component' => 'ai_service_config',
|
||||||
|
'status' => 'error',
|
||||||
|
'message' => 'AI服务配置检查失败:' . $e->getMessage(),
|
||||||
|
'response_time_ms' => round((microtime(true) - $start_time) * 1000, 2)
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检查AI服务连接
|
||||||
|
*/
|
||||||
|
private function checkAIServiceConnection($site_id)
|
||||||
|
{
|
||||||
|
$start_time = microtime(true);
|
||||||
|
|
||||||
|
try {
|
||||||
|
$config_model = new KefuConfigModel();
|
||||||
|
$config_info = $config_model->getConfig($site_id);
|
||||||
|
|
||||||
|
if (empty($config_info['data']['value'])) {
|
||||||
|
return [
|
||||||
|
'component' => 'ai_service_connection',
|
||||||
|
'status' => 'warning',
|
||||||
|
'message' => 'AI服务未配置,跳过连接检查',
|
||||||
|
'response_time_ms' => round((microtime(true) - $start_time) * 1000, 2)
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
$config = $config_info['data']['value'];
|
||||||
|
|
||||||
|
if ($config['status'] != 1 || empty($config['api_key']) || empty($config['base_url'])) {
|
||||||
|
return [
|
||||||
|
'component' => 'ai_service_connection',
|
||||||
|
'status' => 'warning',
|
||||||
|
'message' => 'AI服务未启用或配置不完整,跳过连接检查',
|
||||||
|
'response_time_ms' => round((microtime(true) - $start_time) * 1000, 2)
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
// 测试连接(发送一个简单的健康检查请求)
|
||||||
|
$url = $config['base_url'];
|
||||||
|
$headers = [
|
||||||
|
'Authorization: Bearer ' . $config['api_key'],
|
||||||
|
'Content-Type: application/json',
|
||||||
|
];
|
||||||
|
|
||||||
|
$ch = curl_init();
|
||||||
|
curl_setopt($ch, CURLOPT_URL, $url);
|
||||||
|
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
||||||
|
curl_setopt($ch, CURLOPT_HEADER, true);
|
||||||
|
curl_setopt($ch, CURLOPT_NOBODY, true);
|
||||||
|
curl_setopt($ch, CURLOPT_TIMEOUT, 10);
|
||||||
|
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
|
||||||
|
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
|
||||||
|
|
||||||
|
$response = curl_exec($ch);
|
||||||
|
$http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||||||
|
curl_close($ch);
|
||||||
|
|
||||||
|
if ($response === false) {
|
||||||
|
return [
|
||||||
|
'component' => 'ai_service_connection',
|
||||||
|
'status' => 'error',
|
||||||
|
'message' => '无法连接到AI服务',
|
||||||
|
'response_time_ms' => round((microtime(true) - $start_time) * 1000, 2)
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($http_code >= 200 && $http_code < 300) {
|
||||||
|
return [
|
||||||
|
'component' => 'ai_service_connection',
|
||||||
|
'status' => 'healthy',
|
||||||
|
'message' => 'AI服务连接正常',
|
||||||
|
'response_time_ms' => round((microtime(true) - $start_time) * 1000, 2),
|
||||||
|
'details' => [
|
||||||
|
'http_status' => $http_code,
|
||||||
|
'url' => $url
|
||||||
|
]
|
||||||
|
];
|
||||||
|
} else {
|
||||||
|
return [
|
||||||
|
'component' => 'ai_service_connection',
|
||||||
|
'status' => 'warning',
|
||||||
|
'message' => 'AI服务响应异常,HTTP状态码:' . $http_code,
|
||||||
|
'response_time_ms' => round((microtime(true) - $start_time) * 1000, 2),
|
||||||
|
'details' => [
|
||||||
|
'http_status' => $http_code,
|
||||||
|
'url' => $url
|
||||||
|
]
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
return [
|
||||||
|
'component' => 'ai_service_connection',
|
||||||
|
'status' => 'error',
|
||||||
|
'message' => 'AI服务连接检查失败:' . $e->getMessage(),
|
||||||
|
'response_time_ms' => round((microtime(true) - $start_time) * 1000, 2)
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检查系统资源
|
||||||
|
*/
|
||||||
|
private function checkSystemResources()
|
||||||
|
{
|
||||||
|
$start_time = microtime(true);
|
||||||
|
|
||||||
|
try {
|
||||||
|
$memory_usage = memory_get_usage(true);
|
||||||
|
$memory_limit = $this->parseMemoryLimit(ini_get('memory_limit'));
|
||||||
|
$memory_usage_percent = ($memory_usage / $memory_limit) * 100;
|
||||||
|
|
||||||
|
$details = [
|
||||||
|
'php_version' => PHP_VERSION,
|
||||||
|
'memory_usage' => round($memory_usage / 1024 / 1024, 2) . ' MB',
|
||||||
|
'memory_limit' => round($memory_limit / 1024 / 1024, 2) . ' MB',
|
||||||
|
'memory_usage_percent' => round($memory_usage_percent, 2) . '%',
|
||||||
|
'max_execution_time' => ini_get('max_execution_time') . 's',
|
||||||
|
'upload_max_filesize' => ini_get('upload_max_filesize'),
|
||||||
|
'post_max_size' => ini_get('post_max_size')
|
||||||
|
];
|
||||||
|
|
||||||
|
$status = 'healthy';
|
||||||
|
$message = '系统资源正常';
|
||||||
|
|
||||||
|
// 检查内存使用率
|
||||||
|
if ($memory_usage_percent > 90) {
|
||||||
|
$status = 'error';
|
||||||
|
$message = '内存使用率过高';
|
||||||
|
} elseif ($memory_usage_percent > 80) {
|
||||||
|
$status = 'warning';
|
||||||
|
$message = '内存使用率较高';
|
||||||
|
}
|
||||||
|
|
||||||
|
return [
|
||||||
|
'component' => 'system_resources',
|
||||||
|
'status' => $status,
|
||||||
|
'message' => $message,
|
||||||
|
'response_time_ms' => round((microtime(true) - $start_time) * 1000, 2),
|
||||||
|
'details' => $details
|
||||||
|
];
|
||||||
|
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
return [
|
||||||
|
'component' => 'system_resources',
|
||||||
|
'status' => 'error',
|
||||||
|
'message' => '系统资源检查失败:' . $e->getMessage(),
|
||||||
|
'response_time_ms' => round((microtime(true) - $start_time) * 1000, 2)
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 解析内存限制值
|
||||||
|
*/
|
||||||
|
private function parseMemoryLimit($val)
|
||||||
|
{
|
||||||
|
$val = trim($val);
|
||||||
|
$last = strtolower($val[strlen($val)-1]);
|
||||||
|
$val = (int)$val;
|
||||||
|
|
||||||
|
switch($last) {
|
||||||
|
case 'g':
|
||||||
|
$val *= 1024;
|
||||||
|
case 'm':
|
||||||
|
$val *= 1024;
|
||||||
|
case 'k':
|
||||||
|
$val *= 1024;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $val;
|
||||||
|
}
|
||||||
|
}
|
||||||
20
src/addon/aikefu/event/UnInstall.php
Normal file
20
src/addon/aikefu/event/UnInstall.php
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace addon\aikefu\event;
|
||||||
|
|
||||||
|
use app\model\system\Addon as AddonModel;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 智能客服插件卸载
|
||||||
|
*/
|
||||||
|
class UnInstall
|
||||||
|
{
|
||||||
|
public function handle()
|
||||||
|
{
|
||||||
|
$addon_model = new AddonModel();
|
||||||
|
// 删除插件信息
|
||||||
|
$addon_model->deleteAddon(['name' => 'aikefu']);
|
||||||
|
|
||||||
|
return success(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
1
src/addon/aikefu/icon.png
Normal file
1
src/addon/aikefu/icon.png
Normal file
File diff suppressed because one or more lines are too long
67
src/addon/aikefu/model/Config.php
Normal file
67
src/addon/aikefu/model/Config.php
Normal file
@@ -0,0 +1,67 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* 智能客服配置模型
|
||||||
|
* 用于存储和管理智能客服的配置信息
|
||||||
|
* 版本:1.0.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace addon\aikefu\model;
|
||||||
|
|
||||||
|
use app\model\system\Config as ConfigModel;
|
||||||
|
use app\model\BaseModel;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 智能客服配置
|
||||||
|
*/
|
||||||
|
class Config extends BaseModel
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* 设置智能客服配置
|
||||||
|
* @param array $data
|
||||||
|
* @param int $site_id
|
||||||
|
* @param string $app_module
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public function setConfig($data, $site_id = 0, $app_module = 'shop')
|
||||||
|
{
|
||||||
|
$config = new ConfigModel();
|
||||||
|
|
||||||
|
// 获取原始配置
|
||||||
|
$original_config = $this->getConfig($site_id, $app_module)['data']['value'] ?? [];
|
||||||
|
|
||||||
|
// 如果 API Key 为空或保持不变,则使用原始值
|
||||||
|
if (isset($data['api_key']) && empty($data['api_key'])) {
|
||||||
|
$data['api_key'] = $original_config['api_key'] ?? '';
|
||||||
|
}
|
||||||
|
|
||||||
|
$res = $config->setConfig($data, '智能客服配置', 1, [['site_id', '=', $site_id], ['app_module', '=', $app_module], ['config_key', '=', 'AIKEFU_CONFIG']]);
|
||||||
|
return $res;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取智能客服配置
|
||||||
|
* @param int $site_id
|
||||||
|
* @param string $app_module
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public function getConfig($site_id = 0, $app_module = 'shop')
|
||||||
|
{
|
||||||
|
$config = new ConfigModel();
|
||||||
|
$res = $config->getConfig([['site_id', '=', $site_id], ['app_module', '=', $app_module], ['config_key', '=', 'AIKEFU_CONFIG']]);
|
||||||
|
return $res;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取智能客服配置信息
|
||||||
|
* @param array $condition
|
||||||
|
* @param string $field
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public function getConfigInfo($condition = [], $field = '*')
|
||||||
|
{
|
||||||
|
// 兼容旧的调用方式
|
||||||
|
$site_id = $condition[0][1] ?? 0;
|
||||||
|
$res = $this->getConfig($site_id);
|
||||||
|
return $res;
|
||||||
|
}
|
||||||
|
}
|
||||||
192
src/addon/aikefu/model/Conversation.php
Normal file
192
src/addon/aikefu/model/Conversation.php
Normal file
@@ -0,0 +1,192 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace addon\aikefu\model;
|
||||||
|
|
||||||
|
use think\Model;
|
||||||
|
|
||||||
|
class Conversation extends Model
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* 操作成功返回值函数
|
||||||
|
* @param string $data
|
||||||
|
* @param string $code_var
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public function success($data = '', $code_var = 'SUCCESS')
|
||||||
|
{
|
||||||
|
$lang_array = $this->getLang();
|
||||||
|
$lang_var = $lang_array[$code_var] ?? $code_var;
|
||||||
|
|
||||||
|
if ($code_var == 'SUCCESS') {
|
||||||
|
$code_var = 0;
|
||||||
|
} else {
|
||||||
|
$code_array = array_keys($lang_array);
|
||||||
|
$code_index = array_search($code_var, $code_array);
|
||||||
|
if ($code_index != false) {
|
||||||
|
$code_var = 10000 + $code_index;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return success($code_var, $lang_var, $data);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 操作失败返回值函数
|
||||||
|
* @param string $data
|
||||||
|
* @param string $code_var
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public function error($data = '', $code_var = 'FAIL')
|
||||||
|
{
|
||||||
|
$lang_array = $this->getLang();
|
||||||
|
|
||||||
|
if (isset($lang_array[$code_var])) {
|
||||||
|
$lang_var = $lang_array[$code_var];
|
||||||
|
} else {
|
||||||
|
$lang_var = $code_var;
|
||||||
|
$code_var = 'FAIL';
|
||||||
|
}
|
||||||
|
$code_array = array_keys($lang_array);
|
||||||
|
$code_index = array_search($code_var, $code_array);
|
||||||
|
if ($code_index != false) {
|
||||||
|
$code_var = -10000 - $code_index;
|
||||||
|
}
|
||||||
|
return error($code_var, $lang_var, $data);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取语言包数组
|
||||||
|
* @return Ambigous <multitype:, unknown>
|
||||||
|
*/
|
||||||
|
public function getLang()
|
||||||
|
{
|
||||||
|
$default_lang = config("lang.default_lang");
|
||||||
|
$cache_common = \think\facade\Cache::get("lang_app/lang/" . $default_lang . '/model.php');
|
||||||
|
if (empty($cache_common)) {
|
||||||
|
$cache_common = include 'app/lang/' . $default_lang . '/model.php';
|
||||||
|
\think\facade\Cache::tag("lang")->set("lang_app/lang/" . $default_lang, $cache_common);
|
||||||
|
}
|
||||||
|
$lang_path = $this->lang ?? '';
|
||||||
|
if (!empty($lang_path)) {
|
||||||
|
$cache_path = \think\facade\Cache::get("lang_" . $lang_path . "/" . $default_lang . '/model.php');
|
||||||
|
if (empty($cache_path)) {
|
||||||
|
$cache_path = include $lang_path . "/" . $default_lang . '/model.php';
|
||||||
|
\think\facade\Cache::tag("lang")->set("lang_" . $lang_path . "/" . $default_lang, $cache_path);
|
||||||
|
}
|
||||||
|
$lang = array_merge($cache_common, $cache_path);
|
||||||
|
} else {
|
||||||
|
$lang = $cache_common;
|
||||||
|
}
|
||||||
|
return $lang;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 表名
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
protected $name = 'aikefu_conversation';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 主键
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
protected $pk = 'id';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取会话信息
|
||||||
|
* @param array $condition
|
||||||
|
* @param string $field
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public function getConversationInfo($condition = [], $field = '*')
|
||||||
|
{
|
||||||
|
$info = $this->where($condition)->field($field)->find();
|
||||||
|
return empty($info) ? [] : $info->toArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取会话列表
|
||||||
|
* @param array $condition
|
||||||
|
* @param string $field
|
||||||
|
* @param string $order
|
||||||
|
* @param int $page
|
||||||
|
* @param int $limit
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public function getConversationList($condition = [], $field = '*', $order = 'id desc', $page = 1, $limit = 10)
|
||||||
|
{
|
||||||
|
$list = $this->where($condition)->field($field)->order($order)->paginate([
|
||||||
|
'page' => $page,
|
||||||
|
'list_rows' => $limit
|
||||||
|
]);
|
||||||
|
return $this->pageFormat($list);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 分页数据格式化
|
||||||
|
* @param \think\Paginator $paginator
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
protected function pageFormat($paginator)
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'data' => $paginator->items(),
|
||||||
|
'total' => $paginator->total(),
|
||||||
|
'per_page' => $paginator->listRows(),
|
||||||
|
'current_page' => $paginator->currentPage(),
|
||||||
|
'last_page' => $paginator->lastPage()
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 添加会话
|
||||||
|
* @param array $data
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public function addConversation($data)
|
||||||
|
{
|
||||||
|
$result = $this->insert($data);
|
||||||
|
return $this->success(['result' => $result]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新会话
|
||||||
|
* @param array $data
|
||||||
|
* @param array $condition
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public function updateConversation($data, $condition)
|
||||||
|
{
|
||||||
|
$result = $this->where($condition)->update($data);
|
||||||
|
return $this->success(['result' => $result]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 删除会话
|
||||||
|
* @param array $condition
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public function deleteConversation($condition)
|
||||||
|
{
|
||||||
|
$result = $this->where($condition)->delete();
|
||||||
|
return $this->success(['result' => $result]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取用户会话列表
|
||||||
|
* @param int $site_id
|
||||||
|
* @param string $user_id
|
||||||
|
* @param int $page
|
||||||
|
* @param int $limit
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public function getUserConversationList($site_id, $user_id, $page = 1, $limit = 10)
|
||||||
|
{
|
||||||
|
$condition = [
|
||||||
|
['site_id', '=', $site_id],
|
||||||
|
['user_id', '=', $user_id]
|
||||||
|
];
|
||||||
|
|
||||||
|
return $this->getConversationList($condition, '*', 'update_time desc', $page, $limit);
|
||||||
|
}
|
||||||
|
}
|
||||||
208
src/addon/aikefu/model/Message.php
Normal file
208
src/addon/aikefu/model/Message.php
Normal file
@@ -0,0 +1,208 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace addon\aikefu\model;
|
||||||
|
|
||||||
|
use think\Model;
|
||||||
|
|
||||||
|
class Message extends Model
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* 操作成功返回值函数
|
||||||
|
* @param string $data
|
||||||
|
* @param string $code_var
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public function success($data = '', $code_var = 'SUCCESS')
|
||||||
|
{
|
||||||
|
$lang_array = $this->getLang();
|
||||||
|
$lang_var = $lang_array[$code_var] ?? $code_var;
|
||||||
|
|
||||||
|
if ($code_var == 'SUCCESS') {
|
||||||
|
$code_var = 0;
|
||||||
|
} else {
|
||||||
|
$code_array = array_keys($lang_array);
|
||||||
|
$code_index = array_search($code_var, $code_array);
|
||||||
|
if ($code_index != false) {
|
||||||
|
$code_var = 10000 + $code_index;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return success($code_var, $lang_var, $data);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 操作失败返回值函数
|
||||||
|
* @param string $data
|
||||||
|
* @param string $code_var
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public function error($data = '', $code_var = 'FAIL')
|
||||||
|
{
|
||||||
|
$lang_array = $this->getLang();
|
||||||
|
|
||||||
|
if (isset($lang_array[$code_var])) {
|
||||||
|
$lang_var = $lang_array[$code_var];
|
||||||
|
} else {
|
||||||
|
$lang_var = $code_var;
|
||||||
|
$code_var = 'FAIL';
|
||||||
|
}
|
||||||
|
$code_array = array_keys($lang_array);
|
||||||
|
$code_index = array_search($code_var, $code_array);
|
||||||
|
if ($code_index != false) {
|
||||||
|
$code_var = -10000 - $code_index;
|
||||||
|
}
|
||||||
|
return error($code_var, $lang_var, $data);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取语言包数组
|
||||||
|
* @return Ambigous <multitype:, unknown>
|
||||||
|
*/
|
||||||
|
public function getLang()
|
||||||
|
{
|
||||||
|
$default_lang = config("lang.default_lang");
|
||||||
|
$cache_common = \think\facade\Cache::get("lang_app/lang/" . $default_lang . '/model.php');
|
||||||
|
if (empty($cache_common)) {
|
||||||
|
$cache_common = include 'app/lang/' . $default_lang . '/model.php';
|
||||||
|
\think\facade\Cache::tag("lang")->set("lang_app/lang/" . $default_lang, $cache_common);
|
||||||
|
}
|
||||||
|
$lang_path = $this->lang ?? '';
|
||||||
|
if (!empty($lang_path)) {
|
||||||
|
$cache_path = \think\facade\Cache::get("lang_" . $lang_path . "/" . $default_lang . '/model.php');
|
||||||
|
if (empty($cache_path)) {
|
||||||
|
$cache_path = include $lang_path . "/" . $default_lang . '/model.php';
|
||||||
|
\think\facade\Cache::tag("lang")->set("lang_" . $lang_path . "/" . $default_lang, $cache_path);
|
||||||
|
}
|
||||||
|
$lang = array_merge($cache_common, $cache_path);
|
||||||
|
} else {
|
||||||
|
$lang = $cache_common;
|
||||||
|
}
|
||||||
|
return $lang;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 表名
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
protected $name = 'aikefu_message';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 主键
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
protected $pk = 'id';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取消息信息
|
||||||
|
* @param array $condition
|
||||||
|
* @param string $field
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public function getMessageInfo($condition = [], $field = '*')
|
||||||
|
{
|
||||||
|
$info = $this->where($condition)->field($field)->find();
|
||||||
|
return empty($info) ? [] : $info->toArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取消息列表
|
||||||
|
* @param array $condition
|
||||||
|
* @param string $field
|
||||||
|
* @param string $order
|
||||||
|
* @param int $page
|
||||||
|
* @param int $limit
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public function getMessageList($condition = [], $field = '*', $order = 'id asc', $page = 1, $limit = 20)
|
||||||
|
{
|
||||||
|
$list = $this->where($condition)->field($field)->order($order)->paginate([
|
||||||
|
'page' => $page,
|
||||||
|
'list_rows' => $limit
|
||||||
|
]);
|
||||||
|
return $this->pageFormat($list);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 分页数据格式化
|
||||||
|
* @param \think\Paginator $paginator
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
protected function pageFormat($paginator)
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'data' => $paginator->items(),
|
||||||
|
'total' => $paginator->total(),
|
||||||
|
'per_page' => $paginator->listRows(),
|
||||||
|
'current_page' => $paginator->currentPage(),
|
||||||
|
'last_page' => $paginator->lastPage()
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 添加消息
|
||||||
|
* @param array $data
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public function addMessage($data)
|
||||||
|
{
|
||||||
|
$result = $this->insert($data);
|
||||||
|
return $this->success(['result' => $result]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新消息
|
||||||
|
* @param array $data
|
||||||
|
* @param array $condition
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public function updateMessage($data, $condition)
|
||||||
|
{
|
||||||
|
$result = $this->where($condition)->update($data);
|
||||||
|
return $this->success(['result' => $result]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 删除消息
|
||||||
|
* @param array $condition
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public function deleteMessage($condition)
|
||||||
|
{
|
||||||
|
$result = $this->where($condition)->delete();
|
||||||
|
return $this->success(['result' => $result]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取会话消息记录
|
||||||
|
* @param int $site_id
|
||||||
|
* @param string $conversation_id
|
||||||
|
* @param int $limit
|
||||||
|
* @param int $offset
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public function getConversationMessages($site_id, $conversation_id, $limit = 20, $offset = 0)
|
||||||
|
{
|
||||||
|
$condition = [
|
||||||
|
['site_id', '=', $site_id],
|
||||||
|
['conversation_id', '=', $conversation_id]
|
||||||
|
];
|
||||||
|
|
||||||
|
return $this->getMessageList($condition, '*', 'create_time asc', ($offset / $limit) + 1, $limit);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取用户消息总数
|
||||||
|
* @param int $site_id
|
||||||
|
* @param string $user_id
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public function getUserMessageCount($site_id, $user_id)
|
||||||
|
{
|
||||||
|
$count = $this->where([
|
||||||
|
['site_id', '=', $site_id],
|
||||||
|
['user_id', '=', $user_id]
|
||||||
|
])->count();
|
||||||
|
|
||||||
|
return $this->success(['count' => $count]);
|
||||||
|
}
|
||||||
|
}
|
||||||
230
src/addon/aikefu/shop/controller/Kefu.php
Normal file
230
src/addon/aikefu/shop/controller/Kefu.php
Normal file
@@ -0,0 +1,230 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* 智能客服控制器
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace addon\aikefu\shop\controller;
|
||||||
|
|
||||||
|
use addon\aikefu\model\Config as KefuConfigModel;
|
||||||
|
use addon\aikefu\model\Conversation as KefuConversationModel;
|
||||||
|
use addon\aikefu\model\Message as KefuMessageModel;
|
||||||
|
use app\shop\controller\BaseShop;
|
||||||
|
|
||||||
|
use think\facade\Db as Db;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 智能客服 控制器
|
||||||
|
*/
|
||||||
|
class Kefu extends BaseShop
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* 智能客服默认页面
|
||||||
|
* @return \think\response\View
|
||||||
|
*/
|
||||||
|
public function index()
|
||||||
|
{
|
||||||
|
$kefu_config_model = new KefuConfigModel();
|
||||||
|
$config_info = $kefu_config_model->getConfig($this->site_id, $this->app_module)['data']['value'] ?? [];
|
||||||
|
$this->assign("config_info", $config_info);
|
||||||
|
return $this->fetch("kefu/index");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 智能客服配置页
|
||||||
|
* @return \think\response\View|\think\response\Json
|
||||||
|
*/
|
||||||
|
public function config()
|
||||||
|
{
|
||||||
|
$kefu_config_model = new KefuConfigModel();
|
||||||
|
|
||||||
|
if (request()->isJson()) {
|
||||||
|
$api_key = input("api_key", "");//Dify API密钥
|
||||||
|
$base_url = input("base_url", "https://api.dify.ai/v1");//API基础地址
|
||||||
|
$chat_endpoint = input("chat_endpoint", "/chat-messages");//聊天接口端点
|
||||||
|
$status = input("status", 0);//状态
|
||||||
|
|
||||||
|
$data = array(
|
||||||
|
"api_key" => $api_key,
|
||||||
|
"base_url" => $base_url,
|
||||||
|
"chat_endpoint" => $chat_endpoint,
|
||||||
|
"status" => $status
|
||||||
|
);
|
||||||
|
$result = $kefu_config_model->setConfig($data, $this->site_id, $this->app_module);
|
||||||
|
return $result;
|
||||||
|
} else {
|
||||||
|
$config_info = $kefu_config_model->getConfig($this->site_id, $this->app_module)['data']['value'] ?? [];
|
||||||
|
$this->assign("config_info", $config_info);
|
||||||
|
return $this->fetch("kefu/config");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 会话管理列表
|
||||||
|
* @return \think\response\View
|
||||||
|
*/
|
||||||
|
public function conversation()
|
||||||
|
{
|
||||||
|
return $this->fetch("kefu/conversation");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取会话列表
|
||||||
|
* @return \think\response\Json
|
||||||
|
*/
|
||||||
|
public function getConversationList()
|
||||||
|
{
|
||||||
|
$page = input("page", 1);
|
||||||
|
$limit = input("limit", 10);
|
||||||
|
$user_id = input("user_id", "");
|
||||||
|
$status = input("status", "");
|
||||||
|
|
||||||
|
$kefu_conversation_model = new KefuConversationModel();
|
||||||
|
$condition = [['site_id', '=', $this->site_id]];
|
||||||
|
|
||||||
|
if (!empty($user_id)) {
|
||||||
|
$condition[] = ['user_id', '=', $user_id];
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($status !== '') {
|
||||||
|
$condition[] = ['status', '=', $status];
|
||||||
|
}
|
||||||
|
|
||||||
|
$conversation_list = $kefu_conversation_model->getConversationList($condition, '*', 'update_time desc', $page, $limit);
|
||||||
|
|
||||||
|
return $this->success($conversation_list);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取会话信息
|
||||||
|
* @return \think\response\Json
|
||||||
|
*/
|
||||||
|
public function getConversationInfo()
|
||||||
|
{
|
||||||
|
$conversation_id = input("conversation_id", "");
|
||||||
|
|
||||||
|
if (empty($conversation_id)) {
|
||||||
|
return $this->error('会话ID不能为空');
|
||||||
|
}
|
||||||
|
|
||||||
|
$kefu_conversation_model = new KefuConversationModel();
|
||||||
|
$conversation_info = $kefu_conversation_model->getConversationInfo([
|
||||||
|
['site_id', '=', $this->site_id],
|
||||||
|
['conversation_id', '=', $conversation_id]
|
||||||
|
]);
|
||||||
|
|
||||||
|
if (empty($conversation_info)) {
|
||||||
|
return $this->error('会话不存在');
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->success($conversation_info);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 结束会话
|
||||||
|
* @return \think\response\Json
|
||||||
|
*/
|
||||||
|
public function endConversation()
|
||||||
|
{
|
||||||
|
$id = input("id", "");
|
||||||
|
|
||||||
|
if (empty($id)) {
|
||||||
|
return $this->error('会话ID不能为空');
|
||||||
|
}
|
||||||
|
|
||||||
|
$kefu_conversation_model = new KefuConversationModel();
|
||||||
|
$result = $kefu_conversation_model->updateConversation(
|
||||||
|
['status' => 0],
|
||||||
|
[
|
||||||
|
['id', '=', $id],
|
||||||
|
['site_id', '=', $this->site_id]
|
||||||
|
]
|
||||||
|
);
|
||||||
|
|
||||||
|
return $this->success($result);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 删除会话
|
||||||
|
* @return \think\response\Json
|
||||||
|
*/
|
||||||
|
public function deleteConversation()
|
||||||
|
{
|
||||||
|
$id = input("id", "");
|
||||||
|
|
||||||
|
if (empty($id)) {
|
||||||
|
return $this->error('会话ID不能为空');
|
||||||
|
}
|
||||||
|
|
||||||
|
$kefu_conversation_model = new KefuConversationModel();
|
||||||
|
$kefu_message_model = new KefuMessageModel();
|
||||||
|
|
||||||
|
// 开启事务
|
||||||
|
Db::startTrans();
|
||||||
|
|
||||||
|
try {
|
||||||
|
// 删除会话关联的消息
|
||||||
|
$conversation_info = $kefu_conversation_model->getConversationInfo([
|
||||||
|
['id', '=', $id],
|
||||||
|
['site_id', '=', $this->site_id]
|
||||||
|
]);
|
||||||
|
|
||||||
|
if (!empty($conversation_info)) {
|
||||||
|
$kefu_message_model->deleteMessage([
|
||||||
|
['site_id', '=', $this->site_id],
|
||||||
|
['conversation_id', '=', $conversation_info['conversation_id']]
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 删除会话
|
||||||
|
$result = $kefu_conversation_model->deleteConversation([
|
||||||
|
['id', '=', $id],
|
||||||
|
['site_id', '=', $this->site_id]
|
||||||
|
]);
|
||||||
|
|
||||||
|
// 提交事务
|
||||||
|
Db::commit();
|
||||||
|
|
||||||
|
return $this->success($result);
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
// 回滚事务
|
||||||
|
Db::rollback();
|
||||||
|
return $this->error($e->getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 消息管理列表
|
||||||
|
* @return \think\response\View
|
||||||
|
*/
|
||||||
|
public function message()
|
||||||
|
{
|
||||||
|
$conversation_id = input("conversation_id", "");
|
||||||
|
View::assign("conversation_id", $conversation_id);
|
||||||
|
return $this->fetch("kefu/message");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取消息列表
|
||||||
|
* @return \think\response\Json
|
||||||
|
*/
|
||||||
|
public function getMessageList()
|
||||||
|
{
|
||||||
|
$page = input("page", 1);
|
||||||
|
$limit = input("limit", 50);
|
||||||
|
$conversation_id = input("conversation_id", "");
|
||||||
|
|
||||||
|
if (empty($conversation_id)) {
|
||||||
|
return $this->error('会话ID不能为空');
|
||||||
|
}
|
||||||
|
|
||||||
|
$kefu_message_model = new KefuMessageModel();
|
||||||
|
$condition = [
|
||||||
|
['site_id', '=', $this->site_id],
|
||||||
|
['conversation_id', '=', $conversation_id]
|
||||||
|
];
|
||||||
|
|
||||||
|
$message_list = $kefu_message_model->getMessageList($condition, '*', 'create_time asc', $page, $limit);
|
||||||
|
|
||||||
|
return $this->success($message_list);
|
||||||
|
}
|
||||||
|
}
|
||||||
104
src/addon/aikefu/shop/view/kefu/config.html
Normal file
104
src/addon/aikefu/shop/view/kefu/config.html
Normal file
@@ -0,0 +1,104 @@
|
|||||||
|
<style>
|
||||||
|
.word-aux {
|
||||||
|
margin-left: 110px;
|
||||||
|
color: #999;
|
||||||
|
font-size: 12px;
|
||||||
|
margin-top: 5px;
|
||||||
|
}
|
||||||
|
.required {
|
||||||
|
color: red;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<div class="layui-form form-wrap">
|
||||||
|
<div class="layui-form-item">
|
||||||
|
<label class="layui-form-label"><span class="required">*</span>Dify API密钥:</label>
|
||||||
|
<div class="layui-input-block">
|
||||||
|
<input type="text" name="api_key" placeholder="请输入Dify API密钥" value="{$config_info.api_key ?? ''}" class="layui-input">
|
||||||
|
</div>
|
||||||
|
<div class="word-aux">
|
||||||
|
从Dify平台获取的API密钥,用于调用Dify聊天机器人API。
|
||||||
|
<a href="https://dify.ai/" target="_blank">前往Dify平台</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="layui-form-item">
|
||||||
|
<label class="layui-form-label">API基础地址:</label>
|
||||||
|
<div class="layui-input-block">
|
||||||
|
<input type="text" name="base_url" placeholder="请输入Dify API基础地址" value="{$config_info.base_url ?? 'https://api.dify.ai/v1'}" class="layui-input">
|
||||||
|
</div>
|
||||||
|
<div class="word-aux">Dify API的基础地址,默认为https://api.dify.ai/v1</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="layui-form-item">
|
||||||
|
<label class="layui-form-label">聊天接口端点:</label>
|
||||||
|
<div class="layui-input-block">
|
||||||
|
<input type="text" name="chat_endpoint" placeholder="请输入聊天接口端点" value="{$config_info.chat_endpoint ?? '/chat-messages'}" class="layui-input">
|
||||||
|
</div>
|
||||||
|
<div class="word-aux">聊天接口的端点,默认为/chat-messages</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="layui-form-item">
|
||||||
|
<label class="layui-form-label"><span class="required">*</span>状态:</label>
|
||||||
|
<div class="layui-input-block">
|
||||||
|
<input type="checkbox" name="status" value="1" lay-skin="switch" {if condition="isset($config_info.status) && $config_info.status == 1"} checked {/if}>
|
||||||
|
</div>
|
||||||
|
<div class="word-aux">启用或禁用智能客服功能</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-row">
|
||||||
|
<button class="layui-btn" lay-submit lay-filter="save">保存</button>
|
||||||
|
<button class="layui-btn layui-btn-primary" onclick="back()">返回</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
layui.use('form', function() {
|
||||||
|
var form = layui.form;
|
||||||
|
var repeat_flag = false; //防重复标识
|
||||||
|
form.render();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 监听提交
|
||||||
|
*/
|
||||||
|
form.on('submit(save)', function(data) {
|
||||||
|
if (repeat_flag) return false;
|
||||||
|
repeat_flag = true;
|
||||||
|
|
||||||
|
$.ajax({
|
||||||
|
url: ns.url("aikefu://shop/kefu/config"),
|
||||||
|
type: 'POST',
|
||||||
|
data: data.field,
|
||||||
|
dataType: 'json',
|
||||||
|
success: function(res) {
|
||||||
|
repeat_flag = false;
|
||||||
|
if (res.code === 0) {
|
||||||
|
layer.confirm('保存成功', {
|
||||||
|
title: '操作提示',
|
||||||
|
btn: ['返回列表', '继续编辑'],
|
||||||
|
yes: function(index, layero) {
|
||||||
|
location.reload();
|
||||||
|
layer.close(index);
|
||||||
|
},
|
||||||
|
btn2: function(index, layero) {
|
||||||
|
layer.close(index);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
layer.msg(res.message, {icon: 2});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
error: function() {
|
||||||
|
repeat_flag = false;
|
||||||
|
layer.msg('请求失败,请稍后重试', {icon: 2});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
function back() {
|
||||||
|
window.history.back();
|
||||||
|
}
|
||||||
|
</script>
|
||||||
214
src/addon/aikefu/shop/view/kefu/conversation.html
Normal file
214
src/addon/aikefu/shop/view/kefu/conversation.html
Normal file
@@ -0,0 +1,214 @@
|
|||||||
|
<style>
|
||||||
|
.search-box {
|
||||||
|
padding: 10px 0;
|
||||||
|
border-bottom: 1px solid #eee;
|
||||||
|
margin-bottom: 15px;
|
||||||
|
}
|
||||||
|
.search-item {
|
||||||
|
display: inline-block;
|
||||||
|
margin-right: 15px;
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
|
.search-item label {
|
||||||
|
display: inline-block;
|
||||||
|
width: 80px;
|
||||||
|
text-align: right;
|
||||||
|
margin-right: 10px;
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
|
.search-item input,
|
||||||
|
.search-item select {
|
||||||
|
width: 150px;
|
||||||
|
display: inline-block;
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
|
.layui-btn-container {
|
||||||
|
margin-bottom: 15px;
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
.layui-btn-sm {
|
||||||
|
margin-left: 5px;
|
||||||
|
}
|
||||||
|
.status-active {
|
||||||
|
color: #52c41a;
|
||||||
|
}
|
||||||
|
.status-inactive {
|
||||||
|
color: #faad14;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<div class="layui-card-body">
|
||||||
|
<!-- 搜索区域 -->
|
||||||
|
<div class="search-box">
|
||||||
|
<form class="layui-form" id="searchForm">
|
||||||
|
<div class="search-item">
|
||||||
|
<label for="user_id">用户ID</label>
|
||||||
|
<input type="text" name="user_id" id="user_id" placeholder="请输入用户ID" class="layui-input">
|
||||||
|
</div>
|
||||||
|
<div class="search-item">
|
||||||
|
<label for="status">状态</label>
|
||||||
|
<select name="status" id="status" class="layui-select">
|
||||||
|
<option value="">全部</option>
|
||||||
|
<option value="1">活跃</option>
|
||||||
|
<option value="0">已结束</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div class="search-item">
|
||||||
|
<button type="button" class="layui-btn layui-btn-primary" id="searchBtn">搜索</button>
|
||||||
|
<button type="button" class="layui-btn layui-btn-primary" id="resetBtn">重置</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 表格区域 -->
|
||||||
|
<table class="layui-table" id="conversationTable" lay-filter="conversationTable"></table>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script type="text/html" id="toolbarDemo">
|
||||||
|
<div class="layui-btn-container">
|
||||||
|
<!-- 可以在这里添加按钮 -->
|
||||||
|
</div>
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script type="text/html" id="statusTpl">
|
||||||
|
{{# if(d.status === 1) {
|
||||||
|
return '<span class="status-active">活跃</span>';
|
||||||
|
} else {
|
||||||
|
return '<span class="status-inactive">已结束</span>';
|
||||||
|
}}}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script type="text/html" id="barDemo">
|
||||||
|
<a class="layui-btn layui-btn-xs" lay-event="view">查看消息</a>
|
||||||
|
<a class="layui-btn layui-btn-xs layui-btn-warning" lay-event="end">结束会话</a>
|
||||||
|
<a class="layui-btn layui-btn-xs layui-btn-danger" lay-event="delete">删除</a>
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
layui.use(['table', 'form', 'layer'], function() {
|
||||||
|
var table = layui.table;
|
||||||
|
var form = layui.form;
|
||||||
|
var layer = layui.layer;
|
||||||
|
|
||||||
|
// 渲染表格
|
||||||
|
var tableIns = table.render({
|
||||||
|
elem: '#conversationTable',
|
||||||
|
url: ns.url("aikefu://shop/kefu/getConversationList"),
|
||||||
|
method: 'POST',
|
||||||
|
toolbar: '#toolbarDemo',
|
||||||
|
defaultToolbar: ['filter', 'exports', 'print'],
|
||||||
|
title: '会话管理',
|
||||||
|
cols: [[
|
||||||
|
{field: 'id', title: 'ID', width: 80, align: 'center', fixed: 'left'},
|
||||||
|
{field: 'conversation_id', title: '会话ID', width: 200, align: 'center'},
|
||||||
|
{field: 'user_id', title: '用户ID', width: 150, align: 'center'},
|
||||||
|
{field: 'name', title: '会话名称', width: 180, align: 'center'},
|
||||||
|
{field: 'status', title: '状态', width: 100, align: 'center', templet: '#statusTpl'},
|
||||||
|
{field: 'create_time', title: '创建时间', width: 180, align: 'center'},
|
||||||
|
{field: 'update_time', title: '更新时间', width: 180, align: 'center'},
|
||||||
|
{fixed: 'right', title: '操作', width: 200, align: 'center', toolbar: '#barDemo'}
|
||||||
|
]],
|
||||||
|
page: true,
|
||||||
|
limit: 10,
|
||||||
|
limits: [10, 20, 30, 50, 100],
|
||||||
|
height: 'full-200',
|
||||||
|
text: {
|
||||||
|
none: '暂无会话数据'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 搜索按钮点击事件
|
||||||
|
$('#searchBtn').click(function() {
|
||||||
|
// 执行搜索
|
||||||
|
tableIns.reload({
|
||||||
|
page: {
|
||||||
|
curr: 1
|
||||||
|
},
|
||||||
|
where: {
|
||||||
|
user_id: $('#user_id').val(),
|
||||||
|
status: $('#status').val()
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// 重置按钮点击事件
|
||||||
|
$('#resetBtn').click(function() {
|
||||||
|
$('#user_id').val('');
|
||||||
|
$('#status').val('');
|
||||||
|
form.render('select');
|
||||||
|
|
||||||
|
// 执行重置后的搜索
|
||||||
|
tableIns.reload({
|
||||||
|
page: {
|
||||||
|
curr: 1
|
||||||
|
},
|
||||||
|
where: {
|
||||||
|
user_id: '',
|
||||||
|
status: ''
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// 监听行工具事件
|
||||||
|
table.on('tool(conversationTable)', function(obj) {
|
||||||
|
var data = obj.data;
|
||||||
|
var layEvent = obj.event;
|
||||||
|
|
||||||
|
if (layEvent === 'view') {
|
||||||
|
// 查看消息
|
||||||
|
layer.open({
|
||||||
|
type: 2,
|
||||||
|
title: '消息记录',
|
||||||
|
content: ns.url("aikefu://shop/kefu/message", {conversation_id: data.conversation_id}),
|
||||||
|
area: ['90%', '90%']
|
||||||
|
});
|
||||||
|
} else if (layEvent === 'end') {
|
||||||
|
// 结束会话
|
||||||
|
layer.confirm('确定要结束该会话吗?', function(index) {
|
||||||
|
$.ajax({
|
||||||
|
url: ns.url("aikefu://shop/kefu/endConversation"),
|
||||||
|
type: 'POST',
|
||||||
|
data: {id: data.id},
|
||||||
|
dataType: 'json',
|
||||||
|
success: function(res) {
|
||||||
|
if (res.code === 0) {
|
||||||
|
layer.msg('会话已结束', {icon: 1});
|
||||||
|
// 重新加载表格数据
|
||||||
|
tableIns.reload();
|
||||||
|
} else {
|
||||||
|
layer.msg('操作失败:' + res.message, {icon: 2});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
error: function() {
|
||||||
|
layer.msg('请求失败,请稍后重试', {icon: 2});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
layer.close(index);
|
||||||
|
});
|
||||||
|
} else if (layEvent === 'delete') {
|
||||||
|
// 删除会话
|
||||||
|
layer.confirm('确定要删除该会话吗?删除后将无法恢复', function(index) {
|
||||||
|
$.ajax({
|
||||||
|
url: ns.url("aikefu://shop/kefu/deleteConversation"),
|
||||||
|
type: 'POST',
|
||||||
|
data: {id: data.id},
|
||||||
|
dataType: 'json',
|
||||||
|
success: function(res) {
|
||||||
|
if (res.code === 0) {
|
||||||
|
layer.msg('会话已删除', {icon: 1});
|
||||||
|
// 重新加载表格数据
|
||||||
|
tableIns.reload();
|
||||||
|
} else {
|
||||||
|
layer.msg('操作失败:' + res.message, {icon: 2});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
error: function() {
|
||||||
|
layer.msg('请求失败,请稍后重试', {icon: 2});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
layer.close(index);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
</script>
|
||||||
38
src/addon/aikefu/shop/view/kefu/index.html
Normal file
38
src/addon/aikefu/shop/view/kefu/index.html
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
<div class="layui-fluid">
|
||||||
|
<div class="layui-card">
|
||||||
|
<div class="layui-card-header">智能客服管理</div>
|
||||||
|
<div class="layui-card-body">
|
||||||
|
<div class="layui-tab layui-tab-brief" lay-filter="kefu-tab">
|
||||||
|
<ul class="layui-tab-title">
|
||||||
|
<li class="layui-this" lay-id="config">配置</li>
|
||||||
|
<li lay-id="conversation">会话</li>
|
||||||
|
<li lay-id="message">消息</li>
|
||||||
|
</ul>
|
||||||
|
<div class="layui-tab-content">
|
||||||
|
<div class="layui-tab-item layui-show">
|
||||||
|
{include file="kefu/config" /}
|
||||||
|
</div>
|
||||||
|
<div class="layui-tab-item">
|
||||||
|
{include file="kefu/conversation" /}
|
||||||
|
</div>
|
||||||
|
<div class="layui-tab-item">
|
||||||
|
{include file="kefu/message" /}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
layui.use(['element', 'form', 'table', 'layer', 'laypage'], function() {
|
||||||
|
var element = layui.element;
|
||||||
|
var form = layui.form;
|
||||||
|
var table = layui.table;
|
||||||
|
var layer = layui.layer;
|
||||||
|
var laypage = layui.laypage;
|
||||||
|
|
||||||
|
// 初始化表单渲染
|
||||||
|
form.render();
|
||||||
|
});
|
||||||
|
</script>
|
||||||
274
src/addon/aikefu/shop/view/kefu/message.html
Normal file
274
src/addon/aikefu/shop/view/kefu/message.html
Normal file
@@ -0,0 +1,274 @@
|
|||||||
|
<style>
|
||||||
|
.message-list {
|
||||||
|
max-height: 600px;
|
||||||
|
overflow-y: auto;
|
||||||
|
padding: 20px;
|
||||||
|
background-color: #f5f7fa;
|
||||||
|
border-radius: 8px;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
border: 1px solid #e6e6e6;
|
||||||
|
}
|
||||||
|
.message-item {
|
||||||
|
margin-bottom: 20px;
|
||||||
|
display: flex;
|
||||||
|
align-items: flex-start;
|
||||||
|
}
|
||||||
|
.message-item.user {
|
||||||
|
justify-content: flex-end;
|
||||||
|
}
|
||||||
|
.message-item.assistant {
|
||||||
|
justify-content: flex-start;
|
||||||
|
}
|
||||||
|
.message-avatar {
|
||||||
|
width: 40px;
|
||||||
|
height: 40px;
|
||||||
|
border-radius: 50%;
|
||||||
|
margin: 0 10px;
|
||||||
|
}
|
||||||
|
.message-content {
|
||||||
|
max-width: 70%;
|
||||||
|
padding: 12px 16px;
|
||||||
|
border-radius: 18px;
|
||||||
|
word-wrap: break-word;
|
||||||
|
line-height: 1.5;
|
||||||
|
}
|
||||||
|
.message-item.user .message-content {
|
||||||
|
background-color: #1E9FFF;
|
||||||
|
color: white;
|
||||||
|
border-bottom-right-radius: 4px;
|
||||||
|
}
|
||||||
|
.message-item.assistant .message-content {
|
||||||
|
background-color: white;
|
||||||
|
color: #333;
|
||||||
|
border-bottom-left-radius: 4px;
|
||||||
|
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
|
||||||
|
}
|
||||||
|
.message-time {
|
||||||
|
font-size: 12px;
|
||||||
|
color: #909399;
|
||||||
|
margin-top: 5px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
.message-role {
|
||||||
|
font-size: 12px;
|
||||||
|
color: #909399;
|
||||||
|
margin-bottom: 5px;
|
||||||
|
}
|
||||||
|
.conversation-info {
|
||||||
|
margin-bottom: 20px;
|
||||||
|
padding: 15px;
|
||||||
|
background-color: white;
|
||||||
|
border-radius: 8px;
|
||||||
|
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
|
||||||
|
border: 1px solid #e6e6e6;
|
||||||
|
}
|
||||||
|
.conversation-info h3 {
|
||||||
|
margin: 0 0 10px 0;
|
||||||
|
font-size: 16px;
|
||||||
|
color: #333;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
.conversation-info p {
|
||||||
|
margin: 5px 0;
|
||||||
|
font-size: 14px;
|
||||||
|
color: #606266;
|
||||||
|
}
|
||||||
|
.status-active {
|
||||||
|
color: #52c41a;
|
||||||
|
}
|
||||||
|
.status-inactive {
|
||||||
|
color: #faad14;
|
||||||
|
}
|
||||||
|
.search-box {
|
||||||
|
margin-bottom: 20px;
|
||||||
|
padding: 15px;
|
||||||
|
background-color: white;
|
||||||
|
border-radius: 8px;
|
||||||
|
border: 1px solid #e6e6e6;
|
||||||
|
}
|
||||||
|
.search-item {
|
||||||
|
margin-right: 15px;
|
||||||
|
}
|
||||||
|
.search-item label {
|
||||||
|
display: inline-block;
|
||||||
|
width: 80px;
|
||||||
|
text-align: right;
|
||||||
|
margin-right: 10px;
|
||||||
|
}
|
||||||
|
.layui-btn-sm {
|
||||||
|
margin-left: 5px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<div class="layui-card-body">
|
||||||
|
<!-- 搜索区域 -->
|
||||||
|
<div class="search-box">
|
||||||
|
<form class="layui-form" id="searchForm">
|
||||||
|
<div class="search-item">
|
||||||
|
<label for="conversation_id">会话ID</label>
|
||||||
|
<input type="text" name="conversation_id" id="conversation_id" placeholder="请输入会话ID" value="{$conversation_id ?? ''}" class="layui-input" style="width: 200px; display: inline-block;">
|
||||||
|
</div>
|
||||||
|
<div class="search-item">
|
||||||
|
<button type="button" class="layui-btn layui-btn-primary" id="searchBtn">搜索</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 会话信息 -->
|
||||||
|
<div id="conversationInfo" class="conversation-info">
|
||||||
|
<!-- 会话信息将通过JavaScript动态加载 -->
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 消息列表 -->
|
||||||
|
<div class="message-list" id="messageList">
|
||||||
|
<!-- 消息列表将通过JavaScript动态加载 -->
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 分页 -->
|
||||||
|
<div class="layui-fixbar" id="pagination"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
layui.use(['laypage', 'layer'], function() {
|
||||||
|
var laypage = layui.laypage;
|
||||||
|
var layer = layui.layer;
|
||||||
|
|
||||||
|
// 分页参数
|
||||||
|
var page = 1;
|
||||||
|
var limit = 50;
|
||||||
|
var total = 0;
|
||||||
|
var conversation_id = $('#conversation_id').val();
|
||||||
|
|
||||||
|
// 加载会话信息
|
||||||
|
function loadConversationInfo() {
|
||||||
|
if (!conversation_id) {
|
||||||
|
$('#conversationInfo').html('<h3>会话详情</h3><p>请输入会话ID进行搜索</p>');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$.ajax({
|
||||||
|
url: ns.url("aikefu://shop/kefu/getConversationInfo"),
|
||||||
|
type: 'POST',
|
||||||
|
data: {
|
||||||
|
conversation_id: conversation_id
|
||||||
|
},
|
||||||
|
dataType: 'json',
|
||||||
|
success: function(res) {
|
||||||
|
if (res.code === 0) {
|
||||||
|
var info = res.data;
|
||||||
|
var html = '<h3>会话详情</h3>';
|
||||||
|
html += '<p><strong>会话ID:</strong>' + info.conversation_id + '</p>';
|
||||||
|
html += '<p><strong>用户ID:</strong>' + info.user_id + '</p>';
|
||||||
|
html += '<p><strong>会话名称:</strong>' + info.name + '</p>';
|
||||||
|
html += '<p><strong>状态:</strong>' + (info.status === 1 ? '<span class="status-active">活跃</span>' : '<span class="status-inactive">已结束</span>') + '</p>';
|
||||||
|
html += '<p><strong>创建时间:</strong>' + info.create_time + '</p>';
|
||||||
|
html += '<p><strong>更新时间:</strong>' + info.update_time + '</p>';
|
||||||
|
$('#conversationInfo').html(html);
|
||||||
|
} else {
|
||||||
|
$('#conversationInfo').html('<h3>会话详情</h3><p>未找到会话信息</p>');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
error: function() {
|
||||||
|
$('#conversationInfo').html('<h3>会话详情</h3><p>加载会话信息失败</p>');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 加载消息列表
|
||||||
|
function loadMessageList() {
|
||||||
|
if (!conversation_id) {
|
||||||
|
$('#messageList').html('<div style="text-align: center; padding: 50px; color: #999;">请输入会话ID进行搜索</div>');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$.ajax({
|
||||||
|
url: ns.url("aikefu://shop/kefu/getMessageList"),
|
||||||
|
type: 'POST',
|
||||||
|
data: {
|
||||||
|
page: page,
|
||||||
|
limit: limit,
|
||||||
|
conversation_id: conversation_id
|
||||||
|
},
|
||||||
|
dataType: 'json',
|
||||||
|
success: function(res) {
|
||||||
|
if (res.code === 0) {
|
||||||
|
var list = res.data.list;
|
||||||
|
total = res.data.total;
|
||||||
|
|
||||||
|
var html = '';
|
||||||
|
if (list.length > 0) {
|
||||||
|
list.forEach(function(item) {
|
||||||
|
var role = item.role === 'user' ? '用户' : '机器人';
|
||||||
|
var roleClass = item.role === 'user' ? 'user' : 'assistant';
|
||||||
|
var avatar = item.role === 'user' ? '/__STATIC__/admin/img/user.png' : '/__STATIC__/admin/img/robot.png';
|
||||||
|
|
||||||
|
html += '<div class="message-item ' + roleClass + '">';
|
||||||
|
if (item.role === 'assistant') {
|
||||||
|
html += '<img src="' + avatar + '" class="message-avatar">';
|
||||||
|
}
|
||||||
|
html += '<div>';
|
||||||
|
html += '<div class="message-role">' + role + '</div>';
|
||||||
|
html += '<div class="message-content">' + item.content + '</div>';
|
||||||
|
html += '<div class="message-time">' + item.create_time + '</div>';
|
||||||
|
html += '</div>';
|
||||||
|
if (item.role === 'user') {
|
||||||
|
html += '<img src="' + avatar + '" class="message-avatar">';
|
||||||
|
}
|
||||||
|
html += '</div>';
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
html += '<div style="text-align: center; padding: 50px; color: #999;">暂无消息记录</div>';
|
||||||
|
}
|
||||||
|
|
||||||
|
$('#messageList').html(html);
|
||||||
|
// 滚动到底部
|
||||||
|
$('#messageList').scrollTop($('#messageList')[0].scrollHeight);
|
||||||
|
// 渲染分页
|
||||||
|
renderPagination();
|
||||||
|
} else {
|
||||||
|
layer.msg('加载失败:' + res.message, {icon: 2});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
error: function() {
|
||||||
|
layer.msg('请求失败,请稍后重试', {icon: 2});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 渲染分页
|
||||||
|
function renderPagination() {
|
||||||
|
if (total <= limit) {
|
||||||
|
$('#pagination').html('');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
laypage.render({
|
||||||
|
elem: 'pagination',
|
||||||
|
count: total,
|
||||||
|
limit: limit,
|
||||||
|
curr: page,
|
||||||
|
layout: ['prev', 'page', 'next', 'count', 'skip'],
|
||||||
|
jump: function(obj, first) {
|
||||||
|
if (!first) {
|
||||||
|
page = obj.curr;
|
||||||
|
loadMessageList();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 搜索按钮点击事件
|
||||||
|
$('#searchBtn').click(function() {
|
||||||
|
conversation_id = $('#conversation_id').val();
|
||||||
|
page = 1;
|
||||||
|
loadConversationInfo();
|
||||||
|
loadMessageList();
|
||||||
|
});
|
||||||
|
|
||||||
|
// 初始化加载
|
||||||
|
if (conversation_id) {
|
||||||
|
loadConversationInfo();
|
||||||
|
loadMessageList();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
@@ -1,38 +1,30 @@
|
|||||||
<?php
|
<?php
|
||||||
/**
|
return [
|
||||||
|
|
||||||
|
// 自定义模板页面类型,格式:[ 'title' => '页面类型名称', 'name' => '页面标识', 'path' => '页面路径', 'value' => '页面数据,json格式' ]
|
||||||
|
'template' => [],
|
||||||
|
|
||||||
|
// 后台自定义组件——装修
|
||||||
|
'util' => [],
|
||||||
*/
|
|
||||||
return [
|
// 自定义页面路径
|
||||||
|
'link' => [],
|
||||||
// 自定义模板页面类型,格式:[ 'title' => '页面类型名称', 'name' => '页面标识', 'path' => '页面路径', 'value' => '页面数据,json格式' ]
|
|
||||||
'template' => [],
|
// 自定义图标库
|
||||||
|
'icon_library' => [],
|
||||||
// 后台自定义组件——装修
|
|
||||||
'util' => [],
|
// uni-app 组件,格式:[ 'name' => '组件名称/文件夹名称', 'path' => '文件路径/目录路径' ],多个逗号隔开,自定义组件名称前缀必须是diy-,也可以引用第三方组件
|
||||||
|
'component' => [],
|
||||||
// 自定义页面路径
|
|
||||||
'link' => [],
|
// uni-app 页面,多个逗号隔开
|
||||||
|
'pages' => [],
|
||||||
// 自定义图标库
|
|
||||||
'icon_library' => [],
|
// 模板信息,格式:'title' => '模板名称', 'name' => '模板标识', 'cover' => '模板封面图', 'preview' => '模板预览图', 'desc' => '模板描述'
|
||||||
|
'info' => [],
|
||||||
// uni-app 组件,格式:[ 'name' => '组件名称/文件夹名称', 'path' => '文件路径/目录路径' ],多个逗号隔开,自定义组件名称前缀必须是diy-,也可以引用第三方组件
|
|
||||||
'component' => [],
|
// 主题风格配色,格式可以自由定义扩展,【在uni-app中通过:this.themeStyle... 获取定义的颜色字段,例如:this.themeStyle.main_color】
|
||||||
|
'theme' => [],
|
||||||
// uni-app 页面,多个逗号隔开
|
|
||||||
'pages' => [],
|
// 自定义页面数据,格式:[ 'title' => '页面名称', 'name' => "页面标识", 'value' => [页面数据,json格式] ]
|
||||||
|
'data' => []
|
||||||
// 模板信息,格式:'title' => '模板名称', 'name' => '模板标识', 'cover' => '模板封面图', 'preview' => '模板预览图', 'desc' => '模板描述'
|
|
||||||
'info' => [],
|
|
||||||
|
|
||||||
// 主题风格配色,格式可以自由定义扩展,【在uni-app中通过:this.themeStyle... 获取定义的颜色字段,例如:this.themeStyle.main_color】
|
|
||||||
'theme' => [],
|
|
||||||
|
|
||||||
// 自定义页面数据,格式:[ 'title' => '页面名称', 'name' => "页面标识", 'value' => [页面数据,json格式] ]
|
|
||||||
'data' => []
|
|
||||||
];
|
];
|
||||||
@@ -1,21 +1,12 @@
|
|||||||
<?php
|
<?php
|
||||||
/**
|
return [
|
||||||
|
'name' => 'alioss',
|
||||||
|
'title' => '阿里云OSS',
|
||||||
|
'description' => '阿里云OSS',
|
||||||
|
'type' => 'system', //插件类型 system :系统插件(自动安装), business:业务插件 promotion:营销插件 tool:工具插件
|
||||||
|
'status' => 1,
|
||||||
|
'author' => '',
|
||||||
|
'version' => '5.3.1',
|
||||||
*/
|
'version_no' => '525231212001',
|
||||||
return [
|
'content' => '',
|
||||||
'name' => 'alioss',
|
|
||||||
'title' => '阿里云OSS',
|
|
||||||
'description' => '阿里云OSS',
|
|
||||||
'type' => 'system', //插件类型 system :系统插件(自动安装), business:业务插件 promotion:营销插件 tool:工具插件
|
|
||||||
'status' => 1,
|
|
||||||
'author' => '',
|
|
||||||
'version' => '5.3.1',
|
|
||||||
'version_no' => '525231212001',
|
|
||||||
'content' => '',
|
|
||||||
];
|
];
|
||||||
@@ -1,39 +1,31 @@
|
|||||||
<?php
|
<?php
|
||||||
/**
|
|
||||||
|
|
||||||
|
namespace addon\alioss\event;
|
||||||
|
|
||||||
|
use addon\alioss\model\Alioss;
|
||||||
|
use addon\alioss\model\Config;
|
||||||
|
|
||||||
*/
|
/**
|
||||||
|
* 删除阿里云图片
|
||||||
|
*/
|
||||||
namespace addon\alioss\event;
|
class ClearAlbumPic
|
||||||
|
{
|
||||||
use addon\alioss\model\Alioss;
|
public function handle($params)
|
||||||
use addon\alioss\model\Config;
|
{
|
||||||
|
$config_model = new Config();
|
||||||
/**
|
$alioss_model = new Alioss();
|
||||||
* 删除阿里云图片
|
|
||||||
*/
|
$config = $config_model->getAliossConfig($params[ 'site_id' ]);
|
||||||
class ClearAlbumPic
|
if (!empty($config[ 'data' ])) {
|
||||||
{
|
if (!empty($config[ 'data' ][ 'value' ][ 'endpoint' ]) && strpos($params[ 'pic_path' ], $config[ 'data' ][ 'value' ][ 'endpoint' ]) === 0) {
|
||||||
public function handle($params)
|
$result = $alioss_model->deleteAlbumPic($params[ 'pic_path' ], $config[ 'data' ][ 'value' ][ 'endpoint' ]);
|
||||||
{
|
return $result;
|
||||||
$config_model = new Config();
|
}
|
||||||
$alioss_model = new Alioss();
|
if (!empty($config[ 'data' ][ 'value' ][ 'domain' ]) && strpos($params[ 'pic_path' ], $config[ 'data' ][ 'value' ][ 'domain' ]) === 0) {
|
||||||
|
$result = $alioss_model->deleteAlbumPic($params[ 'pic_path' ], $config[ 'data' ][ 'value' ][ 'domain' ]);
|
||||||
$config = $config_model->getAliossConfig($params[ 'site_id' ]);
|
return $result;
|
||||||
if (!empty($config[ 'data' ])) {
|
}
|
||||||
if (!empty($config[ 'data' ][ 'value' ][ 'endpoint' ]) && strpos($params[ 'pic_path' ], $config[ 'data' ][ 'value' ][ 'endpoint' ]) === 0) {
|
}
|
||||||
$result = $alioss_model->deleteAlbumPic($params[ 'pic_path' ], $config[ 'data' ][ 'value' ][ 'endpoint' ]);
|
}
|
||||||
return $result;
|
|
||||||
}
|
|
||||||
if (!empty($config[ 'data' ][ 'value' ][ 'domain' ]) && strpos($params[ 'pic_path' ], $config[ 'data' ][ 'value' ][ 'domain' ]) === 0) {
|
|
||||||
$result = $alioss_model->deleteAlbumPic($params[ 'pic_path' ], $config[ 'data' ][ 'value' ][ 'domain' ]);
|
|
||||||
return $result;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@@ -1,27 +1,19 @@
|
|||||||
<?php
|
<?php
|
||||||
/**
|
|
||||||
|
|
||||||
|
namespace addon\alioss\event;
|
||||||
|
|
||||||
|
use addon\alioss\model\Config;
|
||||||
|
|
||||||
|
/**
|
||||||
*/
|
* 关闭云上传
|
||||||
|
*/
|
||||||
|
class CloseOss
|
||||||
namespace addon\alioss\event;
|
{
|
||||||
|
public function handle()
|
||||||
use addon\alioss\model\Config;
|
{
|
||||||
|
$config_model = new Config();
|
||||||
/**
|
$result = $config_model->modifyConfigIsUse(0);
|
||||||
* 关闭云上传
|
return $result;
|
||||||
*/
|
}
|
||||||
class CloseOss
|
|
||||||
{
|
|
||||||
public function handle()
|
|
||||||
{
|
|
||||||
$config_model = new Config();
|
|
||||||
$result = $config_model->modifyConfigIsUse(0);
|
|
||||||
return $result;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@@ -1,26 +1,18 @@
|
|||||||
<?php
|
<?php
|
||||||
/**
|
|
||||||
|
|
||||||
|
namespace addon\alioss\event;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 应用安装
|
||||||
|
*/
|
||||||
*/
|
class Install
|
||||||
|
{
|
||||||
|
/**
|
||||||
namespace addon\alioss\event;
|
* 执行安装
|
||||||
|
*/
|
||||||
/**
|
public function handle()
|
||||||
* 应用安装
|
{
|
||||||
*/
|
return success();
|
||||||
class Install
|
}
|
||||||
{
|
|
||||||
/**
|
|
||||||
* 执行安装
|
|
||||||
*/
|
|
||||||
public function handle()
|
|
||||||
{
|
|
||||||
return success();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@@ -1,33 +1,25 @@
|
|||||||
<?php
|
<?php
|
||||||
/**
|
|
||||||
|
|
||||||
|
namespace addon\alioss\event;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 云上传方式
|
||||||
|
*/
|
||||||
*/
|
class OssType
|
||||||
|
{
|
||||||
|
/**
|
||||||
namespace addon\alioss\event;
|
* 短信发送方式方式及配置
|
||||||
|
*/
|
||||||
/**
|
public function handle()
|
||||||
* 云上传方式
|
{
|
||||||
*/
|
$info = array(
|
||||||
class OssType
|
"sms_type" => "alioss",
|
||||||
{
|
"sms_type_name" => "阿里云上传",
|
||||||
/**
|
"edit_url" => "alioss://shop/config/config",
|
||||||
* 短信发送方式方式及配置
|
"shop_url" => "alioss://shop/config/config",
|
||||||
*/
|
"desc" => "阿里云上传"
|
||||||
public function handle()
|
);
|
||||||
{
|
return $info;
|
||||||
$info = array(
|
}
|
||||||
"sms_type" => "alioss",
|
|
||||||
"sms_type_name" => "阿里云上传",
|
|
||||||
"edit_url" => "alioss://shop/config/config",
|
|
||||||
"shop_url" => "alioss://shop/config/config",
|
|
||||||
"desc" => "阿里云上传"
|
|
||||||
);
|
|
||||||
return $info;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@@ -1,31 +1,23 @@
|
|||||||
<?php
|
<?php
|
||||||
/**
|
|
||||||
|
|
||||||
|
namespace addon\alioss\event;
|
||||||
|
|
||||||
|
use addon\alioss\model\Alioss;
|
||||||
|
|
||||||
|
/**
|
||||||
*/
|
* 云上传方式
|
||||||
|
*/
|
||||||
|
class Put
|
||||||
namespace addon\alioss\event;
|
{
|
||||||
|
/**
|
||||||
use addon\alioss\model\Alioss;
|
* @param $param
|
||||||
|
* @return array
|
||||||
/**
|
*/
|
||||||
* 云上传方式
|
public function handle($param)
|
||||||
*/
|
{
|
||||||
class Put
|
$qiniu_model = new Alioss();
|
||||||
{
|
$result = $qiniu_model->putFile($param);
|
||||||
/**
|
return $result;
|
||||||
* @param $param
|
}
|
||||||
* @return array
|
|
||||||
*/
|
|
||||||
public function handle($param)
|
|
||||||
{
|
|
||||||
$qiniu_model = new Alioss();
|
|
||||||
$result = $qiniu_model->putFile($param);
|
|
||||||
return $result;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@@ -1,27 +1,18 @@
|
|||||||
<?php
|
<?php
|
||||||
/**
|
|
||||||
|
|
||||||
|
namespace addon\alioss\event;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 应用卸载
|
||||||
|
*/
|
||||||
|
class UnInstall
|
||||||
*/
|
{
|
||||||
|
/**
|
||||||
|
* 执行卸载
|
||||||
namespace addon\alioss\event;
|
*/
|
||||||
|
public function handle()
|
||||||
/**
|
{
|
||||||
* 应用卸载
|
return success();
|
||||||
*/
|
}
|
||||||
class UnInstall
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* 执行卸载
|
|
||||||
*/
|
|
||||||
public function handle()
|
|
||||||
{
|
|
||||||
return success();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@@ -1,146 +1,138 @@
|
|||||||
<?php
|
<?php
|
||||||
/**
|
|
||||||
|
namespace addon\alioss\model;
|
||||||
|
|
||||||
|
use app\model\BaseModel;
|
||||||
|
use OSS\Core\OssException;
|
||||||
|
use OSS\OssClient;
|
||||||
|
use think\facade\Log;
|
||||||
*/
|
|
||||||
|
/**
|
||||||
namespace addon\alioss\model;
|
* 阿里云OSS上传
|
||||||
|
*/
|
||||||
use app\model\BaseModel;
|
class Alioss extends BaseModel
|
||||||
use OSS\Core\OssException;
|
{
|
||||||
use OSS\OssClient;
|
|
||||||
use think\facade\Log;
|
/**
|
||||||
|
* 字节组上传
|
||||||
/**
|
* @param $data
|
||||||
* 阿里云OSS上传
|
* @param $key
|
||||||
*/
|
* @return array
|
||||||
class Alioss extends BaseModel
|
*/
|
||||||
{
|
public function put($param)
|
||||||
|
{
|
||||||
/**
|
$data = $param['data'];
|
||||||
* 字节组上传
|
$key = $param['key'];
|
||||||
* @param $data
|
$config_model = new Config();
|
||||||
* @param $key
|
$config_result = $config_model->getAliossConfig();
|
||||||
* @return array
|
$config = $config_result['data'];
|
||||||
*/
|
|
||||||
public function put($param)
|
if ($config['is_use'] == 1) {
|
||||||
{
|
$config = $config['value'];
|
||||||
$data = $param['data'];
|
$access_key_id = $config['access_key_id'];
|
||||||
$key = $param['key'];
|
$access_key_secret = $config['access_key_secret'];
|
||||||
$config_model = new Config();
|
$bucket = $config['bucket'];
|
||||||
$config_result = $config_model->getAliossConfig();
|
$endpoint = $config['endpoint'];
|
||||||
$config = $config_result['data'];
|
try {
|
||||||
|
$ossClient = new OssClient($access_key_id, $access_key_secret, $endpoint);
|
||||||
if ($config['is_use'] == 1) {
|
|
||||||
$config = $config['value'];
|
$result = $ossClient->putObject($bucket, $key, $data);
|
||||||
$access_key_id = $config['access_key_id'];
|
$is_domain = $config[ 'is_domain' ] ?? 0;
|
||||||
$access_key_secret = $config['access_key_secret'];
|
$path = $is_domain > 0 ? $config[ 'domain' ] . '/' . $key : $result['info']['url'];
|
||||||
$bucket = $config['bucket'];
|
$data = array (
|
||||||
$endpoint = $config['endpoint'];
|
'path' => $path,
|
||||||
try {
|
// "path" => $result["info"]["url"],
|
||||||
$ossClient = new OssClient($access_key_id, $access_key_secret, $endpoint);
|
'domain' => $endpoint,
|
||||||
|
'bucket' => $bucket
|
||||||
$result = $ossClient->putObject($bucket, $key, $data);
|
);
|
||||||
$is_domain = $config[ 'is_domain' ] ?? 0;
|
return $this->success($data);
|
||||||
$path = $is_domain > 0 ? $config[ 'domain' ] . '/' . $key : $result['info']['url'];
|
} catch (OssException $e) {
|
||||||
$data = array (
|
return $this->error('', $e->getErrorMessage());
|
||||||
'path' => $path,
|
}
|
||||||
// "path" => $result["info"]["url"],
|
|
||||||
'domain' => $endpoint,
|
}
|
||||||
'bucket' => $bucket
|
}
|
||||||
);
|
|
||||||
return $this->success($data);
|
/**
|
||||||
} catch (OssException $e) {
|
* 设置阿里云OSS参数配置
|
||||||
return $this->error('', $e->getErrorMessage());
|
* @param unknown $filePath 上传图片路径
|
||||||
}
|
* @param unknown $key 上传到阿里云后保存的文件名
|
||||||
|
*/
|
||||||
}
|
public function putFile($param)
|
||||||
}
|
{
|
||||||
|
$file_path = $param['file_path'];
|
||||||
/**
|
$key = $param['key'];
|
||||||
* 设置阿里云OSS参数配置
|
$config_model = new Config();
|
||||||
* @param unknown $filePath 上传图片路径
|
$config = $config_model->getAliossConfig()['data'];
|
||||||
* @param unknown $key 上传到阿里云后保存的文件名
|
if ($config['is_use'] == 1) {
|
||||||
*/
|
$config = $config['value'];
|
||||||
public function putFile($param)
|
$access_key_id = $config['access_key_id'];
|
||||||
{
|
$access_key_secret = $config['access_key_secret'];
|
||||||
$file_path = $param['file_path'];
|
$bucket = $config['bucket'];
|
||||||
$key = $param['key'];
|
//要上传的空间
|
||||||
$config_model = new Config();
|
$endpoint = $config['endpoint'];
|
||||||
$config = $config_model->getAliossConfig()['data'];
|
try {
|
||||||
if ($config['is_use'] == 1) {
|
$ossClient = new OssClient($access_key_id, $access_key_secret, $endpoint);
|
||||||
$config = $config['value'];
|
$result = $ossClient->uploadFile($bucket, $key, $file_path);
|
||||||
$access_key_id = $config['access_key_id'];
|
$is_domain = $config[ 'is_domain' ] ?? 0;
|
||||||
$access_key_secret = $config['access_key_secret'];
|
$path = $is_domain > 0 ? $config[ 'domain' ] . '/' . $key : $result['info']['url'];
|
||||||
$bucket = $config['bucket'];
|
$path = str_replace('http://', 'https://', $path);
|
||||||
//要上传的空间
|
//返回图片的完整URL
|
||||||
$endpoint = $config['endpoint'];
|
$data = array (
|
||||||
try {
|
// "path" => $this->subEndpoint($endpoint, $bucket)."/". $key,
|
||||||
$ossClient = new OssClient($access_key_id, $access_key_secret, $endpoint);
|
'path' => $path,
|
||||||
$result = $ossClient->uploadFile($bucket, $key, $file_path);
|
'domain' => $endpoint,
|
||||||
$is_domain = $config[ 'is_domain' ] ?? 0;
|
'bucket' => $bucket
|
||||||
$path = $is_domain > 0 ? $config[ 'domain' ] . '/' . $key : $result['info']['url'];
|
);
|
||||||
$path = str_replace('http://', 'https://', $path);
|
return $this->success($data);
|
||||||
//返回图片的完整URL
|
} catch (\Exception $e) {
|
||||||
$data = array (
|
return $this->error('', $e->getMessage());
|
||||||
// "path" => $this->subEndpoint($endpoint, $bucket)."/". $key,
|
}
|
||||||
'path' => $path,
|
}
|
||||||
'domain' => $endpoint,
|
}
|
||||||
'bucket' => $bucket
|
|
||||||
);
|
public function subEndpoint($endpoint, $bucket)
|
||||||
return $this->success($data);
|
{
|
||||||
} catch (\Exception $e) {
|
if (strpos($endpoint, 'http://') === 0) {
|
||||||
return $this->error('', $e->getMessage());
|
$temp = 'http://';
|
||||||
}
|
} else {
|
||||||
}
|
$temp = 'https://';
|
||||||
}
|
}
|
||||||
|
$temp_array = explode($temp, $endpoint);
|
||||||
public function subEndpoint($endpoint, $bucket)
|
return $temp . $bucket . '.' . $temp_array[ 1 ];
|
||||||
{
|
}
|
||||||
if (strpos($endpoint, 'http://') === 0) {
|
|
||||||
$temp = 'http://';
|
/**
|
||||||
} else {
|
* @param $file_path
|
||||||
$temp = 'https://';
|
* @return array
|
||||||
}
|
* 删除阿里云图片
|
||||||
$temp_array = explode($temp, $endpoint);
|
*/
|
||||||
return $temp . $bucket . '.' . $temp_array[ 1 ];
|
public function deleteAlbumPic($file_path, $prefix)
|
||||||
}
|
{
|
||||||
|
$config_model = new Config();
|
||||||
/**
|
$config_result = $config_model->getAliossConfig();
|
||||||
* @param $file_path
|
$config = $config_result['data'];
|
||||||
* @return array
|
|
||||||
* 删除阿里云图片
|
if (!empty($config)) {
|
||||||
*/
|
$config = $config['value'];
|
||||||
public function deleteAlbumPic($file_path, $prefix)
|
$access_key_id = $config['access_key_id'];
|
||||||
{
|
$access_key_secret = $config['access_key_secret'];
|
||||||
$config_model = new Config();
|
$bucket = $config['bucket'];
|
||||||
$config_result = $config_model->getAliossConfig();
|
//要上传的空间
|
||||||
$config = $config_result['data'];
|
$endpoint = $config['endpoint'];
|
||||||
|
try {
|
||||||
if (!empty($config)) {
|
$ossClient = new OssClient($access_key_id, $access_key_secret, $endpoint);
|
||||||
$config = $config['value'];
|
$ossClient->deleteObject($bucket, str_replace($prefix . '/', '', $file_path));
|
||||||
$access_key_id = $config['access_key_id'];
|
$ossClient->deleteObject($bucket, str_replace($prefix . '/', '', img($file_path, 'big')));
|
||||||
$access_key_secret = $config['access_key_secret'];
|
$ossClient->deleteObject($bucket, str_replace($prefix . '/', '', img($file_path, 'mid')));
|
||||||
$bucket = $config['bucket'];
|
$ossClient->deleteObject($bucket, str_replace($prefix . '/', '', img($file_path, 'small')));
|
||||||
//要上传的空间
|
|
||||||
$endpoint = $config['endpoint'];
|
return $this->success();
|
||||||
try {
|
} catch (OssException $e) {
|
||||||
$ossClient = new OssClient($access_key_id, $access_key_secret, $endpoint);
|
return $this->error('', $e->getErrorMessage());
|
||||||
$ossClient->deleteObject($bucket, str_replace($prefix . '/', '', $file_path));
|
}
|
||||||
$ossClient->deleteObject($bucket, str_replace($prefix . '/', '', img($file_path, 'big')));
|
}
|
||||||
$ossClient->deleteObject($bucket, str_replace($prefix . '/', '', img($file_path, 'mid')));
|
|
||||||
$ossClient->deleteObject($bucket, str_replace($prefix . '/', '', img($file_path, 'small')));
|
}
|
||||||
|
|
||||||
return $this->success();
|
|
||||||
} catch (OssException $e) {
|
|
||||||
return $this->error('', $e->getErrorMessage());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -1,56 +1,48 @@
|
|||||||
<?php
|
<?php
|
||||||
/**
|
|
||||||
|
namespace addon\alioss\model;
|
||||||
|
|
||||||
|
use app\model\system\Config as ConfigModel;
|
||||||
|
use app\model\BaseModel;
|
||||||
|
|
||||||
|
/**
|
||||||
*/
|
* 阿里云配置
|
||||||
|
*/
|
||||||
namespace addon\alioss\model;
|
class Config extends BaseModel
|
||||||
|
{
|
||||||
use app\model\system\Config as ConfigModel;
|
/**
|
||||||
use app\model\BaseModel;
|
* 设置阿里云OSS上传配置
|
||||||
|
* array $data
|
||||||
/**
|
*/
|
||||||
* 阿里云配置
|
public function setAliossConfig($data, $status, $site_id = 1, $app_module = 'shop')
|
||||||
*/
|
{
|
||||||
class Config extends BaseModel
|
if ($status == 1) {
|
||||||
{
|
event('CloseOss', []);//同步关闭所有云上传
|
||||||
/**
|
}
|
||||||
* 设置阿里云OSS上传配置
|
|
||||||
* array $data
|
$config = new ConfigModel();
|
||||||
*/
|
$res = $config->setConfig($data, '阿里云OSS上传配置', $status, [ [ 'site_id', '=', $site_id ], [ 'app_module', '=', $app_module ], [ 'config_key', '=', 'ALIOSS_CONFIG' ] ]);
|
||||||
public function setAliossConfig($data, $status, $site_id = 1, $app_module = 'shop')
|
return $res;
|
||||||
{
|
}
|
||||||
if ($status == 1) {
|
|
||||||
event('CloseOss', []);//同步关闭所有云上传
|
/**
|
||||||
}
|
* 获取阿里云上传配置
|
||||||
|
*/
|
||||||
$config = new ConfigModel();
|
public function getAliossConfig($site_id = 1, $app_module = 'shop')
|
||||||
$res = $config->setConfig($data, '阿里云OSS上传配置', $status, [ [ 'site_id', '=', $site_id ], [ 'app_module', '=', $app_module ], [ 'config_key', '=', 'ALIOSS_CONFIG' ] ]);
|
{
|
||||||
return $res;
|
$config = new ConfigModel();
|
||||||
}
|
$res = $config->getConfig([ [ 'site_id', '=', $site_id ], [ 'app_module', '=', $app_module ], [ 'config_key', '=', 'ALIOSS_CONFIG' ] ]);
|
||||||
|
return $res;
|
||||||
/**
|
}
|
||||||
* 获取阿里云上传配置
|
|
||||||
*/
|
/**
|
||||||
public function getAliossConfig($site_id = 1, $app_module = 'shop')
|
* 配置阿里云开关状态
|
||||||
{
|
* @param $status
|
||||||
$config = new ConfigModel();
|
*/
|
||||||
$res = $config->getConfig([ [ 'site_id', '=', $site_id ], [ 'app_module', '=', $app_module ], [ 'config_key', '=', 'ALIOSS_CONFIG' ] ]);
|
public function modifyConfigIsUse($status, $site_id = 1, $app_module = 'shop')
|
||||||
return $res;
|
{
|
||||||
}
|
$config = new ConfigModel();
|
||||||
|
$res = $config->modifyConfigIsUse($status, [ [ 'site_id', '=', $site_id ], [ 'app_module', '=', $app_module ], [ 'config_key', '=', 'ALIOSS_CONFIG' ] ]);
|
||||||
/**
|
return $res;
|
||||||
* 配置阿里云开关状态
|
}
|
||||||
* @param $status
|
|
||||||
*/
|
|
||||||
public function modifyConfigIsUse($status, $site_id = 1, $app_module = 'shop')
|
|
||||||
{
|
|
||||||
$config = new ConfigModel();
|
|
||||||
$res = $config->modifyConfigIsUse($status, [ [ 'site_id', '=', $site_id ], [ 'app_module', '=', $app_module ], [ 'config_key', '=', 'ALIOSS_CONFIG' ] ]);
|
|
||||||
return $res;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@@ -1,56 +1,48 @@
|
|||||||
<?php
|
<?php
|
||||||
/**
|
|
||||||
|
namespace addon\alioss\shop\controller;
|
||||||
|
|
||||||
|
use addon\alioss\model\Config as ConfigModel;
|
||||||
|
use app\shop\controller\BaseShop;
|
||||||
|
|
||||||
|
/**
|
||||||
*/
|
* 七牛云上传管理
|
||||||
|
*/
|
||||||
namespace addon\alioss\shop\controller;
|
class Config extends BaseShop
|
||||||
|
{
|
||||||
use addon\alioss\model\Config as ConfigModel;
|
|
||||||
use app\shop\controller\BaseShop;
|
/**
|
||||||
|
* 云上传配置
|
||||||
/**
|
* @return mixed
|
||||||
* 七牛云上传管理
|
*/
|
||||||
*/
|
public function config()
|
||||||
class Config extends BaseShop
|
{
|
||||||
{
|
$config_model = new ConfigModel();
|
||||||
|
if (request()->isJson()) {
|
||||||
/**
|
$bucket = input('bucket', '');
|
||||||
* 云上传配置
|
$access_key_id = input('access_key_id', '');
|
||||||
* @return mixed
|
$access_key_secret = input('access_key_secret', '');
|
||||||
*/
|
$endpoint = input('endpoint', '');
|
||||||
public function config()
|
$status = input('status', 0);
|
||||||
{
|
$domain = input('domain', '');
|
||||||
$config_model = new ConfigModel();
|
$is_domain = input('is_domain', 0);
|
||||||
if (request()->isJson()) {
|
|
||||||
$bucket = input('bucket', '');
|
$data = array (
|
||||||
$access_key_id = input('access_key_id', '');
|
'bucket' => $bucket,
|
||||||
$access_key_secret = input('access_key_secret', '');
|
'access_key_id' => $access_key_id,
|
||||||
$endpoint = input('endpoint', '');
|
'access_key_secret' => $access_key_secret,
|
||||||
$status = input('status', 0);
|
'endpoint' => $endpoint,
|
||||||
$domain = input('domain', '');
|
'domain' => $domain,
|
||||||
$is_domain = input('is_domain', 0);
|
'is_domain' => $is_domain
|
||||||
|
);
|
||||||
$data = array (
|
|
||||||
'bucket' => $bucket,
|
$result = $config_model->setAliossConfig($data, $status, $this->site_id, $this->app_module);
|
||||||
'access_key_id' => $access_key_id,
|
return $result;
|
||||||
'access_key_secret' => $access_key_secret,
|
} else {
|
||||||
'endpoint' => $endpoint,
|
$info_result = $config_model->getAliossConfig($this->site_id, $this->app_module);
|
||||||
'domain' => $domain,
|
$info = $info_result['data'];
|
||||||
'is_domain' => $is_domain
|
$this->assign('info', $info);
|
||||||
);
|
return $this->fetch('config/config');
|
||||||
|
}
|
||||||
$result = $config_model->setAliossConfig($data, $status, $this->site_id, $this->app_module);
|
}
|
||||||
return $result;
|
|
||||||
} else {
|
|
||||||
$info_result = $config_model->getAliossConfig($this->site_id, $this->app_module);
|
|
||||||
$info = $info_result['data'];
|
|
||||||
$this->assign('info', $info);
|
|
||||||
return $this->fetch('config/config');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@@ -1,38 +1,30 @@
|
|||||||
<?php
|
<?php
|
||||||
/**
|
return [
|
||||||
|
|
||||||
|
// 自定义模板页面类型,格式:[ 'title' => '页面类型名称', 'name' => '页面标识', 'path' => '页面路径', 'value' => '页面数据,json格式' ]
|
||||||
|
'template' => [],
|
||||||
|
|
||||||
|
// 后台自定义组件——装修
|
||||||
|
'util' => [],
|
||||||
*/
|
|
||||||
return [
|
// 自定义页面路径
|
||||||
|
'link' => [],
|
||||||
// 自定义模板页面类型,格式:[ 'title' => '页面类型名称', 'name' => '页面标识', 'path' => '页面路径', 'value' => '页面数据,json格式' ]
|
|
||||||
'template' => [],
|
// 自定义图标库
|
||||||
|
'icon_library' => [],
|
||||||
// 后台自定义组件——装修
|
|
||||||
'util' => [],
|
// uni-app 组件,格式:[ 'name' => '组件名称/文件夹名称', 'path' => '文件路径/目录路径' ],多个逗号隔开,自定义组件名称前缀必须是diy-,也可以引用第三方组件
|
||||||
|
'component' => [],
|
||||||
// 自定义页面路径
|
|
||||||
'link' => [],
|
// uni-app 页面,多个逗号隔开
|
||||||
|
'pages' => [],
|
||||||
// 自定义图标库
|
|
||||||
'icon_library' => [],
|
// 模板信息,格式:'title' => '模板名称', 'name' => '模板标识', 'cover' => '模板封面图', 'preview' => '模板预览图', 'desc' => '模板描述'
|
||||||
|
'info' => [],
|
||||||
// uni-app 组件,格式:[ 'name' => '组件名称/文件夹名称', 'path' => '文件路径/目录路径' ],多个逗号隔开,自定义组件名称前缀必须是diy-,也可以引用第三方组件
|
|
||||||
'component' => [],
|
// 主题风格配色,格式可以自由定义扩展,【在uni-app中通过:this.themeStyle... 获取定义的颜色字段,例如:this.themeStyle.main_color】
|
||||||
|
'theme' => [],
|
||||||
// uni-app 页面,多个逗号隔开
|
|
||||||
'pages' => [],
|
// 自定义页面数据,格式:[ 'title' => '页面名称', 'name' => "页面标识", 'value' => [页面数据,json格式] ]
|
||||||
|
'data' => []
|
||||||
// 模板信息,格式:'title' => '模板名称', 'name' => '模板标识', 'cover' => '模板封面图', 'preview' => '模板预览图', 'desc' => '模板描述'
|
|
||||||
'info' => [],
|
|
||||||
|
|
||||||
// 主题风格配色,格式可以自由定义扩展,【在uni-app中通过:this.themeStyle... 获取定义的颜色字段,例如:this.themeStyle.main_color】
|
|
||||||
'theme' => [],
|
|
||||||
|
|
||||||
// 自定义页面数据,格式:[ 'title' => '页面名称', 'name' => "页面标识", 'value' => [页面数据,json格式] ]
|
|
||||||
'data' => []
|
|
||||||
];
|
];
|
||||||
@@ -1,50 +1,42 @@
|
|||||||
<?php
|
<?php
|
||||||
/**
|
return [
|
||||||
|
'bind' => [
|
||||||
|
|
||||||
|
],
|
||||||
|
|
||||||
|
'listen' => [
|
||||||
|
//支付异步回调
|
||||||
*/
|
'PayNotify' => [
|
||||||
return [
|
'addon\alipay\event\PayNotify'
|
||||||
'bind' => [
|
],
|
||||||
|
//支付方式,后台查询
|
||||||
],
|
'PayType' => [
|
||||||
|
'addon\alipay\event\PayType'
|
||||||
'listen' => [
|
],
|
||||||
//支付异步回调
|
//支付,前台应用
|
||||||
'PayNotify' => [
|
'Pay' => [
|
||||||
'addon\alipay\event\PayNotify'
|
'addon\alipay\event\Pay'
|
||||||
],
|
],
|
||||||
//支付方式,后台查询
|
'PayClose' => [
|
||||||
'PayType' => [
|
'addon\alipay\event\PayClose'
|
||||||
'addon\alipay\event\PayType'
|
],
|
||||||
],
|
'PayRefund' => [
|
||||||
//支付,前台应用
|
'addon\alipay\event\PayRefund'
|
||||||
'Pay' => [
|
],
|
||||||
'addon\alipay\event\Pay'
|
'PayTransfer' => [
|
||||||
],
|
'addon\alipay\event\PayTransfer'
|
||||||
'PayClose' => [
|
],
|
||||||
'addon\alipay\event\PayClose'
|
'TransferType' => [
|
||||||
],
|
'addon\alipay\event\TransferType'
|
||||||
'PayRefund' => [
|
],
|
||||||
'addon\alipay\event\PayRefund'
|
'AuthcodePay' => [
|
||||||
],
|
'addon\alipay\event\AuthcodePay'
|
||||||
'PayTransfer' => [
|
],
|
||||||
'addon\alipay\event\PayTransfer'
|
'PayOrderQuery' => [
|
||||||
],
|
'addon\alipay\event\PayOrderQuery'
|
||||||
'TransferType' => [
|
],
|
||||||
'addon\alipay\event\TransferType'
|
],
|
||||||
],
|
|
||||||
'AuthcodePay' => [
|
'subscribe' => [
|
||||||
'addon\alipay\event\AuthcodePay'
|
],
|
||||||
],
|
];
|
||||||
'PayOrderQuery' => [
|
|
||||||
'addon\alipay\event\PayOrderQuery'
|
|
||||||
],
|
|
||||||
],
|
|
||||||
|
|
||||||
'subscribe' => [
|
|
||||||
],
|
|
||||||
];
|
|
||||||
|
|||||||
@@ -1,21 +1,12 @@
|
|||||||
<?php
|
<?php
|
||||||
/**
|
return [
|
||||||
|
'name' => 'alipay',
|
||||||
|
'title' => '支付宝支付',
|
||||||
|
'description' => '支付宝支付功能',
|
||||||
|
'type' => 'system', //插件类型 system :系统插件(自动安装), promotion:营销插件 tool:工具插件
|
||||||
|
'status' => 1,
|
||||||
|
'author' => '',
|
||||||
|
'version' => '5.3.1',
|
||||||
*/
|
'version_no' => '525231212001',
|
||||||
return [
|
'content' => '',
|
||||||
'name' => 'alipay',
|
|
||||||
'title' => '支付宝支付',
|
|
||||||
'description' => '支付宝支付功能',
|
|
||||||
'type' => 'system', //插件类型 system :系统插件(自动安装), promotion:营销插件 tool:工具插件
|
|
||||||
'status' => 1,
|
|
||||||
'author' => '',
|
|
||||||
'version' => '5.3.1',
|
|
||||||
'version_no' => '525231212001',
|
|
||||||
'content' => '',
|
|
||||||
];
|
];
|
||||||
@@ -1,23 +1,15 @@
|
|||||||
<?php
|
<?php
|
||||||
/**
|
return [
|
||||||
|
[
|
||||||
|
'name' => 'ALI_PAY_CONFIG',
|
||||||
|
'title' => '支付宝支付编辑',
|
||||||
|
'url' => 'alipay://shop/pay/config',
|
||||||
|
'parent' => 'CONFIG_PAY',
|
||||||
|
'is_show' => 0,
|
||||||
*/
|
'is_control' => 1,
|
||||||
return [
|
'is_icon' => 0,
|
||||||
[
|
'picture' => '',
|
||||||
'name' => 'ALI_PAY_CONFIG',
|
'picture_select' => '',
|
||||||
'title' => '支付宝支付编辑',
|
'sort' => 1,
|
||||||
'url' => 'alipay://shop/pay/config',
|
],
|
||||||
'parent' => 'CONFIG_PAY',
|
];
|
||||||
'is_show' => 0,
|
|
||||||
'is_control' => 1,
|
|
||||||
'is_icon' => 0,
|
|
||||||
'picture' => '',
|
|
||||||
'picture_select' => '',
|
|
||||||
'sort' => 1,
|
|
||||||
],
|
|
||||||
];
|
|
||||||
|
|||||||
@@ -1,27 +1,18 @@
|
|||||||
<?php
|
<?php
|
||||||
/**
|
|
||||||
|
|
||||||
|
namespace addon\alipay\event;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 应用安装
|
||||||
|
*/
|
||||||
|
class Install
|
||||||
*/
|
{
|
||||||
|
/**
|
||||||
|
* 执行安装
|
||||||
namespace addon\alipay\event;
|
*/
|
||||||
|
public function handle()
|
||||||
/**
|
{
|
||||||
* 应用安装
|
return success();
|
||||||
*/
|
}
|
||||||
class Install
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* 执行安装
|
|
||||||
*/
|
|
||||||
public function handle()
|
|
||||||
{
|
|
||||||
return success();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@@ -1,33 +1,25 @@
|
|||||||
<?php
|
<?php
|
||||||
/**
|
|
||||||
|
namespace addon\alipay\event;
|
||||||
|
|
||||||
|
use addon\alipay\model\Pay as PayModel;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 生成支付
|
||||||
*/
|
*/
|
||||||
|
class Pay
|
||||||
namespace addon\alipay\event;
|
{
|
||||||
|
/**
|
||||||
use addon\alipay\model\Pay as PayModel;
|
* 支付方式及配置
|
||||||
|
*/
|
||||||
/**
|
public function handle($param)
|
||||||
* 生成支付
|
{
|
||||||
*/
|
if ($param[ "pay_type" ] == "alipay") {
|
||||||
class Pay
|
if (in_array($param[ "app_type" ], [ "h5", "app", "pc", "aliapp", 'wechat' ])) {
|
||||||
{
|
$pay_model = new PayModel($param[ 'site_id' ], $param[ "app_type" ] == 'aliapp');
|
||||||
/**
|
$res = $pay_model->pay($param);
|
||||||
* 支付方式及配置
|
return $res;
|
||||||
*/
|
}
|
||||||
public function handle($param)
|
}
|
||||||
{
|
}
|
||||||
if ($param[ "pay_type" ] == "alipay") {
|
|
||||||
if (in_array($param[ "app_type" ], [ "h5", "app", "pc", "aliapp", 'wechat' ])) {
|
|
||||||
$pay_model = new PayModel($param[ 'site_id' ], $param[ "app_type" ] == 'aliapp');
|
|
||||||
$res = $pay_model->pay($param);
|
|
||||||
return $res;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@@ -1,39 +1,31 @@
|
|||||||
<?php
|
<?php
|
||||||
/**
|
|
||||||
|
namespace addon\alipay\event;
|
||||||
|
|
||||||
|
use addon\alipay\model\Pay as PayModel;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 关闭支付
|
||||||
*/
|
*/
|
||||||
|
class PayClose
|
||||||
namespace addon\alipay\event;
|
{
|
||||||
|
/**
|
||||||
use addon\alipay\model\Pay as PayModel;
|
* 关闭支付
|
||||||
|
* @param $params
|
||||||
/**
|
* @return \addon\alipay\model\multitype|array
|
||||||
* 关闭支付
|
*/
|
||||||
*/
|
public function handle($params)
|
||||||
class PayClose
|
{
|
||||||
{
|
if ($params["pay_type"] == "alipay") {
|
||||||
/**
|
try {
|
||||||
* 关闭支付
|
$pay_model = new PayModel($params[ 'site_id' ]);
|
||||||
* @param $params
|
$result = $pay_model->close($params);
|
||||||
* @return \addon\alipay\model\multitype|array
|
return $result;
|
||||||
*/
|
} catch (\Exception $e) {
|
||||||
public function handle($params)
|
return error(-1, $e->getMessage());
|
||||||
{
|
} catch (\Throwable $e) {
|
||||||
// if ($params["pay_type"] == "alipay") {
|
return error(-1, $e->getMessage());
|
||||||
try {
|
}
|
||||||
$pay_model = new PayModel($params[ 'site_id' ]);
|
}
|
||||||
$result = $pay_model->close($params);
|
}
|
||||||
return $result;
|
|
||||||
} catch (\Exception $e) {
|
|
||||||
return error(-1, $e->getMessage());
|
|
||||||
} catch (\Throwable $e) {
|
|
||||||
return error(-1, $e->getMessage());
|
|
||||||
}
|
|
||||||
// }
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@@ -1,41 +1,35 @@
|
|||||||
<?php
|
<?php
|
||||||
/**
|
|
||||||
|
namespace addon\alipay\event;
|
||||||
|
|
||||||
|
use addon\alipay\model\Pay as PayModel;
|
||||||
|
use app\model\system\Pay as PayCommon;
|
||||||
|
|
||||||
|
/**
|
||||||
*/
|
* 支付回调
|
||||||
|
*/
|
||||||
namespace addon\alipay\event;
|
class PayNotify
|
||||||
|
{
|
||||||
use addon\alipay\model\Pay as PayModel;
|
/**
|
||||||
use app\model\system\Pay as PayCommon;
|
* 支付方式及配置
|
||||||
|
*/
|
||||||
/**
|
public function handle($param)
|
||||||
* 支付回调
|
{
|
||||||
*/
|
if ($param[ "pay_type" ] != "alipay") return false;
|
||||||
class PayNotify
|
|
||||||
{
|
if (isset($_POST[ 'out_trade_no' ])) {
|
||||||
/**
|
$out_trade_no = $_POST[ 'out_trade_no' ];
|
||||||
* 支付方式及配置
|
$pay = new PayCommon();
|
||||||
*/
|
$pay_info = $pay->getPayInfo($out_trade_no)[ 'data' ];
|
||||||
public function handle()
|
if (empty($pay_info)) return false;
|
||||||
{
|
|
||||||
if (isset($_POST[ 'out_trade_no' ])) {
|
if ($_POST[ 'total_amount' ] != $pay_info[ 'pay_money' ]) {
|
||||||
$out_trade_no = $_POST[ 'out_trade_no' ];
|
return false;
|
||||||
$pay = new PayCommon();
|
}
|
||||||
$pay_info = $pay->getPayInfo($out_trade_no)[ 'data' ];
|
$mch_info = empty($pay_info[ 'mch_info' ]) ? [] : json_decode($pay_info[ 'mch_info' ], true);
|
||||||
if (empty($pay_info)) return false;
|
|
||||||
|
$pay_model = new PayModel($pay_info[ 'site_id' ], $mch_info[ 'is_aliapp' ] ?? 0);
|
||||||
if ($_POST[ 'total_amount' ] != $pay_info[ 'pay_money' ]) {
|
$pay_model->payNotify();
|
||||||
return false;
|
}
|
||||||
}
|
}
|
||||||
$mch_info = empty($pay_info[ 'mch_info' ]) ? [] : json_decode($pay_info[ 'mch_info' ], true);
|
|
||||||
|
|
||||||
$pay_model = new PayModel($pay_info[ 'site_id' ], $mch_info[ 'is_aliapp' ] ?? 0);
|
|
||||||
$pay_model->payNotify();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@@ -1,34 +1,26 @@
|
|||||||
<?php
|
<?php
|
||||||
/**
|
|
||||||
|
namespace addon\alipay\event;
|
||||||
|
|
||||||
|
use addon\alipay\model\Config as ConfigModel;
|
||||||
|
use addon\alipay\model\Pay as PayModel;
|
||||||
|
use app\model\system\Pay;
|
||||||
|
|
||||||
*/
|
/**
|
||||||
|
* 查询支付结果
|
||||||
namespace addon\alipay\event;
|
*/
|
||||||
|
class PayOrderQuery
|
||||||
use addon\alipay\model\Config as ConfigModel;
|
{
|
||||||
use addon\alipay\model\Pay as PayModel;
|
public function handle(array $params)
|
||||||
use app\model\system\Pay;
|
{
|
||||||
|
$pay_info = ( new Pay() )->getInfo([ [ 'id', '=', $params[ 'relate_id' ] ] ])[ 'data' ];
|
||||||
/**
|
if (!empty($pay_info)) {
|
||||||
* 查询支付结果
|
$config_model = new ConfigModel();
|
||||||
*/
|
$pay_config = $config_model->getPayConfig($pay_info[ 'site_id' ])[ 'data' ][ 'value' ];
|
||||||
class PayOrderQuery
|
if (!empty($pay_config) && $pay_config[ 'pay_status' ] != 2) {
|
||||||
{
|
$pay_common = new PayModel($pay_info[ 'site_id' ]);
|
||||||
public function handle(array $params)
|
$pay_common->orderQuery($pay_info);
|
||||||
{
|
}
|
||||||
$pay_info = ( new Pay() )->getInfo([ [ 'id', '=', $params[ 'relate_id' ] ] ])[ 'data' ];
|
}
|
||||||
if (!empty($pay_info)) {
|
}
|
||||||
$config_model = new ConfigModel();
|
}
|
||||||
$pay_config = $config_model->getPayConfig($pay_info[ 'site_id' ])[ 'data' ][ 'value' ];
|
|
||||||
if (!empty($pay_config) && $pay_config[ 'pay_status' ] != 2) {
|
|
||||||
$pay_common = new PayModel($pay_info[ 'site_id' ]);
|
|
||||||
$pay_common->orderQuery($pay_info);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,33 +1,25 @@
|
|||||||
<?php
|
<?php
|
||||||
/**
|
|
||||||
|
namespace addon\alipay\event;
|
||||||
|
|
||||||
|
use addon\alipay\model\Pay as PayModel;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 原路退款
|
||||||
*/
|
*/
|
||||||
|
class PayRefund
|
||||||
namespace addon\alipay\event;
|
{
|
||||||
|
/**
|
||||||
use addon\alipay\model\Pay as PayModel;
|
* 关闭支付
|
||||||
|
*/
|
||||||
/**
|
public function handle($params)
|
||||||
* 原路退款
|
{
|
||||||
*/
|
if ($params[ "pay_info" ][ "pay_type" ] == "alipay") {
|
||||||
class PayRefund
|
$mch_info = empty($params[ 'pay_info' ][ 'mch_info' ]) ? [] : json_decode($params[ 'pay_info' ][ 'mch_info' ], true);
|
||||||
{
|
|
||||||
/**
|
$pay_model = new PayModel($params[ 'site_id' ], $mch_info[ 'is_aliapp' ] ?? 0);
|
||||||
* 关闭支付
|
$result = $pay_model->refund($params);
|
||||||
*/
|
return $result;
|
||||||
public function handle($params)
|
}
|
||||||
{
|
}
|
||||||
if ($params[ "pay_info" ][ "pay_type" ] == "alipay") {
|
|
||||||
$mch_info = empty($params[ 'pay_info' ][ 'mch_info' ]) ? [] : json_decode($params[ 'pay_info' ][ 'mch_info' ], true);
|
|
||||||
|
|
||||||
$pay_model = new PayModel($params[ 'site_id' ], $mch_info[ 'is_aliapp' ] ?? 0);
|
|
||||||
$result = $pay_model->refund($params);
|
|
||||||
return $result;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@@ -1,42 +1,34 @@
|
|||||||
<?php
|
<?php
|
||||||
/**
|
|
||||||
|
namespace addon\alipay\event;
|
||||||
|
|
||||||
|
use addon\alipay\model\Pay;
|
||||||
|
use addon\alipay\model\Config;
|
||||||
|
|
||||||
|
class PayTransfer
|
||||||
*/
|
{
|
||||||
|
public function handle(array $params)
|
||||||
namespace addon\alipay\event;
|
{
|
||||||
|
if ($params[ 'transfer_type' ] == 'alipay') {
|
||||||
use addon\alipay\model\Pay;
|
$pay = new Pay($params[ 'site_id' ]);
|
||||||
use addon\alipay\model\Config;
|
|
||||||
|
$config_model = new Config();
|
||||||
class PayTransfer
|
$config_result = $config_model->getPayConfig($params[ 'site_id' ]);
|
||||||
{
|
$config = $config_result[ "data" ];
|
||||||
public function handle(array $params)
|
if (!empty($config[ 'value' ])) {
|
||||||
{
|
$config_info = $config[ "value" ];
|
||||||
if ($params[ 'transfer_type' ] == 'alipay') {
|
$countersign_type = $config_info['countersign_type'] ?? 0;
|
||||||
$pay = new Pay($params[ 'site_id' ]);
|
if ($countersign_type == 0) {
|
||||||
|
$res = $pay->payTransfer($params);
|
||||||
$config_model = new Config();
|
return $res;
|
||||||
$config_result = $config_model->getPayConfig($params[ 'site_id' ]);
|
} else {
|
||||||
$config = $config_result[ "data" ];
|
$res = $pay->payNewTransfer($params);
|
||||||
if (!empty($config[ 'value' ])) {
|
return $res;
|
||||||
$config_info = $config[ "value" ];
|
}
|
||||||
$countersign_type = $config_info['countersign_type'] ?? 0;
|
} else {
|
||||||
if ($countersign_type == 0) {
|
$res = $pay->payTransfer($params);
|
||||||
$res = $pay->payTransfer($params);
|
return $res;
|
||||||
return $res;
|
}
|
||||||
} else {
|
}
|
||||||
$res = $pay->payNewTransfer($params);
|
}
|
||||||
return $res;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
$res = $pay->payTransfer($params);
|
|
||||||
return $res;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@@ -1,51 +1,43 @@
|
|||||||
<?php
|
<?php
|
||||||
/**
|
|
||||||
|
namespace addon\alipay\event;
|
||||||
|
|
||||||
|
use addon\alipay\model\Config;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 支付方式 (后台调用)
|
||||||
*/
|
*/
|
||||||
|
class PayType
|
||||||
namespace addon\alipay\event;
|
{
|
||||||
|
/**
|
||||||
use addon\alipay\model\Config;
|
* 支付方式及配置
|
||||||
|
*/
|
||||||
/**
|
public function handle($param)
|
||||||
* 支付方式 (后台调用)
|
{
|
||||||
*/
|
$app_type = $param['app_type'] ?? '';
|
||||||
class PayType
|
if (!empty($app_type)) {
|
||||||
{
|
if (!in_array($app_type, [ "h5", "app", "pc", "aliapp", 'wechat' ])) {
|
||||||
/**
|
return '';
|
||||||
* 支付方式及配置
|
}
|
||||||
*/
|
|
||||||
public function handle($param)
|
if ($app_type != 'aliapp') {
|
||||||
{
|
$config_model = new Config();
|
||||||
$app_type = $param['app_type'] ?? '';
|
$config_result = $config_model->getPayConfig($param[ 'site_id' ]);
|
||||||
if (!empty($app_type)) {
|
$config = $config_result[ "data" ][ "value" ] ?? [];
|
||||||
if (!in_array($app_type, [ "h5", "app", "pc", "aliapp", 'wechat' ])) {
|
$pay_status = $config[ "pay_status" ] ?? 0;
|
||||||
return '';
|
if ($pay_status == 0) {
|
||||||
}
|
return '';
|
||||||
|
}
|
||||||
if ($app_type != 'aliapp') {
|
}
|
||||||
$config_model = new Config();
|
}
|
||||||
$config_result = $config_model->getPayConfig($param[ 'site_id' ]);
|
$info = array (
|
||||||
$config = $config_result[ "data" ][ "value" ] ?? [];
|
"pay_type" => "alipay",
|
||||||
$pay_status = $config[ "pay_status" ] ?? 0;
|
"pay_type_name" => "支付宝支付",
|
||||||
if ($pay_status == 0) {
|
"edit_url" => "alipay://shop/pay/config",
|
||||||
return '';
|
"shop_url" => "alipay://shop/pay/config",
|
||||||
}
|
"logo" => "addon/alipay/icon.png",
|
||||||
}
|
"desc" => "支付宝网站(www.alipay.com) 是国内先进的网上支付平台。"
|
||||||
}
|
);
|
||||||
$info = array (
|
return $info;
|
||||||
"pay_type" => "alipay",
|
}
|
||||||
"pay_type_name" => "支付宝支付",
|
|
||||||
"edit_url" => "alipay://shop/pay/config",
|
|
||||||
"shop_url" => "alipay://shop/pay/config",
|
|
||||||
"logo" => "addon/alipay/icon.png",
|
|
||||||
"desc" => "支付宝网站(www.alipay.com) 是国内先进的网上支付平台。"
|
|
||||||
);
|
|
||||||
return $info;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@@ -1,39 +1,31 @@
|
|||||||
<?php
|
<?php
|
||||||
/**
|
|
||||||
|
namespace addon\alipay\event;
|
||||||
|
|
||||||
|
use addon\alipay\model\Config;
|
||||||
|
|
||||||
|
class TransferType
|
||||||
|
{
|
||||||
*/
|
public function handle(array $param)
|
||||||
|
{
|
||||||
namespace addon\alipay\event;
|
|
||||||
|
$app_type = $param['app_type'] ?? '';
|
||||||
use addon\alipay\model\Config;
|
if (!empty($app_type)) {
|
||||||
|
if (!in_array($app_type, [ "h5", "app", "pc", "aliapp" ])) {
|
||||||
class TransferType
|
return '';
|
||||||
{
|
}
|
||||||
public function handle(array $param)
|
$config_model = new Config();
|
||||||
{
|
$config_result = $config_model->getPayConfig($param[ 'site_id' ]);
|
||||||
|
$config = $config_result[ "data" ][ "value" ] ?? [];
|
||||||
$app_type = $param['app_type'] ?? '';
|
$transfer_status = $config[ "transfer_status" ] ?? 0;
|
||||||
if (!empty($app_type)) {
|
if ($transfer_status == 0) {
|
||||||
if (!in_array($app_type, [ "h5", "app", "pc", "aliapp" ])) {
|
return '';
|
||||||
return '';
|
}
|
||||||
}
|
}
|
||||||
$config_model = new Config();
|
$info = array (
|
||||||
$config_result = $config_model->getPayConfig($param[ 'site_id' ]);
|
"type" => "alipay",
|
||||||
$config = $config_result[ "data" ][ "value" ] ?? [];
|
"type_name" => "支付宝",
|
||||||
$transfer_status = $config[ "transfer_status" ] ?? 0;
|
);
|
||||||
if ($transfer_status == 0) {
|
return $info;
|
||||||
return '';
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
$info = array (
|
|
||||||
"type" => "alipay",
|
|
||||||
"type_name" => "支付宝",
|
|
||||||
);
|
|
||||||
return $info;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@@ -1,26 +1,17 @@
|
|||||||
<?php
|
<?php
|
||||||
/**
|
|
||||||
|
namespace addon\alipay\event;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 应用卸载
|
||||||
|
*/
|
||||||
|
class UnInstall
|
||||||
|
{
|
||||||
*/
|
/**
|
||||||
|
* 执行卸载
|
||||||
namespace addon\alipay\event;
|
*/
|
||||||
|
public function handle()
|
||||||
/**
|
{
|
||||||
* 应用卸载
|
return error(-1, "系统插件不得删除");
|
||||||
*/
|
}
|
||||||
class UnInstall
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* 执行卸载
|
|
||||||
*/
|
|
||||||
public function handle()
|
|
||||||
{
|
|
||||||
return error(-1, "系统插件不得删除");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@@ -1,96 +1,88 @@
|
|||||||
<?php
|
<?php
|
||||||
/**
|
|
||||||
|
namespace addon\alipay\model;
|
||||||
|
|
||||||
|
use app\model\system\Config as ConfigModel;
|
||||||
|
use app\model\BaseModel;
|
||||||
|
|
||||||
|
/**
|
||||||
*/
|
* 支付宝支付配置
|
||||||
|
*/
|
||||||
namespace addon\alipay\model;
|
class Config extends BaseModel
|
||||||
|
{
|
||||||
use app\model\system\Config as ConfigModel;
|
|
||||||
use app\model\BaseModel;
|
private $encrypt = '******';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 支付宝支付配置
|
* 设置支付配置
|
||||||
*/
|
* @param $data
|
||||||
class Config extends BaseModel
|
* @param int $site_id
|
||||||
{
|
* @param string $app_module
|
||||||
|
* @return array
|
||||||
private $encrypt = '******';
|
*/
|
||||||
|
public function setPayConfig($data, $site_id = 0, $app_module = 'shop')
|
||||||
/**
|
{
|
||||||
* 设置支付配置
|
$config = new ConfigModel();
|
||||||
* @param $data
|
|
||||||
* @param int $site_id
|
// 未加密前的数据
|
||||||
* @param string $app_module
|
$original_config = $this->getPayConfig($site_id)[ 'data' ][ 'value' ];
|
||||||
* @return array
|
|
||||||
*/
|
// 检测数据是否发生变化,如果没有变化,则保持未加密前的数据
|
||||||
public function setPayConfig($data, $site_id = 0, $app_module = 'shop')
|
if (!empty($data[ 'private_key' ]) && $data[ 'private_key' ] == $this->encrypt) {
|
||||||
{
|
$data[ 'private_key' ] = $original_config[ 'private_key' ]; // 应用私钥
|
||||||
$config = new ConfigModel();
|
}
|
||||||
|
if (!empty($data[ 'public_key' ]) && $data[ 'public_key' ] == $this->encrypt) {
|
||||||
// 未加密前的数据
|
$data[ 'public_key' ] = $original_config[ 'public_key' ]; // 应用公钥
|
||||||
$original_config = $this->getPayConfig($site_id)[ 'data' ][ 'value' ];
|
}
|
||||||
|
if (!empty($data[ 'alipay_public_key' ]) && $data[ 'alipay_public_key' ] == $this->encrypt) {
|
||||||
// 检测数据是否发生变化,如果没有变化,则保持未加密前的数据
|
$data[ 'alipay_public_key' ] = $original_config[ 'alipay_public_key' ]; // 支付宝公钥
|
||||||
if (!empty($data[ 'private_key' ]) && $data[ 'private_key' ] == $this->encrypt) {
|
}
|
||||||
$data[ 'private_key' ] = $original_config[ 'private_key' ]; // 应用私钥
|
if (!empty($data[ 'public_key_crt' ]) && $data[ 'public_key_crt' ] == $this->encrypt) {
|
||||||
}
|
$data[ 'public_key_crt' ] = $original_config[ 'public_key_crt' ]; // 应用公钥证书
|
||||||
if (!empty($data[ 'public_key' ]) && $data[ 'public_key' ] == $this->encrypt) {
|
}
|
||||||
$data[ 'public_key' ] = $original_config[ 'public_key' ]; // 应用公钥
|
if (!empty($data[ 'alipay_public_key_crt' ]) && $data[ 'alipay_public_key_crt' ] == $this->encrypt) {
|
||||||
}
|
$data[ 'alipay_public_key_crt' ] = $original_config[ 'alipay_public_key_crt' ]; // 支付宝公钥证书
|
||||||
if (!empty($data[ 'alipay_public_key' ]) && $data[ 'alipay_public_key' ] == $this->encrypt) {
|
}
|
||||||
$data[ 'alipay_public_key' ] = $original_config[ 'alipay_public_key' ]; // 支付宝公钥
|
if (!empty($data[ 'alipay_with_crt' ]) && $data[ 'alipay_with_crt' ] == $this->encrypt) {
|
||||||
}
|
$data[ 'alipay_with_crt' ] = $original_config[ 'alipay_with_crt' ]; // 支付宝根证书
|
||||||
if (!empty($data[ 'public_key_crt' ]) && $data[ 'public_key_crt' ] == $this->encrypt) {
|
}
|
||||||
$data[ 'public_key_crt' ] = $original_config[ 'public_key_crt' ]; // 应用公钥证书
|
|
||||||
}
|
$res = $config->setConfig($data, '支付宝支付配置', 1, [ [ 'site_id', '=', $site_id ], [ 'app_module', '=', $app_module ], [ 'config_key', '=', 'ALI_PAY_CONFIG' ] ]);
|
||||||
if (!empty($data[ 'alipay_public_key_crt' ]) && $data[ 'alipay_public_key_crt' ] == $this->encrypt) {
|
return $res;
|
||||||
$data[ 'alipay_public_key_crt' ] = $original_config[ 'alipay_public_key_crt' ]; // 支付宝公钥证书
|
}
|
||||||
}
|
|
||||||
if (!empty($data[ 'alipay_with_crt' ]) && $data[ 'alipay_with_crt' ] == $this->encrypt) {
|
/**
|
||||||
$data[ 'alipay_with_crt' ] = $original_config[ 'alipay_with_crt' ]; // 支付宝根证书
|
* 获取支付配置
|
||||||
}
|
* @param int $site_id
|
||||||
|
* @param string $app_module
|
||||||
$res = $config->setConfig($data, '支付宝支付配置', 1, [ [ 'site_id', '=', $site_id ], [ 'app_module', '=', $app_module ], [ 'config_key', '=', 'ALI_PAY_CONFIG' ] ]);
|
* @param bool $need_encrypt 是否需要加密数据,true:加密、false:不加密
|
||||||
return $res;
|
* @return array
|
||||||
}
|
*/
|
||||||
|
public function getPayConfig($site_id = 0, $app_module = 'shop', $need_encrypt = false)
|
||||||
/**
|
{
|
||||||
* 获取支付配置
|
$config = new ConfigModel();
|
||||||
* @param int $site_id
|
$res = $config->getConfig([ [ 'site_id', '=', $site_id ], [ 'app_module', '=', $app_module ], [ 'config_key', '=', 'ALI_PAY_CONFIG' ] ]);
|
||||||
* @param string $app_module
|
if ($need_encrypt) {
|
||||||
* @param bool $need_encrypt 是否需要加密数据,true:加密、false:不加密
|
// 加密敏感信息
|
||||||
* @return array
|
if (!empty($res[ 'data' ][ 'value' ][ 'private_key' ])) {
|
||||||
*/
|
$res[ 'data' ][ 'value' ][ 'private_key' ] = $this->encrypt; // 应用私钥
|
||||||
public function getPayConfig($site_id = 0, $app_module = 'shop', $need_encrypt = false)
|
}
|
||||||
{
|
if (!empty($res[ 'data' ][ 'value' ][ 'public_key' ])) {
|
||||||
$config = new ConfigModel();
|
$res[ 'data' ][ 'value' ][ 'public_key' ] = $this->encrypt; // 应用公钥
|
||||||
$res = $config->getConfig([ [ 'site_id', '=', $site_id ], [ 'app_module', '=', $app_module ], [ 'config_key', '=', 'ALI_PAY_CONFIG' ] ]);
|
}
|
||||||
if ($need_encrypt) {
|
if (!empty($res[ 'data' ][ 'value' ][ 'alipay_public_key' ])) {
|
||||||
// 加密敏感信息
|
$res[ 'data' ][ 'value' ][ 'alipay_public_key' ] = $this->encrypt; // 支付宝公钥
|
||||||
if (!empty($res[ 'data' ][ 'value' ][ 'private_key' ])) {
|
}
|
||||||
$res[ 'data' ][ 'value' ][ 'private_key' ] = $this->encrypt; // 应用私钥
|
if (!empty($res[ 'data' ][ 'value' ][ 'public_key_crt' ])) {
|
||||||
}
|
$res[ 'data' ][ 'value' ][ 'public_key_crt' ] = $this->encrypt; // 应用公钥证书
|
||||||
if (!empty($res[ 'data' ][ 'value' ][ 'public_key' ])) {
|
}
|
||||||
$res[ 'data' ][ 'value' ][ 'public_key' ] = $this->encrypt; // 应用公钥
|
if (!empty($res[ 'data' ][ 'value' ][ 'alipay_public_key_crt' ])) {
|
||||||
}
|
$res[ 'data' ][ 'value' ][ 'alipay_public_key_crt' ] = $this->encrypt; // 支付宝公钥证书
|
||||||
if (!empty($res[ 'data' ][ 'value' ][ 'alipay_public_key' ])) {
|
}
|
||||||
$res[ 'data' ][ 'value' ][ 'alipay_public_key' ] = $this->encrypt; // 支付宝公钥
|
if (!empty($res[ 'data' ][ 'value' ][ 'alipay_with_crt' ])) {
|
||||||
}
|
$res[ 'data' ][ 'value' ][ 'alipay_with_crt' ] = $this->encrypt; // 支付宝根证书
|
||||||
if (!empty($res[ 'data' ][ 'value' ][ 'public_key_crt' ])) {
|
}
|
||||||
$res[ 'data' ][ 'value' ][ 'public_key_crt' ] = $this->encrypt; // 应用公钥证书
|
}
|
||||||
}
|
return $res;
|
||||||
if (!empty($res[ 'data' ][ 'value' ][ 'alipay_public_key_crt' ])) {
|
}
|
||||||
$res[ 'data' ][ 'value' ][ 'alipay_public_key_crt' ] = $this->encrypt; // 支付宝公钥证书
|
|
||||||
}
|
|
||||||
if (!empty($res[ 'data' ][ 'value' ][ 'alipay_with_crt' ])) {
|
|
||||||
$res[ 'data' ][ 'value' ][ 'alipay_with_crt' ] = $this->encrypt; // 支付宝根证书
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return $res;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@@ -1,486 +1,478 @@
|
|||||||
<?php
|
<?php
|
||||||
/**
|
|
||||||
|
namespace addon\alipay\model;
|
||||||
|
|
||||||
|
use addon\alipay\data\sdk\AopClient;
|
||||||
|
use addon\alipay\data\sdk\request\AlipayFundTransToaccountTransferRequest;
|
||||||
|
use addon\alipay\data\sdk\request\AlipayTradeAppPayRequest;
|
||||||
|
use addon\alipay\data\sdk\request\AlipayTradeCloseRequest;
|
||||||
*/
|
use addon\alipay\data\sdk\request\AlipayTradeCreateRequest;
|
||||||
|
use addon\alipay\data\sdk\request\AlipayTradePagePayRequest;
|
||||||
namespace addon\alipay\model;
|
use addon\alipay\data\sdk\request\AlipayTradeRefundRequest;
|
||||||
|
use addon\alipay\data\sdk\request\AlipayTradeWapPayRequest;
|
||||||
use addon\alipay\data\sdk\AopClient;
|
use addon\alipay\data\sdk\request\AlipayTradePrecreateRequest;
|
||||||
use addon\alipay\data\sdk\request\AlipayFundTransToaccountTransferRequest;
|
use addon\alipay\data\sdk\request\AlipayTradePayRequest;
|
||||||
use addon\alipay\data\sdk\request\AlipayTradeAppPayRequest;
|
use addon\alipay\data\sdk\request\AlipayTradeQueryRequest;
|
||||||
use addon\alipay\data\sdk\request\AlipayTradeCloseRequest;
|
use app\model\BaseModel;
|
||||||
use addon\alipay\data\sdk\request\AlipayTradeCreateRequest;
|
use app\model\system\Cron;
|
||||||
use addon\alipay\data\sdk\request\AlipayTradePagePayRequest;
|
use app\model\system\Pay as PayCommon;
|
||||||
use addon\alipay\data\sdk\request\AlipayTradeRefundRequest;
|
use addon\alipay\data\sdk\request\AlipayFundTransUniTransferRequest;
|
||||||
use addon\alipay\data\sdk\request\AlipayTradeWapPayRequest;
|
use addon\alipay\data\sdk\AopCertClient;
|
||||||
use addon\alipay\data\sdk\request\AlipayTradePrecreateRequest;
|
use app\model\system\Pay as PayModel;
|
||||||
use addon\alipay\data\sdk\request\AlipayTradePayRequest;
|
use addon\aliapp\model\Config as AliappConfig;
|
||||||
use addon\alipay\data\sdk\request\AlipayTradeQueryRequest;
|
use think\facade\Log;
|
||||||
use app\model\BaseModel;
|
|
||||||
use app\model\system\Cron;
|
/**
|
||||||
use app\model\system\Pay as PayCommon;
|
* 支付宝支付配置
|
||||||
use addon\alipay\data\sdk\request\AlipayFundTransUniTransferRequest;
|
*/
|
||||||
use addon\alipay\data\sdk\AopCertClient;
|
class Pay extends BaseModel
|
||||||
use app\model\system\Pay as PayModel;
|
{
|
||||||
use addon\aliapp\model\Config as AliappConfig;
|
|
||||||
use think\facade\Log;
|
public $aop;
|
||||||
|
|
||||||
/**
|
private $is_aliapp = 0;
|
||||||
* 支付宝支付配置
|
|
||||||
*/
|
/**
|
||||||
class Pay extends BaseModel
|
*
|
||||||
{
|
* @param $site_id
|
||||||
|
* @param int $is_aliapp 是否是小程序
|
||||||
public $aop;
|
*/
|
||||||
|
function __construct($site_id, $is_aliapp = 0)
|
||||||
private $is_aliapp = 0;
|
{
|
||||||
|
$this->is_aliapp = $is_aliapp;
|
||||||
/**
|
|
||||||
*
|
try {
|
||||||
* @param $site_id
|
// 获取支付宝支付参数(统一支付到平台账户)
|
||||||
* @param int $is_aliapp 是否是小程序
|
if ($is_aliapp) {
|
||||||
*/
|
$config_info = ( new AliappConfig() )->getAliappConfig($site_id)[ 'data' ][ 'value' ];
|
||||||
function __construct($site_id, $is_aliapp = 0)
|
} else {
|
||||||
{
|
$config_info = ( new Config() )->getPayConfig($site_id)[ 'data' ][ 'value' ];
|
||||||
$this->is_aliapp = $is_aliapp;
|
}
|
||||||
|
|
||||||
try {
|
if (!empty($config_info)) {
|
||||||
// 获取支付宝支付参数(统一支付到平台账户)
|
$countersign_type = $config_info[ 'countersign_type' ] ?? 0;
|
||||||
if ($is_aliapp) {
|
|
||||||
$config_info = ( new AliappConfig() )->getAliappConfig($site_id)[ 'data' ][ 'value' ];
|
if ($countersign_type == 1) {
|
||||||
} else {
|
$appCertPath = $config_info[ "public_key_crt" ] ?? "";
|
||||||
$config_info = ( new Config() )->getPayConfig($site_id)[ 'data' ][ 'value' ];
|
$alipayCertPath = $config_info[ "alipay_public_key_crt" ] ?? "";
|
||||||
}
|
$rootCertPath = $config_info[ "alipay_with_crt" ] ?? "";
|
||||||
|
|
||||||
if (!empty($config_info)) {
|
$this->aop = new AopCertClient();
|
||||||
$countersign_type = $config_info[ 'countersign_type' ] ?? 0;
|
//调用getPublicKey从支付宝公钥证书中提取公钥
|
||||||
|
$this->aop->alipayrsaPublicKey = $this->aop->getPublicKey($alipayCertPath);
|
||||||
if ($countersign_type == 1) {
|
//是否校验自动下载的支付宝公钥证书,如果开启校验要保证支付宝根证书在有效期内
|
||||||
$appCertPath = $config_info[ "public_key_crt" ] ?? "";
|
$this->aop->isCheckAlipayPublicCert = false;
|
||||||
$alipayCertPath = $config_info[ "alipay_public_key_crt" ] ?? "";
|
//调用getCertSN获取证书序列号
|
||||||
$rootCertPath = $config_info[ "alipay_with_crt" ] ?? "";
|
$this->aop->appCertSN = $this->aop->getCertSN($appCertPath);
|
||||||
|
//调用getRootCertSN获取支付宝根证书序列号
|
||||||
$this->aop = new AopCertClient();
|
$this->aop->alipayRootCertSN = $this->aop->getRootCertSN($rootCertPath);
|
||||||
//调用getPublicKey从支付宝公钥证书中提取公钥
|
|
||||||
$this->aop->alipayrsaPublicKey = $this->aop->getPublicKey($alipayCertPath);
|
} else {
|
||||||
//是否校验自动下载的支付宝公钥证书,如果开启校验要保证支付宝根证书在有效期内
|
// 获取支付宝支付参数(统一支付到平台账户)
|
||||||
$this->aop->isCheckAlipayPublicCert = false;
|
$this->aop = new AopClient();
|
||||||
//调用getCertSN获取证书序列号
|
$this->aop->alipayrsaPublicKey = $config_info[ 'public_key' ] ?? "";
|
||||||
$this->aop->appCertSN = $this->aop->getCertSN($appCertPath);
|
$this->aop->alipayPublicKey = $config_info[ 'alipay_public_key' ] ?? "";
|
||||||
//调用getRootCertSN获取支付宝根证书序列号
|
}
|
||||||
$this->aop->alipayRootCertSN = $this->aop->getRootCertSN($rootCertPath);
|
$this->aop->appId = $config_info[ "app_id" ] ?? "";
|
||||||
|
$this->aop->rsaPrivateKey = $config_info[ 'private_key' ] ?? "";
|
||||||
} else {
|
$this->aop->gatewayUrl = 'https://openapi.alipay.com/gateway.do';
|
||||||
// 获取支付宝支付参数(统一支付到平台账户)
|
$this->aop->apiVersion = '1.0';
|
||||||
$this->aop = new AopClient();
|
$this->aop->signType = 'RSA2';
|
||||||
$this->aop->alipayrsaPublicKey = $config_info[ 'public_key' ] ?? "";
|
$this->aop->postCharset = 'UTF-8';
|
||||||
$this->aop->alipayPublicKey = $config_info[ 'alipay_public_key' ] ?? "";
|
$this->aop->format = 'json';
|
||||||
}
|
|
||||||
$this->aop->appId = $config_info[ "app_id" ] ?? "";
|
}
|
||||||
$this->aop->rsaPrivateKey = $config_info[ 'private_key' ] ?? "";
|
// else{
|
||||||
$this->aop->gatewayUrl = 'https://openapi.alipay.com/gateway.do';
|
// return $this->error('', '支付宝支付未配置');
|
||||||
$this->aop->apiVersion = '1.0';
|
// }
|
||||||
$this->aop->signType = 'RSA2';
|
|
||||||
$this->aop->postCharset = 'UTF-8';
|
} catch (\Exception $e) {
|
||||||
$this->aop->format = 'json';
|
return $this->error('', '支付宝配置错误');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
// else{
|
|
||||||
// return $this->error('', '支付宝支付未配置');
|
/**
|
||||||
// }
|
* 生成支付
|
||||||
|
* @param $param
|
||||||
} catch (\Exception $e) {
|
* @return array
|
||||||
return $this->error('', '支付宝配置错误');
|
*/
|
||||||
}
|
public function pay($param)
|
||||||
}
|
{
|
||||||
|
//构造要请求的参数数组,无需改动
|
||||||
/**
|
$parameter = array (
|
||||||
* 生成支付
|
"out_trade_no" => $param[ "out_trade_no" ],
|
||||||
* @param $param
|
"subject" => str_sub($param[ "pay_body" ], 15),
|
||||||
* @return array
|
"total_amount" => (float) $param[ "pay_money" ],
|
||||||
*/
|
"body" => str_sub($param[ "pay_body" ], 60),
|
||||||
public function pay($param)
|
"product_code" => 'FAST_INSTANT_TRADE_PAY',
|
||||||
{
|
);
|
||||||
//构造要请求的参数数组,无需改动
|
|
||||||
$parameter = array (
|
switch ( $param[ "app_type" ] ) {
|
||||||
"out_trade_no" => $param[ "out_trade_no" ],
|
case "h5":
|
||||||
"subject" => str_sub($param[ "pay_body" ], 15),
|
$request = new AlipayTradeWapPayRequest();
|
||||||
"total_amount" => (float) $param[ "pay_money" ],
|
break;
|
||||||
"body" => str_sub($param[ "pay_body" ], 60),
|
case "pc":
|
||||||
"product_code" => 'FAST_INSTANT_TRADE_PAY',
|
$request = new AlipayTradePagePayRequest();
|
||||||
);
|
break;
|
||||||
|
case "app":
|
||||||
switch ( $param[ "app_type" ] ) {
|
$request = new AlipayTradeAppPayRequest();
|
||||||
case "h5":
|
break;
|
||||||
$request = new AlipayTradeWapPayRequest();
|
case 'wechat':
|
||||||
break;
|
$request = new AlipayTradeWapPayRequest();
|
||||||
case "pc":
|
break;
|
||||||
$request = new AlipayTradePagePayRequest();
|
case 'cashier':
|
||||||
break;
|
$request = new AlipayTradePrecreateRequest();
|
||||||
case "app":
|
break;
|
||||||
$request = new AlipayTradeAppPayRequest();
|
case 'aliapp':
|
||||||
break;
|
$parameter[ 'product_code' ] = 'FACE_TO_FACE_PAYMENT';
|
||||||
case 'wechat':
|
|
||||||
$request = new AlipayTradeWapPayRequest();
|
$member_info = model('member')->getInfo([ [ "member_id", "=", $param[ "member_id" ] ] ], 'ali_openid');
|
||||||
break;
|
if (empty($member_info)) return $this->error(-1, '未获取到会员信息');
|
||||||
case 'cashier':
|
|
||||||
$request = new AlipayTradePrecreateRequest();
|
$parameter[ 'buyer_id' ] = $member_info[ 'ali_openid' ];
|
||||||
break;
|
$request = new AlipayTradeCreateRequest();
|
||||||
case 'aliapp':
|
break;
|
||||||
$parameter[ 'product_code' ] = 'FACE_TO_FACE_PAYMENT';
|
}
|
||||||
|
|
||||||
$member_info = model('member')->getInfo([ [ "member_id", "=", $param[ "member_id" ] ] ], 'ali_openid');
|
$parameter = json_encode($parameter);
|
||||||
if (empty($member_info)) return $this->error(-1, '未获取到会员信息');
|
$request->setBizContent($parameter);
|
||||||
|
$request->SetReturnUrl($param[ "return_url" ]);
|
||||||
$parameter[ 'buyer_id' ] = $member_info[ 'ali_openid' ];
|
$request->SetNotifyUrl($param[ "notify_url" ]);
|
||||||
$request = new AlipayTradeCreateRequest();
|
|
||||||
break;
|
///绑定商户数据
|
||||||
}
|
$pay_model = new PayModel();
|
||||||
|
$pay_model->bindMchPay($param[ "out_trade_no" ], [ "is_aliapp" => $this->is_aliapp ]);
|
||||||
$parameter = json_encode($parameter);
|
|
||||||
$request->setBizContent($parameter);
|
try {
|
||||||
$request->SetReturnUrl($param[ "return_url" ]);
|
if ($param[ "app_type" ] == 'h5' || $param[ "app_type" ] == 'wechat' || $param[ "app_type" ] == 'pc') {
|
||||||
$request->SetNotifyUrl($param[ "notify_url" ]);
|
$result = $this->aop->pageExecute($request, 'get');
|
||||||
|
return $this->success([
|
||||||
///绑定商户数据
|
'type' => 'url',
|
||||||
$pay_model = new PayModel();
|
'data' => $result
|
||||||
$pay_model->bindMchPay($param[ "out_trade_no" ], [ "is_aliapp" => $this->is_aliapp ]);
|
]);
|
||||||
|
} elseif ($param[ "app_type" ] == 'app') {
|
||||||
try {
|
$result = $this->aop->sdkExecute($request);
|
||||||
if ($param[ "app_type" ] == 'h5' || $param[ "app_type" ] == 'wechat' || $param[ "app_type" ] == 'pc') {
|
if (strpos(get_class($this->aop), 'AopClient') !== false) {
|
||||||
$result = $this->aop->pageExecute($request, 'get');
|
return $this->success([
|
||||||
return $this->success([
|
'type' => 'url',
|
||||||
'type' => 'url',
|
'data' => $result
|
||||||
'data' => $result
|
]);
|
||||||
]);
|
}
|
||||||
} elseif ($param[ "app_type" ] == 'app') {
|
} else {
|
||||||
$result = $this->aop->sdkExecute($request);
|
$result = $this->aop->execute($request);
|
||||||
if (strpos(get_class($this->aop), 'AopClient') !== false) {
|
}
|
||||||
return $this->success([
|
if ($result === false) return $this->error('', '支付宝发起支付失败');
|
||||||
'type' => 'url',
|
} catch (\Exception $e) {
|
||||||
'data' => $result
|
return $this->error('', $e->getMessage());
|
||||||
]);
|
}
|
||||||
}
|
|
||||||
} else {
|
$responseNode = str_replace(".", "_", $request->getApiMethodName()) . "_response";
|
||||||
$result = $this->aop->execute($request);
|
$resultCode = $result->$responseNode->code;
|
||||||
}
|
if (!empty($resultCode) && $resultCode == 10000) {
|
||||||
if ($result === false) return $this->error('', '支付宝发起支付失败');
|
switch ( $param[ "app_type" ] ) {
|
||||||
} catch (\Exception $e) {
|
case 'cashier':
|
||||||
return $this->error('', $e->getMessage());
|
return $this->success([
|
||||||
}
|
'type' => 'qrcode',
|
||||||
|
'data' => [
|
||||||
$responseNode = str_replace(".", "_", $request->getApiMethodName()) . "_response";
|
'qrcode' => $result->$responseNode->qr_code
|
||||||
$resultCode = $result->$responseNode->code;
|
]
|
||||||
if (!empty($resultCode) && $resultCode == 10000) {
|
]);
|
||||||
switch ( $param[ "app_type" ] ) {
|
break;
|
||||||
case 'cashier':
|
case 'aliapp':
|
||||||
return $this->success([
|
return $this->success([
|
||||||
'type' => 'qrcode',
|
'type' => 'data',
|
||||||
'data' => [
|
'data' => [
|
||||||
'qrcode' => $result->$responseNode->qr_code
|
'orderInfo' => $result->$responseNode->trade_no
|
||||||
]
|
]
|
||||||
]);
|
]);
|
||||||
break;
|
break;
|
||||||
case 'aliapp':
|
default:
|
||||||
return $this->success([
|
return $this->success();
|
||||||
'type' => 'data',
|
}
|
||||||
'data' => [
|
} else {
|
||||||
'orderInfo' => $result->$responseNode->trade_no
|
return $this->error("", $result->$responseNode->sub_msg);
|
||||||
]
|
}
|
||||||
]);
|
}
|
||||||
break;
|
|
||||||
default:
|
/**
|
||||||
return $this->success();
|
* 支付关闭
|
||||||
}
|
* @param $param
|
||||||
} else {
|
* @return array
|
||||||
return $this->error("", $result->$responseNode->sub_msg);
|
* @throws \think\Exception
|
||||||
}
|
*/
|
||||||
}
|
public function close($param)
|
||||||
|
{
|
||||||
/**
|
$parameter = array (
|
||||||
* 支付关闭
|
"out_trade_no" => $param[ "out_trade_no" ]
|
||||||
* @param $param
|
);
|
||||||
* @return array
|
// 建立请求
|
||||||
* @throws \think\Exception
|
$request = new AlipayTradeCloseRequest();
|
||||||
*/
|
$request->setBizContent(json_encode($parameter));
|
||||||
public function close($param)
|
$result = $this->aop->execute($request);
|
||||||
{
|
$responseNode = str_replace(".", "_", $request->getApiMethodName()) . "_response";
|
||||||
$parameter = array (
|
$resultCode = $result->$responseNode->code;
|
||||||
"out_trade_no" => $param[ "out_trade_no" ]
|
if (!empty($resultCode) && $resultCode == 10000) {
|
||||||
);
|
return $this->success();
|
||||||
// 建立请求
|
} else {
|
||||||
$request = new AlipayTradeCloseRequest();
|
$sub_code = $result->$responseNode->sub_code;
|
||||||
$request->setBizContent(json_encode($parameter));
|
$data = [];
|
||||||
$result = $this->aop->execute($request);
|
if(in_array($sub_code, ['ACQ.TRADE_STATUS_ERROR', 'ACQ.REASON_TRADE_STATUS_INVALID', 'ACQ.REASON_ILLEGAL_STATUS'])){
|
||||||
$responseNode = str_replace(".", "_", $request->getApiMethodName()) . "_response";
|
$pay_order_result = $this->get($param[ "out_trade_no" ]);
|
||||||
$resultCode = $result->$responseNode->code;
|
if(!empty($pay_order_result) && $pay_order_result['code'] >= 0){
|
||||||
if (!empty($resultCode) && $resultCode == 10000) {
|
if($pay_order_result['data']['trade_status'] == 'TRADE_SUCCESS' || $pay_order_result['data']['trade_status'] == 'TRADE_FINISHED'){
|
||||||
return $this->success();
|
$data['is_paid'] = 1;
|
||||||
} else {
|
}
|
||||||
$sub_code = $result->$responseNode->sub_code;
|
}
|
||||||
$data = [];
|
}
|
||||||
if(in_array($sub_code, ['ACQ.TRADE_STATUS_ERROR', 'ACQ.REASON_TRADE_STATUS_INVALID', 'ACQ.REASON_ILLEGAL_STATUS'])){
|
|
||||||
$pay_order_result = $this->get($param[ "out_trade_no" ]);
|
|
||||||
if(!empty($pay_order_result) && $pay_order_result['code'] >= 0){
|
return $this->error($data, $result->$responseNode->sub_msg);
|
||||||
if($pay_order_result['data']['trade_status'] == 'TRADE_SUCCESS' || $pay_order_result['data']['trade_status'] == 'TRADE_FINISHED'){
|
}
|
||||||
$data['is_paid'] = 1;
|
}
|
||||||
}
|
|
||||||
}
|
/**
|
||||||
}
|
* 支付宝支付原路返回
|
||||||
|
* @param array $param 支付参数
|
||||||
|
* @return array
|
||||||
return $this->error($data, $result->$responseNode->sub_msg);
|
* @throws \think\Exception
|
||||||
}
|
*/
|
||||||
}
|
public function refund($param)
|
||||||
|
{
|
||||||
/**
|
$pay_info = $param[ "pay_info" ];
|
||||||
* 支付宝支付原路返回
|
$refund_no = $param[ "refund_no" ];
|
||||||
* @param array $param 支付参数
|
$out_trade_no = $pay_info[ "trade_no" ] ?? '';
|
||||||
* @return array
|
$refund_fee = $param[ "refund_fee" ];
|
||||||
* @throws \think\Exception
|
$parameter = array (
|
||||||
*/
|
'trade_no' => $out_trade_no,
|
||||||
public function refund($param)
|
'refund_amount' => sprintf("%.2f", $refund_fee),
|
||||||
{
|
'out_request_no' => $refund_no
|
||||||
$pay_info = $param[ "pay_info" ];
|
);
|
||||||
$refund_no = $param[ "refund_no" ];
|
// 建立请求
|
||||||
$out_trade_no = $pay_info[ "trade_no" ] ?? '';
|
$request = new AlipayTradeRefundRequest ();
|
||||||
$refund_fee = $param[ "refund_fee" ];
|
$request->setBizContent(json_encode($parameter));
|
||||||
$parameter = array (
|
$result = $this->aop->execute($request);
|
||||||
'trade_no' => $out_trade_no,
|
$responseNode = str_replace(".", "_", $request->getApiMethodName()) . "_response";
|
||||||
'refund_amount' => sprintf("%.2f", $refund_fee),
|
$resultCode = $result->$responseNode->code;
|
||||||
'out_request_no' => $refund_no
|
if (!empty($resultCode) && $resultCode == 10000) {
|
||||||
);
|
return $this->success();
|
||||||
// 建立请求
|
} else {
|
||||||
$request = new AlipayTradeRefundRequest ();
|
return $this->error("", $result->$responseNode->sub_msg);
|
||||||
$request->setBizContent(json_encode($parameter));
|
}
|
||||||
$result = $this->aop->execute($request);
|
}
|
||||||
$responseNode = str_replace(".", "_", $request->getApiMethodName()) . "_response";
|
|
||||||
$resultCode = $result->$responseNode->code;
|
/**
|
||||||
if (!empty($resultCode) && $resultCode == 10000) {
|
* 支付宝转账
|
||||||
return $this->success();
|
* @param $param
|
||||||
} else {
|
* @return array
|
||||||
return $this->error("", $result->$responseNode->sub_msg);
|
*/
|
||||||
}
|
public function payTransfer($param)
|
||||||
}
|
{
|
||||||
|
try {
|
||||||
/**
|
$config_model = new Config();
|
||||||
* 支付宝转账
|
$config_result = $config_model->getPayConfig($param[ 'site_id' ]);
|
||||||
* @param $param
|
if ($config_result[ 'code' ] < 0) return $config_result;
|
||||||
* @return array
|
$config = $config_result[ 'data' ][ 'value' ];
|
||||||
*/
|
if (empty($config)) return $this->error([], '未配置支付宝支付');
|
||||||
public function payTransfer($param)
|
if (!$config[ 'transfer_status' ]) return $this->error([], '未启用支付宝转账');
|
||||||
{
|
|
||||||
try {
|
$parameter = [
|
||||||
$config_model = new Config();
|
'out_biz_no' => $param[ 'out_trade_no' ],
|
||||||
$config_result = $config_model->getPayConfig($param[ 'site_id' ]);
|
'payee_type' => 'ALIPAY_LOGONID',
|
||||||
if ($config_result[ 'code' ] < 0) return $config_result;
|
'payee_account' => $param[ "account_number" ],
|
||||||
$config = $config_result[ 'data' ][ 'value' ];
|
'amount' => sprintf("%.2f", $param[ 'amount' ]),
|
||||||
if (empty($config)) return $this->error([], '未配置支付宝支付');
|
'payee_real_name' => $param[ "real_name" ],
|
||||||
if (!$config[ 'transfer_status' ]) return $this->error([], '未启用支付宝转账');
|
'remark' => $param[ "desc" ]
|
||||||
|
];
|
||||||
$parameter = [
|
// 建立请求
|
||||||
'out_biz_no' => $param[ 'out_trade_no' ],
|
$request = new AlipayFundTransToaccountTransferRequest();
|
||||||
'payee_type' => 'ALIPAY_LOGONID',
|
$request->setBizContent(json_encode($parameter));
|
||||||
'payee_account' => $param[ "account_number" ],
|
$result = $this->aop->execute($request);
|
||||||
'amount' => sprintf("%.2f", $param[ 'amount' ]),
|
$responseNode = str_replace(".", "_", $request->getApiMethodName()) . "_response";
|
||||||
'payee_real_name' => $param[ "real_name" ],
|
$resultCode = $result->$responseNode->code;
|
||||||
'remark' => $param[ "desc" ]
|
if (!empty($resultCode) && $resultCode == 10000) {
|
||||||
];
|
return $this->success([
|
||||||
// 建立请求
|
'out_trade_no' => $result->$responseNode->out_biz_no, // 商户交易号
|
||||||
$request = new AlipayFundTransToaccountTransferRequest();
|
'payment_no' => $result->$responseNode->order_id, // 微信付款单号
|
||||||
$request->setBizContent(json_encode($parameter));
|
'payment_time' => date_to_time($result->$responseNode->pay_date) // 付款成功时间
|
||||||
$result = $this->aop->execute($request);
|
]);
|
||||||
$responseNode = str_replace(".", "_", $request->getApiMethodName()) . "_response";
|
} else {
|
||||||
$resultCode = $result->$responseNode->code;
|
return $this->error([], $result->$responseNode->sub_msg);
|
||||||
if (!empty($resultCode) && $resultCode == 10000) {
|
}
|
||||||
return $this->success([
|
} catch (\Exception $e) {
|
||||||
'out_trade_no' => $result->$responseNode->out_biz_no, // 商户交易号
|
return $this->error([], $e->getMessage());
|
||||||
'payment_no' => $result->$responseNode->order_id, // 微信付款单号
|
}
|
||||||
'payment_time' => date_to_time($result->$responseNode->pay_date) // 付款成功时间
|
}
|
||||||
]);
|
|
||||||
} else {
|
/**
|
||||||
return $this->error([], $result->$responseNode->sub_msg);
|
* 异步完成支付
|
||||||
}
|
* @param $param
|
||||||
} catch (\Exception $e) {
|
*/
|
||||||
return $this->error([], $e->getMessage());
|
public function payNotify()
|
||||||
}
|
{
|
||||||
}
|
|
||||||
|
// Log::write('pay_notifiy_log:alipay:'.json_encode(input()), 'notice');
|
||||||
/**
|
try {
|
||||||
* 异步完成支付
|
$res = $this->aop->rsaCheckV1($_POST, $this->aop->alipayrsaPublicKey, $this->aop->signType);
|
||||||
* @param $param
|
if ($res) { // 验证成功
|
||||||
*/
|
$out_trade_no = $_POST[ 'out_trade_no' ];
|
||||||
public function payNotify()
|
// 支付宝交易号
|
||||||
{
|
$trade_no = $_POST[ 'trade_no' ];
|
||||||
|
// 交易状态
|
||||||
// Log::write('pay_notifiy_log:alipay:'.json_encode(input()), 'notice');
|
$trade_status = $_POST[ 'trade_status' ];
|
||||||
try {
|
$pay_common = new PayCommon();
|
||||||
$res = $this->aop->rsaCheckV1($_POST, $this->aop->alipayrsaPublicKey, $this->aop->signType);
|
if ($trade_status == "TRADE_SUCCESS") {
|
||||||
if ($res) { // 验证成功
|
$retval = $pay_common->onlinePay($out_trade_no, "alipay", $trade_no, "alipay");
|
||||||
$out_trade_no = $_POST[ 'out_trade_no' ];
|
}
|
||||||
// 支付宝交易号
|
echo "success";
|
||||||
$trade_no = $_POST[ 'trade_no' ];
|
} else {
|
||||||
// 交易状态
|
// 验证失败
|
||||||
$trade_status = $_POST[ 'trade_status' ];
|
echo "fail";
|
||||||
$pay_common = new PayCommon();
|
}
|
||||||
if ($trade_status == "TRADE_SUCCESS") {
|
} catch (\Exception $e) {
|
||||||
$retval = $pay_common->onlinePay($out_trade_no, "alipay", $trade_no, "alipay");
|
echo "fail";
|
||||||
}
|
}
|
||||||
echo "success";
|
}
|
||||||
} else {
|
|
||||||
// 验证失败
|
public function payNewTransfer($param)
|
||||||
echo "fail";
|
{
|
||||||
}
|
try {
|
||||||
} catch (\Exception $e) {
|
$config_model = new Config();
|
||||||
echo "fail";
|
$config_result = $config_model->getPayConfig($param[ 'site_id' ]);
|
||||||
}
|
if ($config_result[ 'code' ] < 0) return $config_result;
|
||||||
}
|
$config = $config_result[ 'data' ][ 'value' ];
|
||||||
|
if (empty($config)) return $this->error([], '未配置支付宝支付');
|
||||||
public function payNewTransfer($param)
|
if (!$config[ 'transfer_status' ]) return $this->error([], '未启用支付宝转账');
|
||||||
{
|
|
||||||
try {
|
$parameter = [
|
||||||
$config_model = new Config();
|
'out_biz_no' => $param[ 'out_trade_no' ],
|
||||||
$config_result = $config_model->getPayConfig($param[ 'site_id' ]);
|
'trans_amount' => sprintf("%.2f", $param[ 'amount' ]),
|
||||||
if ($config_result[ 'code' ] < 0) return $config_result;
|
'product_code' => 'TRANS_ACCOUNT_NO_PWD',
|
||||||
$config = $config_result[ 'data' ][ 'value' ];
|
'biz_scene' => 'DIRECT_TRANSFER',
|
||||||
if (empty($config)) return $this->error([], '未配置支付宝支付');
|
'order_title' => '支付宝转账',
|
||||||
if (!$config[ 'transfer_status' ]) return $this->error([], '未启用支付宝转账');
|
'remark' => $param[ "desc" ],
|
||||||
|
'payee_info' => [
|
||||||
$parameter = [
|
'identity' => $param[ "account_number" ],
|
||||||
'out_biz_no' => $param[ 'out_trade_no' ],
|
'identity_type' => "ALIPAY_LOGON_ID",
|
||||||
'trans_amount' => sprintf("%.2f", $param[ 'amount' ]),
|
'name' => $param[ "real_name" ]
|
||||||
'product_code' => 'TRANS_ACCOUNT_NO_PWD',
|
]
|
||||||
'biz_scene' => 'DIRECT_TRANSFER',
|
];
|
||||||
'order_title' => '支付宝转账',
|
|
||||||
'remark' => $param[ "desc" ],
|
// 建立请求
|
||||||
'payee_info' => [
|
$request = new AlipayFundTransUniTransferRequest();
|
||||||
'identity' => $param[ "account_number" ],
|
$request->setBizContent(json_encode($parameter));
|
||||||
'identity_type' => "ALIPAY_LOGON_ID",
|
$result = $this->aop->execute($request);
|
||||||
'name' => $param[ "real_name" ]
|
$responseNode = str_replace(".", "_", $request->getApiMethodName()) . "_response";
|
||||||
]
|
$resultCode = $result->$responseNode->code;
|
||||||
];
|
if (!empty($resultCode) && $resultCode == 10000) {
|
||||||
|
return $this->success([
|
||||||
// 建立请求
|
'out_trade_no' => $result->$responseNode->out_biz_no, // 商户交易号
|
||||||
$request = new AlipayFundTransUniTransferRequest();
|
'payment_no' => $result->$responseNode->order_id, // 微信付款单号
|
||||||
$request->setBizContent(json_encode($parameter));
|
'payment_time' => date_to_time($result->$responseNode->trans_date) // 付款成功时间
|
||||||
$result = $this->aop->execute($request);
|
]);
|
||||||
$responseNode = str_replace(".", "_", $request->getApiMethodName()) . "_response";
|
} else {
|
||||||
$resultCode = $result->$responseNode->code;
|
return $this->error([], $result->$responseNode->sub_msg);
|
||||||
if (!empty($resultCode) && $resultCode == 10000) {
|
}
|
||||||
return $this->success([
|
} catch (\Exception $e) {
|
||||||
'out_trade_no' => $result->$responseNode->out_biz_no, // 商户交易号
|
return $this->error([], $e->getMessage());
|
||||||
'payment_no' => $result->$responseNode->order_id, // 微信付款单号
|
}
|
||||||
'payment_time' => date_to_time($result->$responseNode->trans_date) // 付款成功时间
|
}
|
||||||
]);
|
|
||||||
} else {
|
/**
|
||||||
return $this->error([], $result->$responseNode->sub_msg);
|
* 付款码支付
|
||||||
}
|
* @param $param
|
||||||
} catch (\Exception $e) {
|
* @return array|mixed|void
|
||||||
return $this->error([], $e->getMessage());
|
*/
|
||||||
}
|
public function micropay($param)
|
||||||
}
|
{
|
||||||
|
try {
|
||||||
/**
|
//构造要请求的参数数组,无需改动
|
||||||
* 付款码支付
|
$parameter = array (
|
||||||
* @param $param
|
"out_trade_no" => $param[ "out_trade_no" ],
|
||||||
* @return array|mixed|void
|
"subject" => str_sub($param[ "pay_body" ], 15),
|
||||||
*/
|
"total_amount" => (float) $param[ "pay_money" ],
|
||||||
public function micropay($param)
|
"scene" => "bar_code",
|
||||||
{
|
"auth_code" => $param[ 'auth_code' ],
|
||||||
try {
|
);
|
||||||
//构造要请求的参数数组,无需改动
|
$parameter = json_encode($parameter);
|
||||||
$parameter = array (
|
$request = new AlipayTradePayRequest();
|
||||||
"out_trade_no" => $param[ "out_trade_no" ],
|
$request->setBizContent($parameter);
|
||||||
"subject" => str_sub($param[ "pay_body" ], 15),
|
$result = $this->aop->execute($request);
|
||||||
"total_amount" => (float) $param[ "pay_money" ],
|
$responseNode = str_replace(".", "_", $request->getApiMethodName()) . "_response";
|
||||||
"scene" => "bar_code",
|
$resultCode = $result->$responseNode->code;
|
||||||
"auth_code" => $param[ 'auth_code' ],
|
Log::write('支付宝—付款码支付,result:' . json_encode($result));
|
||||||
);
|
Log::write('支付宝—付款码支付,resultCode:' . json_encode($resultCode));
|
||||||
$parameter = json_encode($parameter);
|
if (!empty($resultCode)) {
|
||||||
$request = new AlipayTradePayRequest();
|
if ($resultCode == 10000) {
|
||||||
$request->setBizContent($parameter);
|
$pay_common = new PayModel();
|
||||||
$result = $this->aop->execute($request);
|
return $res = $pay_common->onlinePay($param[ 'out_trade_no' ], 'alipay', $result->$responseNode->trade_no, 'alipay');
|
||||||
$responseNode = str_replace(".", "_", $request->getApiMethodName()) . "_response";
|
} else if ($resultCode == 10003) {
|
||||||
$resultCode = $result->$responseNode->code;
|
// 等待用户付款
|
||||||
Log::write('支付宝—付款码支付,result:' . json_encode($result));
|
( new Cron() )->addCron(1, 0, "查询付款码支付结果", "PayOrderQuery", time() + 3, $param[ 'id' ]);
|
||||||
Log::write('支付宝—付款码支付,resultCode:' . json_encode($resultCode));
|
}
|
||||||
if (!empty($resultCode)) {
|
} else {
|
||||||
if ($resultCode == 10000) {
|
return $this->error([], $result->$responseNode->sub_msg);
|
||||||
$pay_common = new PayModel();
|
}
|
||||||
return $res = $pay_common->onlinePay($param[ 'out_trade_no' ], 'alipay', $result->$responseNode->trade_no, 'alipay');
|
} catch (\Exception $e) {
|
||||||
} else if ($resultCode == 10003) {
|
return $this->error([], $e->getMessage());
|
||||||
// 等待用户付款
|
}
|
||||||
( new Cron() )->addCron(1, 0, "查询付款码支付结果", "PayOrderQuery", time() + 3, $param[ 'id' ]);
|
}
|
||||||
}
|
|
||||||
} else {
|
// todo 查询交易信息【AlipayTradeQueryRequest】 https://opendocs.alipay.com/open/194/106039?pathHash=5b8cf9e6
|
||||||
return $this->error([], $result->$responseNode->sub_msg);
|
public function orderQuery($param)
|
||||||
}
|
{
|
||||||
} catch (\Exception $e) {
|
try {
|
||||||
return $this->error([], $e->getMessage());
|
//构造要请求的参数数组,无需改动
|
||||||
}
|
$parameter = array (
|
||||||
}
|
"out_trade_no" => $param[ "out_trade_no" ],
|
||||||
|
);
|
||||||
// todo 查询交易信息【AlipayTradeQueryRequest】 https://opendocs.alipay.com/open/194/106039?pathHash=5b8cf9e6
|
$parameter = json_encode($parameter);
|
||||||
public function orderQuery($param)
|
$request = new AlipayTradeQueryRequest();
|
||||||
{
|
$request->setBizContent($parameter);
|
||||||
try {
|
$result = $this->aop->execute($request);
|
||||||
//构造要请求的参数数组,无需改动
|
$responseNode = str_replace(".", "_", $request->getApiMethodName()) . "_response";
|
||||||
$parameter = array (
|
$resultCode = $result->$responseNode->code;
|
||||||
"out_trade_no" => $param[ "out_trade_no" ],
|
Log::write('alipay_orderQuery' . json_encode($result));
|
||||||
);
|
Log::write('alipay_orderQuery_$resultCode' . json_encode($resultCode));
|
||||||
$parameter = json_encode($parameter);
|
if (!empty($resultCode) && $resultCode == 10000) {
|
||||||
$request = new AlipayTradeQueryRequest();
|
if ($result->$responseNode->trade_status == 'TRADE_SUCCESS') {
|
||||||
$request->setBizContent($parameter);
|
$pay_common = new PayModel();
|
||||||
$result = $this->aop->execute($request);
|
return $res = $pay_common->onlinePay($param[ 'out_trade_no' ], 'alipay', $result->$responseNode->trade_no, 'alipay');
|
||||||
$responseNode = str_replace(".", "_", $request->getApiMethodName()) . "_response";
|
} else {
|
||||||
$resultCode = $result->$responseNode->code;
|
( new Cron() )->addCron(1, 0, "查询付款码支付结果", "PayOrderQuery", time() + 3, $param[ 'id' ]);
|
||||||
Log::write('alipay_orderQuery' . json_encode($result));
|
}
|
||||||
Log::write('alipay_orderQuery_$resultCode' . json_encode($resultCode));
|
|
||||||
if (!empty($resultCode) && $resultCode == 10000) {
|
} else {
|
||||||
if ($result->$responseNode->trade_status == 'TRADE_SUCCESS') {
|
return $this->error([], $result->$responseNode->sub_msg);
|
||||||
$pay_common = new PayModel();
|
}
|
||||||
return $res = $pay_common->onlinePay($param[ 'out_trade_no' ], 'alipay', $result->$responseNode->trade_no, 'alipay');
|
} catch (\Exception $e) {
|
||||||
} else {
|
return $this->error([], $e->getMessage());
|
||||||
( new Cron() )->addCron(1, 0, "查询付款码支付结果", "PayOrderQuery", time() + 3, $param[ 'id' ]);
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
} else {
|
|
||||||
return $this->error([], $result->$responseNode->sub_msg);
|
/**
|
||||||
}
|
* 查询订单信息
|
||||||
} catch (\Exception $e) {
|
* @param $out_trade_no
|
||||||
return $this->error([], $e->getMessage());
|
* @return array
|
||||||
}
|
* @throws \think\Exception
|
||||||
}
|
*/
|
||||||
|
public function get($out_trade_no)
|
||||||
|
{
|
||||||
/**
|
$parameter = array (
|
||||||
* 查询订单信息
|
"out_trade_no" => $out_trade_no
|
||||||
* @param $out_trade_no
|
);
|
||||||
* @return array
|
// 建立请求
|
||||||
* @throws \think\Exception
|
$request = new AlipayTradeQueryRequest();
|
||||||
*/
|
$request->setBizContent(json_encode($parameter));
|
||||||
public function get($out_trade_no)
|
$result = $this->aop->execute($request);
|
||||||
{
|
$responseNode = str_replace(".", "_", $request->getApiMethodName()) . "_response";
|
||||||
$parameter = array (
|
$resultCode = $result->$responseNode->code;
|
||||||
"out_trade_no" => $out_trade_no
|
|
||||||
);
|
|
||||||
// 建立请求
|
if (!empty($resultCode) && $resultCode == 10000) {
|
||||||
$request = new AlipayTradeQueryRequest();
|
return $this->success(json_decode(json_encode($result->$responseNode), true));
|
||||||
$request->setBizContent(json_encode($parameter));
|
} else {
|
||||||
$result = $this->aop->execute($request);
|
return $this->error([], $result->$responseNode->sub_msg);
|
||||||
$responseNode = str_replace(".", "_", $request->getApiMethodName()) . "_response";
|
}
|
||||||
$resultCode = $result->$responseNode->code;
|
}
|
||||||
|
|
||||||
|
|
||||||
if (!empty($resultCode) && $resultCode == 10000) {
|
|
||||||
return $this->success(json_decode(json_encode($result->$responseNode), true));
|
|
||||||
} else {
|
|
||||||
return $this->error([], $result->$responseNode->sub_msg);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@@ -1,94 +1,86 @@
|
|||||||
<?php
|
<?php
|
||||||
/**
|
|
||||||
|
namespace addon\alipay\shop\controller;
|
||||||
|
|
||||||
|
use addon\alipay\model\Config as ConfigModel;
|
||||||
|
use app\shop\controller\BaseShop;
|
||||||
|
use think\facade\Config;
|
||||||
|
use app\model\upload\Upload;
|
||||||
*/
|
|
||||||
|
/**
|
||||||
namespace addon\alipay\shop\controller;
|
* 支付宝 控制器
|
||||||
|
*/
|
||||||
use addon\alipay\model\Config as ConfigModel;
|
class Pay extends BaseShop
|
||||||
use app\shop\controller\BaseShop;
|
{
|
||||||
use think\facade\Config;
|
public function config()
|
||||||
use app\model\upload\Upload;
|
{
|
||||||
|
$config_model = new ConfigModel();
|
||||||
/**
|
if (request()->isJson()) {
|
||||||
* 支付宝 控制器
|
$app_id = input("app_id", "");//支付宝应用ID (支付宝分配给开发者的应用ID)
|
||||||
*/
|
$private_key = input("private_key", "");//应用私钥
|
||||||
class Pay extends BaseShop
|
$public_key = input("public_key", "");//应用公钥
|
||||||
{
|
$alipay_public_key = input("alipay_public_key", "");//支付宝公钥
|
||||||
public function config()
|
$app_type = input("app_type", "");//支持端口 如web app
|
||||||
{
|
$pay_status = input("pay_status", 0);//支付启用状态
|
||||||
$config_model = new ConfigModel();
|
$refund_status = input("refund_status", 0);//退款启用状态
|
||||||
if (request()->isJson()) {
|
$transfer_status = input("transfer_status", 0);//转账启用状态
|
||||||
$app_id = input("app_id", "");//支付宝应用ID (支付宝分配给开发者的应用ID)
|
$public_key_crt = input("public_key_crt", "");
|
||||||
$private_key = input("private_key", "");//应用私钥
|
$alipay_public_key_crt = input("alipay_public_key_crt", "");
|
||||||
$public_key = input("public_key", "");//应用公钥
|
$alipay_with_crt = input("alipay_with_crt", "");
|
||||||
$alipay_public_key = input("alipay_public_key", "");//支付宝公钥
|
$countersign_type = input("countersign_type", 0);//加签模式
|
||||||
$app_type = input("app_type", "");//支持端口 如web app
|
|
||||||
$pay_status = input("pay_status", 0);//支付启用状态
|
$data = array (
|
||||||
$refund_status = input("refund_status", 0);//退款启用状态
|
"app_id" => $app_id,
|
||||||
$transfer_status = input("transfer_status", 0);//转账启用状态
|
"private_key" => $private_key,
|
||||||
$public_key_crt = input("public_key_crt", "");
|
"public_key" => $public_key,
|
||||||
$alipay_public_key_crt = input("alipay_public_key_crt", "");
|
"alipay_public_key" => $alipay_public_key,
|
||||||
$alipay_with_crt = input("alipay_with_crt", "");
|
"refund_status" => $refund_status,
|
||||||
$countersign_type = input("countersign_type", 0);//加签模式
|
"pay_status" => $pay_status,
|
||||||
|
"transfer_status" => $transfer_status,
|
||||||
$data = array (
|
"app_type" => $app_type,
|
||||||
"app_id" => $app_id,
|
"public_key_crt" => $public_key_crt,
|
||||||
"private_key" => $private_key,
|
"alipay_public_key_crt" => $alipay_public_key_crt,
|
||||||
"public_key" => $public_key,
|
"alipay_with_crt" => $alipay_with_crt,
|
||||||
"alipay_public_key" => $alipay_public_key,
|
"countersign_type" => $countersign_type
|
||||||
"refund_status" => $refund_status,
|
);
|
||||||
"pay_status" => $pay_status,
|
$result = $config_model->setPayConfig($data, $this->site_id, $this->app_module);
|
||||||
"transfer_status" => $transfer_status,
|
return $result;
|
||||||
"app_type" => $app_type,
|
} else {
|
||||||
"public_key_crt" => $public_key_crt,
|
$info = $config_model->getPayConfig($this->site_id, $this->app_module, true)[ 'data' ][ 'value' ];
|
||||||
"alipay_public_key_crt" => $alipay_public_key_crt,
|
|
||||||
"alipay_with_crt" => $alipay_with_crt,
|
if (!empty($info)) {
|
||||||
"countersign_type" => $countersign_type
|
$app_type_arr = [];
|
||||||
);
|
if (!empty($info[ 'app_type' ])) {
|
||||||
$result = $config_model->setPayConfig($data, $this->site_id, $this->app_module);
|
$app_type_arr = explode(',', $info[ 'app_type' ]);
|
||||||
return $result;
|
}
|
||||||
} else {
|
$info[ 'app_type_arr' ] = $app_type_arr;
|
||||||
$info = $config_model->getPayConfig($this->site_id, $this->app_module, true)[ 'data' ][ 'value' ];
|
if (empty($info[ 'countersign_type' ])) {
|
||||||
|
$info[ 'countersign_type' ] = 0;
|
||||||
if (!empty($info)) {
|
}
|
||||||
$app_type_arr = [];
|
}
|
||||||
if (!empty($info[ 'app_type' ])) {
|
$this->assign("info", $info);
|
||||||
$app_type_arr = explode(',', $info[ 'app_type' ]);
|
$this->assign("app_type", Config::get("app_type"));
|
||||||
}
|
|
||||||
$info[ 'app_type_arr' ] = $app_type_arr;
|
return $this->fetch("pay/config");
|
||||||
if (empty($info[ 'countersign_type' ])) {
|
}
|
||||||
$info[ 'countersign_type' ] = 0;
|
}
|
||||||
}
|
|
||||||
}
|
/**
|
||||||
$this->assign("info", $info);
|
* 上传微信支付证书
|
||||||
$this->assign("app_type", Config::get("app_type"));
|
*/
|
||||||
|
public function uploadAlipayCrt()
|
||||||
return $this->fetch("pay/config");
|
{
|
||||||
}
|
$upload_model = new Upload();
|
||||||
}
|
$site_id = request()->siteid();
|
||||||
|
$name = input("name", "");
|
||||||
/**
|
$extend_type = [ 'crt' ];
|
||||||
* 上传微信支付证书
|
$param = array (
|
||||||
*/
|
"name" => "file",
|
||||||
public function uploadAlipayCrt()
|
"extend_type" => $extend_type
|
||||||
{
|
);
|
||||||
$upload_model = new Upload();
|
|
||||||
$site_id = request()->siteid();
|
$site_id = max($site_id, 0);
|
||||||
$name = input("name", "");
|
$result = $upload_model->setPath("common/alipay/crt/" . $site_id . "/")->file($param);
|
||||||
$extend_type = [ 'crt' ];
|
return $result;
|
||||||
$param = array (
|
}
|
||||||
"name" => "file",
|
|
||||||
"extend_type" => $extend_type
|
|
||||||
);
|
|
||||||
|
|
||||||
$site_id = max($site_id, 0);
|
|
||||||
$result = $upload_model->setPath("common/alipay/crt/" . $site_id . "/")->file($param);
|
|
||||||
return $result;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@@ -1,48 +1,40 @@
|
|||||||
<?php
|
<?php
|
||||||
/**
|
|
||||||
|
return [
|
||||||
|
|
||||||
|
// 自定义模板页面类型,格式:[ 'title' => '页面类型名称', 'name' => '页面标识', 'path' => '页面路径', 'value' => '页面数据,json格式' ]
|
||||||
|
'template' => [],
|
||||||
|
|
||||||
|
// 后台自定义组件——装修
|
||||||
*/
|
'util' => [],
|
||||||
|
|
||||||
return [
|
// 自定义页面路径
|
||||||
|
'link' => [
|
||||||
// 自定义模板页面类型,格式:[ 'title' => '页面类型名称', 'name' => '页面标识', 'path' => '页面路径', 'value' => '页面数据,json格式' ]
|
[
|
||||||
'template' => [],
|
'name' => 'CASES_INFO',
|
||||||
|
'title' => '案例展示',
|
||||||
// 后台自定义组件——装修
|
'parent' => 'BASICS_LINK',
|
||||||
'util' => [],
|
'wap_url' => '/pages_tool/cases/index',
|
||||||
|
'web_url' => '',
|
||||||
// 自定义页面路径
|
'sort' => 0
|
||||||
'link' => [
|
]
|
||||||
[
|
],
|
||||||
'name' => 'CASES_INFO',
|
|
||||||
'title' => '案例展示',
|
// 自定义图标库
|
||||||
'parent' => 'BASICS_LINK',
|
'icon_library' => [],
|
||||||
'wap_url' => '/pages_tool/cases/index',
|
|
||||||
'web_url' => '',
|
// uni-app 组件,格式:[ 'name' => '组件名称/文件夹名称', 'path' => '文件路径/目录路径' ],多个逗号隔开,自定义组件名称前缀必须是diy-,也可以引用第三方组件
|
||||||
'sort' => 0
|
'component' => [],
|
||||||
]
|
|
||||||
],
|
// uni-app 页面,多个逗号隔开
|
||||||
|
'pages' => [],
|
||||||
// 自定义图标库
|
|
||||||
'icon_library' => [],
|
// 模板信息,格式:'title' => '模板名称', 'name' => '模板标识', 'cover' => '模板封面图', 'preview' => '模板预览图', 'desc' => '模板描述'
|
||||||
|
'info' => [],
|
||||||
// uni-app 组件,格式:[ 'name' => '组件名称/文件夹名称', 'path' => '文件路径/目录路径' ],多个逗号隔开,自定义组件名称前缀必须是diy-,也可以引用第三方组件
|
|
||||||
'component' => [],
|
// 主题风格配色,格式可以自由定义扩展,【在uni-app中通过:this.themeStyle... 获取定义的颜色字段,例如:this.themeStyle.main_color】
|
||||||
|
'theme' => [],
|
||||||
// uni-app 页面,多个逗号隔开
|
|
||||||
'pages' => [],
|
// 自定义页面数据,格式:[ 'title' => '页面名称', 'name' => "页面标识", 'value' => [页面数据,json格式] ]
|
||||||
|
'data' => []
|
||||||
// 模板信息,格式:'title' => '模板名称', 'name' => '模板标识', 'cover' => '模板封面图', 'preview' => '模板预览图', 'desc' => '模板描述'
|
|
||||||
'info' => [],
|
|
||||||
|
|
||||||
// 主题风格配色,格式可以自由定义扩展,【在uni-app中通过:this.themeStyle... 获取定义的颜色字段,例如:this.themeStyle.main_color】
|
|
||||||
'theme' => [],
|
|
||||||
|
|
||||||
// 自定义页面数据,格式:[ 'title' => '页面名称', 'name' => "页面标识", 'value' => [页面数据,json格式] ]
|
|
||||||
'data' => []
|
|
||||||
];
|
];
|
||||||
@@ -1,21 +1,12 @@
|
|||||||
<?php
|
<?php
|
||||||
/**
|
return [
|
||||||
|
'name' => 'cases',
|
||||||
|
'title' => '案例展示',
|
||||||
|
'description' => '展示案例信息',
|
||||||
|
'type' => 'tool', //插件类型 system :系统插件(自动安装), promotion:扩展营销插件 tool:工具插件
|
||||||
|
'status' => 1,
|
||||||
|
'author' => '',
|
||||||
|
'version' => '1.0.0',
|
||||||
*/
|
'version_no' => '2025051923121',
|
||||||
return [
|
'content' => '',
|
||||||
'name' => 'cases',
|
|
||||||
'title' => '案例展示',
|
|
||||||
'description' => '展示案例信息',
|
|
||||||
'type' => 'tool', //插件类型 system :系统插件(自动安装), promotion:扩展营销插件 tool:工具插件
|
|
||||||
'status' => 1,
|
|
||||||
'author' => '',
|
|
||||||
'version' => '1.0.0',
|
|
||||||
'version_no' => '2025051923121',
|
|
||||||
'content' => '',
|
|
||||||
];
|
];
|
||||||
@@ -1,25 +1,17 @@
|
|||||||
<?php
|
<?php
|
||||||
/**
|
|
||||||
|
namespace addon\cases\event;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 应用安装
|
||||||
|
*/
|
||||||
|
class Install
|
||||||
*/
|
{
|
||||||
|
/**
|
||||||
namespace addon\cases\event;
|
* 执行安装
|
||||||
|
*/
|
||||||
/**
|
public function handle()
|
||||||
* 应用安装
|
{
|
||||||
*/
|
return success();
|
||||||
class Install
|
}
|
||||||
{
|
|
||||||
/**
|
|
||||||
* 执行安装
|
|
||||||
*/
|
|
||||||
public function handle()
|
|
||||||
{
|
|
||||||
return success();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@@ -1,30 +1,21 @@
|
|||||||
<?php
|
<?php
|
||||||
/**
|
|
||||||
|
namespace addon\cases\event;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 应用卸载
|
||||||
|
*/
|
||||||
|
class UnInstall
|
||||||
|
{
|
||||||
*/
|
/**
|
||||||
|
* 执行卸载
|
||||||
namespace addon\cases\event;
|
*/
|
||||||
|
public function handle()
|
||||||
/**
|
{
|
||||||
* 应用卸载
|
try {
|
||||||
*/
|
return error('', "系统插件不允许删除");
|
||||||
class UnInstall
|
} catch (\Exception $e) {
|
||||||
{
|
return error('', $e->getMessage());
|
||||||
/**
|
}
|
||||||
* 执行卸载
|
}
|
||||||
*/
|
|
||||||
public function handle()
|
|
||||||
{
|
|
||||||
try {
|
|
||||||
return error('', "系统插件不允许删除");
|
|
||||||
} catch (\Exception $e) {
|
|
||||||
return error('', $e->getMessage());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@@ -1,351 +1,343 @@
|
|||||||
<?php
|
<?php
|
||||||
/**
|
|
||||||
|
namespace addon\coupon\api\controller;
|
||||||
|
|
||||||
|
use app\api\controller\BaseApi;
|
||||||
|
use addon\coupon\model\Coupon as CouponModel;
|
||||||
|
use addon\coupon\model\CouponType as CouponTypeModel;
|
||||||
|
use addon\coupon\model\MemberCoupon;
|
||||||
*/
|
use think\facade\Db;
|
||||||
|
|
||||||
namespace addon\coupon\api\controller;
|
/**
|
||||||
|
* 优惠券
|
||||||
use app\api\controller\BaseApi;
|
*/
|
||||||
use addon\coupon\model\Coupon as CouponModel;
|
class Coupon extends BaseApi
|
||||||
use addon\coupon\model\CouponType as CouponTypeModel;
|
{
|
||||||
use addon\coupon\model\MemberCoupon;
|
|
||||||
use think\facade\Db;
|
/**
|
||||||
|
* 优惠券类型信息
|
||||||
/**
|
*/
|
||||||
* 优惠券
|
public function typeinfo()
|
||||||
*/
|
{
|
||||||
class Coupon extends BaseApi
|
$coupon_type_id = $this->params['coupon_type_id'] ?? 0;
|
||||||
{
|
if (empty($coupon_type_id)) {
|
||||||
|
return $this->response($this->error('', 'REQUEST_COUPON_TYPE_ID'));
|
||||||
/**
|
}
|
||||||
* 优惠券类型信息
|
|
||||||
*/
|
$app_type = $this->params['app_type'] ?? 'h5';
|
||||||
public function typeinfo()
|
|
||||||
{
|
$coupon_model = new CouponModel();
|
||||||
$coupon_type_id = $this->params['coupon_type_id'] ?? 0;
|
$condition = [
|
||||||
if (empty($coupon_type_id)) {
|
[ 'coupon_type_id', '=', $coupon_type_id ],
|
||||||
return $this->response($this->error('', 'REQUEST_COUPON_TYPE_ID'));
|
[ 'is_show', '=', 1 ],
|
||||||
}
|
[ 'site_id', '=', $this->site_id ]
|
||||||
|
];
|
||||||
$app_type = $this->params['app_type'] ?? 'h5';
|
|
||||||
|
$coupon_type_model = new CouponTypeModel();
|
||||||
$coupon_model = new CouponModel();
|
$qrcode = $coupon_type_model->qrcode($coupon_type_id, $app_type, $this->site_id)[ 'data' ];
|
||||||
$condition = [
|
|
||||||
[ 'coupon_type_id', '=', $coupon_type_id ],
|
$info = $coupon_model->getCouponTypeInfo($condition);
|
||||||
[ 'is_show', '=', 1 ],
|
if (!empty($info[ 'data' ]) && !empty($qrcode)) {
|
||||||
[ 'site_id', '=', $this->site_id ]
|
$info[ 'data' ][ 'qrcode' ] = $qrcode[ 'path' ];
|
||||||
];
|
}
|
||||||
|
return $this->response($info);
|
||||||
$coupon_type_model = new CouponTypeModel();
|
}
|
||||||
$qrcode = $coupon_type_model->qrcode($coupon_type_id, $app_type, $this->site_id)[ 'data' ];
|
|
||||||
|
/**
|
||||||
$info = $coupon_model->getCouponTypeInfo($condition);
|
* 列表信息
|
||||||
if (!empty($info[ 'data' ]) && !empty($qrcode)) {
|
*/
|
||||||
$info[ 'data' ][ 'qrcode' ] = $qrcode[ 'path' ];
|
public function memberpage()
|
||||||
}
|
{
|
||||||
return $this->response($info);
|
$token = $this->checkToken();
|
||||||
}
|
if ($token[ 'code' ] < 0) return $this->response($token);
|
||||||
|
|
||||||
/**
|
$page = $this->params['page'] ?? 1;
|
||||||
* 列表信息
|
$page_size = $this->params['page_size'] ?? PAGE_LIST_ROWS;
|
||||||
*/
|
$state = $this->params['state'] ?? 1;//优惠券状态 1已领用(未使用) 2已使用 3已过期
|
||||||
public function memberpage()
|
|
||||||
{
|
$coupon_model = new CouponModel();
|
||||||
$token = $this->checkToken();
|
$condition = [
|
||||||
if ($token[ 'code' ] < 0) return $this->response($token);
|
[ 'npc.member_id', '=', $token[ 'data' ][ 'member_id' ] ],
|
||||||
|
[ 'npc.state', '=', $state ]
|
||||||
$page = $this->params['page'] ?? 1;
|
];
|
||||||
$page_size = $this->params['page_size'] ?? PAGE_LIST_ROWS;
|
|
||||||
$state = $this->params['state'] ?? 1;//优惠券状态 1已领用(未使用) 2已使用 3已过期
|
//按类型筛选
|
||||||
|
$type = $this->params['type'] ?? '';
|
||||||
$coupon_model = new CouponModel();
|
$related_id = $this->params['related_id'] ?? 0;
|
||||||
$condition = [
|
switch ( $type ) {
|
||||||
[ 'npc.member_id', '=', $token[ 'data' ][ 'member_id' ] ],
|
case 'reward'://满减
|
||||||
[ 'npc.state', '=', $state ]
|
$condition[] = ['npc.type', '=', 'reward'];
|
||||||
];
|
break;
|
||||||
|
case 'discount'://折扣
|
||||||
//按类型筛选
|
$condition[] = ['npc.type', '=', 'discount'];
|
||||||
$type = $this->params['type'] ?? '';
|
break;
|
||||||
$related_id = $this->params['related_id'] ?? 0;
|
case 'no_threshold'://无门槛
|
||||||
switch ( $type ) {
|
$condition[] = ['npc.at_least', '=', 0 ];
|
||||||
case 'reward'://满减
|
break;
|
||||||
$condition[] = ['npc.type', '=', 'reward'];
|
}
|
||||||
break;
|
if (!empty($related_id)) {
|
||||||
case 'discount'://折扣
|
$condition[] = [ 'related_id', '=', $related_id ];
|
||||||
$condition[] = ['npc.type', '=', 'discount'];
|
}
|
||||||
break;
|
|
||||||
case 'no_threshold'://无门槛
|
$list = $coupon_model->getMemberCouponPageList($condition, $page, $page_size);
|
||||||
$condition[] = ['npc.at_least', '=', 0 ];
|
|
||||||
break;
|
return $this->response($list);
|
||||||
}
|
}
|
||||||
if (!empty($related_id)) {
|
|
||||||
$condition[] = [ 'related_id', '=', $related_id ];
|
/**
|
||||||
}
|
* 优惠券类型列表
|
||||||
|
*/
|
||||||
$list = $coupon_model->getMemberCouponPageList($condition, $page, $page_size);
|
public function typelists()
|
||||||
|
{
|
||||||
return $this->response($list);
|
$num = $this->params['num'] ?? 0;
|
||||||
}
|
$coupon_type_id_arr = $this->params['coupon_type_id_arr'] ?? '';//coupon_type_id数组
|
||||||
|
$can_receive = $this->params['can_receive'] ?? 0;// 是否只查询可领取的
|
||||||
/**
|
|
||||||
* 优惠券类型列表
|
$token = $this->checkToken();
|
||||||
*/
|
|
||||||
public function typelists()
|
$coupon_model = new CouponModel();
|
||||||
{
|
$condition = [
|
||||||
$num = $this->params['num'] ?? 0;
|
[ 'status', '=', 1 ],
|
||||||
$coupon_type_id_arr = $this->params['coupon_type_id_arr'] ?? '';//coupon_type_id数组
|
[ 'is_show', '=', 1 ],
|
||||||
$can_receive = $this->params['can_receive'] ?? 0;// 是否只查询可领取的
|
[ 'site_id', '=', $this->site_id ]
|
||||||
|
];
|
||||||
$token = $this->checkToken();
|
|
||||||
|
//按类型查询
|
||||||
$coupon_model = new CouponModel();
|
$type = $this->params['type'] ?? '';
|
||||||
$condition = [
|
switch ( $type ) {
|
||||||
[ 'status', '=', 1 ],
|
case 'reward'://满减
|
||||||
[ 'is_show', '=', 1 ],
|
$condition[] = ['type', '=', 'reward'];
|
||||||
[ 'site_id', '=', $this->site_id ]
|
break;
|
||||||
];
|
case 'discount'://折扣
|
||||||
|
$condition[] = ['type', '=', 'discount'];
|
||||||
//按类型查询
|
break;
|
||||||
$type = $this->params['type'] ?? '';
|
case 'no_threshold'://无门槛
|
||||||
switch ( $type ) {
|
$condition[] = ['at_least', '=', 0 ];
|
||||||
case 'reward'://满减
|
break;
|
||||||
$condition[] = ['type', '=', 'reward'];
|
}
|
||||||
break;
|
|
||||||
case 'discount'://折扣
|
if (!empty($coupon_type_id_arr)) {
|
||||||
$condition[] = ['type', '=', 'discount'];
|
$condition[] = [ 'coupon_type_id', 'in', $coupon_type_id_arr ];
|
||||||
break;
|
}
|
||||||
case 'no_threshold'://无门槛
|
$field = 'coupon_type_id,type,site_id,coupon_name,money,discount,max_fetch,at_least,end_time,image,validity_type,fixed_term,status,is_show,goods_type,discount_limit,count,lead_count,IF(count < 0 or count - lead_count > 0, 1, 0) as is_remain';
|
||||||
$condition[] = ['at_least', '=', 0 ];
|
if ($can_receive == 1) {
|
||||||
break;
|
$condition[] = [ [ 'count', '<>', Db::raw('lead_count') ] ];
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!empty($coupon_type_id_arr)) {
|
$order = Db::raw('IF(count < 0 or count - lead_count > 0, 1, 0) DESC,sort ASC');
|
||||||
$condition[] = [ 'coupon_type_id', 'in', $coupon_type_id_arr ];
|
|
||||||
}
|
$list = $coupon_model->getCouponTypeList($condition, $field, $order, $num);
|
||||||
$field = 'coupon_type_id,type,site_id,coupon_name,money,discount,max_fetch,at_least,end_time,image,validity_type,fixed_term,status,is_show,goods_type,discount_limit,count,lead_count,IF(count < 0 or count - lead_count > 0, 1, 0) as is_remain';
|
if (!empty($list[ 'data' ]) && $this->member_id) {
|
||||||
if ($can_receive == 1) {
|
foreach ($list[ 'data' ] as $k => $v) {
|
||||||
$condition[] = [ [ 'count', '<>', Db::raw('lead_count') ] ];
|
$list[ 'data' ][ $k ][ 'member_coupon_num' ] = $coupon_model->getCouponCount([
|
||||||
}
|
[ 'get_type', '=', 2 ],
|
||||||
|
[ 'member_id', '=', $this->member_id ],
|
||||||
$order = Db::raw('IF(count < 0 or count - lead_count > 0, 1, 0) DESC,sort ASC');
|
[ 'coupon_type_id', '=', $v[ 'coupon_type_id' ] ]
|
||||||
|
])[ 'data' ];
|
||||||
$list = $coupon_model->getCouponTypeList($condition, $field, $order, $num);
|
}
|
||||||
if (!empty($list[ 'data' ]) && $this->member_id) {
|
}
|
||||||
foreach ($list[ 'data' ] as $k => $v) {
|
|
||||||
$list[ 'data' ][ $k ][ 'member_coupon_num' ] = $coupon_model->getCouponCount([
|
return $this->response($list);
|
||||||
[ 'get_type', '=', 2 ],
|
}
|
||||||
[ 'member_id', '=', $this->member_id ],
|
|
||||||
[ 'coupon_type_id', '=', $v[ 'coupon_type_id' ] ]
|
/**
|
||||||
])[ 'data' ];
|
* 优惠券类型分页列表
|
||||||
}
|
*/
|
||||||
}
|
public function typepagelists()
|
||||||
|
{
|
||||||
return $this->response($list);
|
$page = $this->params['page'] ?? 1;
|
||||||
}
|
$page_size = $this->params['page_size'] ?? PAGE_LIST_ROWS;
|
||||||
|
$coupon_type_id_arr = $this->params['coupon_type_id_arr'] ?? '';//coupon_type_id数组
|
||||||
/**
|
$can_receive = $this->params['can_receive'] ?? 0;// 是否只查询可领取的
|
||||||
* 优惠券类型分页列表
|
|
||||||
*/
|
$token = $this->checkToken();
|
||||||
public function typepagelists()
|
|
||||||
{
|
$coupon_model = new CouponModel();
|
||||||
$page = $this->params['page'] ?? 1;
|
$condition = [
|
||||||
$page_size = $this->params['page_size'] ?? PAGE_LIST_ROWS;
|
[ 'status', '=', 1 ],
|
||||||
$coupon_type_id_arr = $this->params['coupon_type_id_arr'] ?? '';//coupon_type_id数组
|
[ 'is_show', '=', 1 ],
|
||||||
$can_receive = $this->params['can_receive'] ?? 0;// 是否只查询可领取的
|
[ 'site_id', '=', $this->site_id ]
|
||||||
|
];
|
||||||
$token = $this->checkToken();
|
|
||||||
|
//按类型查询
|
||||||
$coupon_model = new CouponModel();
|
$type = $this->params['type'] ?? '';
|
||||||
$condition = [
|
switch ( $type ) {
|
||||||
[ 'status', '=', 1 ],
|
case 'reward'://满减
|
||||||
[ 'is_show', '=', 1 ],
|
$condition[] = ['type', '=', 'reward'];
|
||||||
[ 'site_id', '=', $this->site_id ]
|
break;
|
||||||
];
|
case 'discount'://折扣
|
||||||
|
$condition[] = ['type', '=', 'discount'];
|
||||||
//按类型查询
|
break;
|
||||||
$type = $this->params['type'] ?? '';
|
case 'no_threshold'://无门槛
|
||||||
switch ( $type ) {
|
$condition[] = ['at_least', '=', 0 ];
|
||||||
case 'reward'://满减
|
break;
|
||||||
$condition[] = ['type', '=', 'reward'];
|
}
|
||||||
break;
|
|
||||||
case 'discount'://折扣
|
if (!empty($coupon_type_id_arr)) {
|
||||||
$condition[] = ['type', '=', 'discount'];
|
$condition[] = [ 'coupon_type_id', 'in', $coupon_type_id_arr ];
|
||||||
break;
|
}
|
||||||
case 'no_threshold'://无门槛
|
$field = 'coupon_type_id,type,site_id,coupon_name,money,discount,max_fetch,at_least,end_time,image,validity_type,fixed_term,status,is_show,goods_type,discount_limit,count,lead_count,IF(count < 0 or count - lead_count > 0, 1, 0) as is_remain';
|
||||||
$condition[] = ['at_least', '=', 0 ];
|
if ($can_receive == 1) {
|
||||||
break;
|
$condition[] = [ [ 'count', '<>', Db::raw('lead_count') ] ];
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!empty($coupon_type_id_arr)) {
|
if ($this->member_id) {
|
||||||
$condition[] = [ 'coupon_type_id', 'in', $coupon_type_id_arr ];
|
$prefix = config('database.connections.mysql.prefix');
|
||||||
}
|
$field .= ', (select count(coupon_id) from ' . $prefix . 'promotion_coupon pc where pc.coupon_type_id = ct.coupon_type_id and pc.get_type=2 and pc.member_id=' . $this->member_id . ') as member_coupon_num';
|
||||||
$field = 'coupon_type_id,type,site_id,coupon_name,money,discount,max_fetch,at_least,end_time,image,validity_type,fixed_term,status,is_show,goods_type,discount_limit,count,lead_count,IF(count < 0 or count - lead_count > 0, 1, 0) as is_remain';
|
}
|
||||||
if ($can_receive == 1) {
|
|
||||||
$condition[] = [ [ 'count', '<>', Db::raw('lead_count') ] ];
|
$order = Db::raw('IF(count < 0 or count - lead_count > 0, 1, 0) DESC,sort ASC');
|
||||||
}
|
|
||||||
|
$list = $coupon_model->getCouponTypePageList($condition, $page, $page_size, $order, $field, 'ct');
|
||||||
if ($this->member_id) {
|
|
||||||
$prefix = config('database.connections.mysql.prefix');
|
return $this->response($list);
|
||||||
$field .= ', (select count(coupon_id) from ' . $prefix . 'promotion_coupon pc where pc.coupon_type_id = ct.coupon_type_id and pc.get_type=2 and pc.member_id=' . $this->member_id . ') as member_coupon_num';
|
}
|
||||||
}
|
|
||||||
|
/**
|
||||||
$order = Db::raw('IF(count < 0 or count - lead_count > 0, 1, 0) DESC,sort ASC');
|
* 获取优惠券
|
||||||
|
*/
|
||||||
$list = $coupon_model->getCouponTypePageList($condition, $page, $page_size, $order, $field, 'ct');
|
public function receive()
|
||||||
|
{
|
||||||
return $this->response($list);
|
$token = $this->checkToken();
|
||||||
}
|
if ($token[ 'code' ] < 0) return $this->response($token);
|
||||||
|
|
||||||
/**
|
$site_id = $this->site_id;
|
||||||
* 获取优惠券
|
$coupon_type_id = $this->params['coupon_type_id'] ?? 0;
|
||||||
*/
|
$get_type = $this->params['get_type'] ?? 2;//获取方式:1订单2.直接领取3.活动领取
|
||||||
public function receive()
|
|
||||||
{
|
if (empty($coupon_type_id)) {
|
||||||
$token = $this->checkToken();
|
return $this->response($this->error('', 'REQUEST_COUPON_TYPE_ID'));
|
||||||
if ($token[ 'code' ] < 0) return $this->response($token);
|
}
|
||||||
|
|
||||||
$site_id = $this->site_id;
|
$coupon_model = new CouponModel();
|
||||||
$coupon_type_id = $this->params['coupon_type_id'] ?? 0;
|
$res = $coupon_model->receiveCoupon($coupon_type_id, $site_id, $token[ 'data' ][ 'member_id' ], $get_type);
|
||||||
$get_type = $this->params['get_type'] ?? 2;//获取方式:1订单2.直接领取3.活动领取
|
$res[ 'data' ] = [];
|
||||||
|
|
||||||
if (empty($coupon_type_id)) {
|
//判断一下用户是否拥有当前优惠券
|
||||||
return $this->response($this->error('', 'REQUEST_COUPON_TYPE_ID'));
|
$coupon = $coupon_model->getCouponInfo([ [ 'coupon_type_id', '=', $coupon_type_id ], [ 'site_id', '=', $site_id ], [ 'member_id', '=', $token[ 'data' ][ 'member_id' ] ] ], 'coupon_id')[ 'data' ];
|
||||||
}
|
$res[ 'data' ][ 'is_exist' ] = empty($coupon) ? 0 : 1;
|
||||||
|
return $this->response($res);
|
||||||
$coupon_model = new CouponModel();
|
}
|
||||||
$res = $coupon_model->receiveCoupon($coupon_type_id, $site_id, $token[ 'data' ][ 'member_id' ], $get_type);
|
|
||||||
$res[ 'data' ] = [];
|
/**
|
||||||
|
* 会员优惠券数量
|
||||||
//判断一下用户是否拥有当前优惠券
|
* @return string
|
||||||
$coupon = $coupon_model->getCouponInfo([ [ 'coupon_type_id', '=', $coupon_type_id ], [ 'site_id', '=', $site_id ], [ 'member_id', '=', $token[ 'data' ][ 'member_id' ] ] ], 'coupon_id')[ 'data' ];
|
*/
|
||||||
$res[ 'data' ][ 'is_exist' ] = empty($coupon) ? 0 : 1;
|
public function num()
|
||||||
return $this->response($res);
|
{
|
||||||
}
|
$token = $this->checkToken();
|
||||||
|
if ($token[ 'code' ] < 0) return $this->response($token);
|
||||||
/**
|
|
||||||
* 会员优惠券数量
|
$state = $this->params[ 'state' ] ?? 1;
|
||||||
* @return string
|
$coupon_model = new MemberCoupon();
|
||||||
*/
|
|
||||||
public function num()
|
$count = $coupon_model->getMemberCouponNum($token[ 'data' ][ 'member_id' ], $state);
|
||||||
{
|
return $this->response($count);
|
||||||
$token = $this->checkToken();
|
}
|
||||||
if ($token[ 'code' ] < 0) return $this->response($token);
|
|
||||||
|
/**
|
||||||
$state = $this->params[ 'state' ] ?? 1;
|
* 是否可以领取
|
||||||
$coupon_model = new MemberCoupon();
|
*/
|
||||||
|
public function receivedNum()
|
||||||
$count = $coupon_model->getMemberCouponNum($token[ 'data' ][ 'member_id' ], $state);
|
{
|
||||||
return $this->response($count);
|
$token = $this->checkToken();
|
||||||
}
|
if ($token[ 'code' ] < 0) return $this->response($token);
|
||||||
|
|
||||||
/**
|
$coupon_type_id = $this->params['coupon_type_id'] ?? 0;
|
||||||
* 是否可以领取
|
|
||||||
*/
|
$coupon_model = new MemberCoupon();
|
||||||
public function receivedNum()
|
$res = $coupon_model->receivedNum($coupon_type_id, $this->member_id);
|
||||||
{
|
return $this->response($res);
|
||||||
$token = $this->checkToken();
|
}
|
||||||
if ($token[ 'code' ] < 0) return $this->response($token);
|
|
||||||
|
/**
|
||||||
$coupon_type_id = $this->params['coupon_type_id'] ?? 0;
|
* 查询商品可用的优惠券
|
||||||
|
* @param int $id
|
||||||
$coupon_model = new MemberCoupon();
|
* @return false|string
|
||||||
$res = $coupon_model->receivedNum($coupon_type_id, $this->member_id);
|
*/
|
||||||
return $this->response($res);
|
public function goodsCoupon($id = 0)
|
||||||
}
|
{
|
||||||
|
$this->checkToken();
|
||||||
/**
|
$coupon_model = new CouponModel();
|
||||||
* 查询商品可用的优惠券
|
$goods_id = $this->params[ 'goods_id' ] ?? 0;
|
||||||
* @param int $id
|
if (!empty($id)) {
|
||||||
* @return false|string
|
$goods_id = $id;
|
||||||
*/
|
}
|
||||||
public function goodsCoupon($id = 0)
|
|
||||||
{
|
// 查询全部商品参与
|
||||||
$this->checkToken();
|
$condition = [
|
||||||
$coupon_model = new CouponModel();
|
[ 'site_id', '=', $this->site_id ],
|
||||||
$goods_id = $this->params[ 'goods_id' ] ?? 0;
|
[ 'status', '=', 1 ],
|
||||||
if (!empty($id)) {
|
[ 'is_show', '=', 1 ],
|
||||||
$goods_id = $id;
|
[ 'goods_type', '=', 1 ]
|
||||||
}
|
];
|
||||||
|
|
||||||
// 查询全部商品参与
|
$field = 'count,lead_count,coupon_type_id,coupon_type_id as type_id,type,site_id,coupon_name,money,discount,max_fetch,at_least,end_time,validity_type,fixed_term,goods_type,discount_limit';
|
||||||
$condition = [
|
|
||||||
[ 'site_id', '=', $this->site_id ],
|
if ($this->member_id) {
|
||||||
[ 'status', '=', 1 ],
|
$prefix = config('database.connections.mysql.prefix');
|
||||||
[ 'is_show', '=', 1 ],
|
$field .= ',(select count(coupon_id) from ' . $prefix . 'promotion_coupon pc where pc.coupon_type_id = type_id and pc.get_type=2 and pc.member_id=' . $this->member_id . ') as member_coupon_num';
|
||||||
[ 'goods_type', '=', 1 ]
|
}
|
||||||
];
|
|
||||||
|
$list = $coupon_model->getCouponTypeList($condition, $field, 'money desc', null, 'ct');
|
||||||
$field = 'count,lead_count,coupon_type_id,coupon_type_id as type_id,type,site_id,coupon_name,money,discount,max_fetch,at_least,end_time,validity_type,fixed_term,goods_type,discount_limit';
|
|
||||||
|
// 查询指定商品参与
|
||||||
if ($this->member_id) {
|
$goods_condition = [
|
||||||
$prefix = config('database.connections.mysql.prefix');
|
[ 'site_id', '=', $this->site_id ],
|
||||||
$field .= ',(select count(coupon_id) from ' . $prefix . 'promotion_coupon pc where pc.coupon_type_id = type_id and pc.get_type=2 and pc.member_id=' . $this->member_id . ') as member_coupon_num';
|
[ 'status', '=', 1 ],
|
||||||
}
|
[ 'is_show', '=', 1 ],
|
||||||
|
[ 'goods_type', '=', 2 ],
|
||||||
$list = $coupon_model->getCouponTypeList($condition, $field, 'money desc', null, 'ct');
|
[ 'goods_ids', 'like', "%,$goods_id,%" ]
|
||||||
|
];
|
||||||
// 查询指定商品参与
|
|
||||||
$goods_condition = [
|
$goods_coupon = $coupon_model->getCouponTypeList($goods_condition, $field, 'money desc', null, 'ct');
|
||||||
[ 'site_id', '=', $this->site_id ],
|
|
||||||
[ 'status', '=', 1 ],
|
if (!empty($goods_coupon[ 'data' ])) {
|
||||||
[ 'is_show', '=', 1 ],
|
$list[ 'data' ] = array_merge($list[ 'data' ], $goods_coupon[ 'data' ]);
|
||||||
[ 'goods_type', '=', 2 ],
|
}
|
||||||
[ 'goods_ids', 'like', "%,$goods_id,%" ]
|
|
||||||
];
|
// 查询指定商品不参与
|
||||||
|
$not_goods_condition = [
|
||||||
$goods_coupon = $coupon_model->getCouponTypeList($goods_condition, $field, 'money desc', null, 'ct');
|
[ 'site_id', '=', $this->site_id ],
|
||||||
|
[ 'status', '=', 1 ],
|
||||||
if (!empty($goods_coupon[ 'data' ])) {
|
[ 'is_show', '=', 1 ],
|
||||||
$list[ 'data' ] = array_merge($list[ 'data' ], $goods_coupon[ 'data' ]);
|
[ 'goods_type', '=', 3 ],
|
||||||
}
|
[ 'goods_ids', 'not like', "%,$goods_id,%" ]
|
||||||
|
];
|
||||||
// 查询指定商品不参与
|
|
||||||
$not_goods_condition = [
|
$not_goods_coupon = $coupon_model->getCouponTypeList($not_goods_condition, $field, 'money desc', null, 'ct');
|
||||||
[ 'site_id', '=', $this->site_id ],
|
if (!empty($not_goods_coupon[ 'data' ])) {
|
||||||
[ 'status', '=', 1 ],
|
$list[ 'data' ] = array_merge($list[ 'data' ], $not_goods_coupon[ 'data' ]);
|
||||||
[ 'is_show', '=', 1 ],
|
}
|
||||||
[ 'goods_type', '=', 3 ],
|
|
||||||
[ 'goods_ids', 'not like', "%,$goods_id,%" ]
|
if ($list[ 'data' ] && $this->member_id) {
|
||||||
];
|
foreach ($list[ 'data' ] as $k => $v) {
|
||||||
|
// 已抢光
|
||||||
$not_goods_coupon = $coupon_model->getCouponTypeList($not_goods_condition, $field, 'money desc', null, 'ct');
|
if ($v[ 'count' ] == $v[ 'lead_count' ]) {
|
||||||
if (!empty($not_goods_coupon[ 'data' ])) {
|
unset($list[ 'data' ][ $k ]);
|
||||||
$list[ 'data' ] = array_merge($list[ 'data' ], $not_goods_coupon[ 'data' ]);
|
} elseif ($v[ 'max_fetch' ] != 0 && $v[ 'member_coupon_num' ] > 0 && $v[ 'member_coupon_num' ] >= $v[ 'max_fetch' ]) {
|
||||||
}
|
// 已领取
|
||||||
|
unset($list[ 'data' ][ $k ]);
|
||||||
if ($list[ 'data' ] && $this->member_id) {
|
}
|
||||||
foreach ($list[ 'data' ] as $k => $v) {
|
}
|
||||||
// 已抢光
|
$list[ 'data' ] = array_values($list[ 'data' ]);
|
||||||
if ($v[ 'count' ] == $v[ 'lead_count' ]) {
|
}
|
||||||
unset($list[ 'data' ][ $k ]);
|
return $this->response($list);
|
||||||
} elseif ($v[ 'max_fetch' ] != 0 && $v[ 'member_coupon_num' ] > 0 && $v[ 'member_coupon_num' ] >= $v[ 'max_fetch' ]) {
|
}
|
||||||
// 已领取
|
|
||||||
unset($list[ 'data' ][ $k ]);
|
/**
|
||||||
}
|
* 查询优惠券通过优惠券类型id
|
||||||
}
|
*/
|
||||||
$list[ 'data' ] = array_values($list[ 'data' ]);
|
public function couponById()
|
||||||
}
|
{
|
||||||
return $this->response($list);
|
$id = $this->params[ 'id' ] ?? 0;
|
||||||
}
|
$coupon_model = new CouponModel();
|
||||||
|
$condition = [
|
||||||
/**
|
[ 'site_id', '=', $this->site_id ],
|
||||||
* 查询优惠券通过优惠券类型id
|
[ 'status', '=', 1 ],
|
||||||
*/
|
[ 'coupon_type_id', 'in', $id ]
|
||||||
public function couponById()
|
];
|
||||||
{
|
$list = $coupon_model->getCouponTypeList($condition, 'coupon_type_id,type,site_id,coupon_name,money,discount,max_fetch,at_least,end_time,validity_type,fixed_term,goods_type,discount_limit', 'money desc', '');
|
||||||
$id = $this->params[ 'id' ] ?? 0;
|
return $this->response($list);
|
||||||
$coupon_model = new CouponModel();
|
}
|
||||||
$condition = [
|
|
||||||
[ 'site_id', '=', $this->site_id ],
|
|
||||||
[ 'status', '=', 1 ],
|
|
||||||
[ 'coupon_type_id', 'in', $id ]
|
|
||||||
];
|
|
||||||
$list = $coupon_model->getCouponTypeList($condition, 'coupon_type_id,type,site_id,coupon_name,money,discount,max_fetch,at_least,end_time,validity_type,fixed_term,goods_type,discount_limit', 'money desc', '');
|
|
||||||
return $this->response($list);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@@ -1,67 +1,59 @@
|
|||||||
<?php
|
<?php
|
||||||
/**
|
return [
|
||||||
|
|
||||||
|
// 自定义模板页面类型,格式:[ 'title' => '页面类型名称', 'name' => '页面标识', 'path' => '页面路径', 'value' => '页面数据,json格式' ]
|
||||||
|
'template' => [],
|
||||||
|
|
||||||
|
// 后台自定义组件——装修
|
||||||
|
'util' => [
|
||||||
*/
|
[
|
||||||
return [
|
'name' => 'Coupon',
|
||||||
|
'title' => '优惠券',
|
||||||
// 自定义模板页面类型,格式:[ 'title' => '页面类型名称', 'name' => '页面标识', 'path' => '页面路径', 'value' => '页面数据,json格式' ]
|
'type' => 'PROMOTION',
|
||||||
'template' => [],
|
'value' => '{"style":1,"sources":"initial","styleName":"风格一","couponIds":[],"count":6,"previewList":[],"nameColor":"","moneyColor":"#FFFFFF","limitColor":"#FFFFFF","btnStyle":{"maxLen": 4,"textColor":"#FFFFFF","bgColor":"","text":"立即领取","aroundRadius":0,"isBgColor":false,"isAroundRadius":false},"isName":false,"couponBgColor":"","couponBgUrl":"","couponType":"img","ifNeedBg":true}',
|
||||||
|
'sort' => '30000',
|
||||||
// 后台自定义组件——装修
|
'support_diy_view' => '',
|
||||||
'util' => [
|
'max_count' => 0,
|
||||||
[
|
'icon' => 'iconfont iconyouhuiquan',
|
||||||
'name' => 'Coupon',
|
],
|
||||||
'title' => '优惠券',
|
],
|
||||||
'type' => 'PROMOTION',
|
|
||||||
'value' => '{"style":1,"sources":"initial","styleName":"风格一","couponIds":[],"count":6,"previewList":[],"nameColor":"","moneyColor":"#FFFFFF","limitColor":"#FFFFFF","btnStyle":{"maxLen": 4,"textColor":"#FFFFFF","bgColor":"","text":"立即领取","aroundRadius":0,"isBgColor":false,"isAroundRadius":false},"isName":false,"couponBgColor":"","couponBgUrl":"","couponType":"img","ifNeedBg":true}',
|
// 自定义页面路径
|
||||||
'sort' => '30000',
|
'link' => [
|
||||||
'support_diy_view' => '',
|
[
|
||||||
'max_count' => 0,
|
'name' => 'COUPON_LIST',
|
||||||
'icon' => 'iconfont iconyouhuiquan',
|
'title' => '优惠券',
|
||||||
],
|
'parent' => 'MARKETING_LINK',
|
||||||
],
|
'wap_url' => '',
|
||||||
|
'web_url' => '',
|
||||||
// 自定义页面路径
|
'sort' => 0,
|
||||||
'link' => [
|
'child_list' => [
|
||||||
[
|
[
|
||||||
'name' => 'COUPON_LIST',
|
'name' => 'COUPON_PREFECTURE',
|
||||||
'title' => '优惠券',
|
'title' => '优惠券专区',
|
||||||
'parent' => 'MARKETING_LINK',
|
'wap_url' => '/pages_tool/goods/coupon',
|
||||||
'wap_url' => '',
|
'web_url' => '',
|
||||||
'web_url' => '',
|
'sort' => 0
|
||||||
'sort' => 0,
|
]
|
||||||
'child_list' => [
|
]
|
||||||
[
|
],
|
||||||
'name' => 'COUPON_PREFECTURE',
|
],
|
||||||
'title' => '优惠券专区',
|
|
||||||
'wap_url' => '/pages_tool/goods/coupon',
|
// 自定义图标库
|
||||||
'web_url' => '',
|
'icon_library' => [],
|
||||||
'sort' => 0
|
|
||||||
]
|
// uni-app 组件,格式:[ 'name' => '组件名称/文件夹名称', 'path' => '文件路径/目录路径' ],多个逗号隔开,自定义组件名称前缀必须是diy-,也可以引用第三方组件
|
||||||
]
|
'component' => [],
|
||||||
],
|
|
||||||
],
|
// uni-app 页面,多个逗号隔开
|
||||||
|
'pages' => [],
|
||||||
// 自定义图标库
|
|
||||||
'icon_library' => [],
|
// 模板信息,格式:'title' => '模板名称', 'name' => '模板标识', 'cover' => '模板封面图', 'preview' => '模板预览图', 'desc' => '模板描述'
|
||||||
|
'info' => [],
|
||||||
// uni-app 组件,格式:[ 'name' => '组件名称/文件夹名称', 'path' => '文件路径/目录路径' ],多个逗号隔开,自定义组件名称前缀必须是diy-,也可以引用第三方组件
|
|
||||||
'component' => [],
|
// 主题风格配色,格式可以自由定义扩展,【在uni-app中通过:this.themeStyle... 获取定义的颜色字段,例如:this.themeStyle.main_color】
|
||||||
|
'theme' => [],
|
||||||
// uni-app 页面,多个逗号隔开
|
|
||||||
'pages' => [],
|
// 自定义页面数据,格式:[ 'title' => '页面名称', 'name' => "页面标识", 'value' => [页面数据,json格式] ]
|
||||||
|
'data' => []
|
||||||
// 模板信息,格式:'title' => '模板名称', 'name' => '模板标识', 'cover' => '模板封面图', 'preview' => '模板预览图', 'desc' => '模板描述'
|
|
||||||
'info' => [],
|
|
||||||
|
|
||||||
// 主题风格配色,格式可以自由定义扩展,【在uni-app中通过:this.themeStyle... 获取定义的颜色字段,例如:this.themeStyle.main_color】
|
|
||||||
'theme' => [],
|
|
||||||
|
|
||||||
// 自定义页面数据,格式:[ 'title' => '页面名称', 'name' => "页面标识", 'value' => [页面数据,json格式] ]
|
|
||||||
'data' => []
|
|
||||||
];
|
];
|
||||||
@@ -1,21 +1,12 @@
|
|||||||
<?php
|
<?php
|
||||||
/**
|
return [
|
||||||
|
'name' => 'coupon',
|
||||||
|
'title' => '优惠券',
|
||||||
|
'description' => '会员优惠券功能',
|
||||||
|
'type' => 'system', //插件类型 system :系统插件(自动安装), promotion:扩展营销插件 tool:工具插件
|
||||||
|
'status' => 1,
|
||||||
|
'author' => '',
|
||||||
|
'version' => '5.3.1',
|
||||||
*/
|
'version_no' => '525231212001',
|
||||||
return [
|
'content' => '',
|
||||||
'name' => 'coupon',
|
|
||||||
'title' => '优惠券',
|
|
||||||
'description' => '会员优惠券功能',
|
|
||||||
'type' => 'system', //插件类型 system :系统插件(自动安装), promotion:扩展营销插件 tool:工具插件
|
|
||||||
'status' => 1,
|
|
||||||
'author' => '',
|
|
||||||
'version' => '5.3.1',
|
|
||||||
'version_no' => '525231212001',
|
|
||||||
'content' => '',
|
|
||||||
];
|
];
|
||||||
@@ -1,55 +1,46 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
/**
|
namespace addon\coupon\dict;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 订单公共属性
|
||||||
|
*/
|
||||||
|
class CouponDict
|
||||||
*/
|
{
|
||||||
|
const normal = 1;
|
||||||
namespace addon\coupon\dict;
|
const used = 2;
|
||||||
|
const expire = 3;
|
||||||
|
const close = 4;
|
||||||
/**
|
|
||||||
* 订单公共属性
|
/**
|
||||||
*/
|
* 优惠券状态
|
||||||
class CouponDict
|
* @param $status
|
||||||
{
|
* @return string|string[]
|
||||||
const normal = 1;
|
*/
|
||||||
const used = 2;
|
public static function getStatus($status = ''){
|
||||||
const expire = 3;
|
$list = [
|
||||||
const close = 4;
|
self::normal => '待使用',
|
||||||
|
self::used => '已使用',
|
||||||
/**
|
self::expire => '已过期',
|
||||||
* 优惠券状态
|
self::close => '已关闭',
|
||||||
* @param $status
|
];
|
||||||
* @return string|string[]
|
|
||||||
*/
|
if($status) return $list[$status] ?? '';
|
||||||
public static function getStatus($status = ''){
|
return $list;
|
||||||
$list = [
|
}
|
||||||
self::normal => '待使用',
|
|
||||||
self::used => '已使用',
|
const all = 1;
|
||||||
self::expire => '已过期',
|
const selected = 2;
|
||||||
self::close => '已关闭',
|
const selected_out = 3;
|
||||||
];
|
public static function getGoodsType($type = ''){
|
||||||
|
$list = [
|
||||||
if($status) return $list[$status] ?? '';
|
self::all => '全部商品参与',
|
||||||
return $list;
|
self::selected => '指定商品参与',
|
||||||
}
|
self::selected_out => '指定不参与商品'
|
||||||
|
];
|
||||||
const all = 1;
|
|
||||||
const selected = 2;
|
if($type) return $list[$type] ?? '';
|
||||||
const selected_out = 3;
|
return $list;
|
||||||
public static function getGoodsType($type = ''){
|
}
|
||||||
$list = [
|
}
|
||||||
self::all => '全部商品参与',
|
|
||||||
self::selected => '指定商品参与',
|
|
||||||
self::selected_out => '指定不参与商品'
|
|
||||||
];
|
|
||||||
|
|
||||||
if($type) return $list[$type] ?? '';
|
|
||||||
return $list;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,27 +1,19 @@
|
|||||||
<?php
|
<?php
|
||||||
/**
|
|
||||||
|
namespace addon\coupon\event;
|
||||||
|
|
||||||
|
use addon\coupon\model\Coupon;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 启动活动
|
||||||
*/
|
*/
|
||||||
|
class CronCouponEnd
|
||||||
namespace addon\coupon\event;
|
{
|
||||||
|
|
||||||
use addon\coupon\model\Coupon;
|
public function handle($params = [])
|
||||||
|
{
|
||||||
/**
|
$coupon = new Coupon();
|
||||||
* 启动活动
|
$res = $coupon->cronCouponEnd();
|
||||||
*/
|
return $res;
|
||||||
class CronCouponEnd
|
}
|
||||||
{
|
|
||||||
|
|
||||||
public function handle($params = [])
|
|
||||||
{
|
|
||||||
$coupon = new Coupon();
|
|
||||||
$res = $coupon->cronCouponEnd();
|
|
||||||
return $res;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@@ -1,27 +1,19 @@
|
|||||||
<?php
|
<?php
|
||||||
/**
|
|
||||||
|
namespace addon\coupon\event;
|
||||||
|
|
||||||
|
use addon\coupon\model\CouponType;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 优惠券定时结束
|
||||||
*/
|
*/
|
||||||
|
class CronCouponTypeEnd
|
||||||
namespace addon\coupon\event;
|
{
|
||||||
|
|
||||||
use addon\coupon\model\CouponType;
|
public function handle($params = [])
|
||||||
|
{
|
||||||
/**
|
$coupon = new CouponType();
|
||||||
* 优惠券定时结束
|
$res = $coupon->couponCronEnd($params[ 'relate_id' ]);
|
||||||
*/
|
return $res;
|
||||||
class CronCouponTypeEnd
|
}
|
||||||
{
|
|
||||||
|
|
||||||
public function handle($params = [])
|
|
||||||
{
|
|
||||||
$coupon = new CouponType();
|
|
||||||
$res = $coupon->couponCronEnd($params[ 'relate_id' ]);
|
|
||||||
return $res;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@@ -1,38 +1,29 @@
|
|||||||
<?php
|
<?php
|
||||||
/**
|
|
||||||
|
namespace addon\coupon\event;
|
||||||
|
|
||||||
|
use app\model\system\Cron;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 应用安装
|
||||||
|
*/
|
||||||
*/
|
class Install
|
||||||
|
{
|
||||||
namespace addon\coupon\event;
|
/**
|
||||||
|
* 执行安装
|
||||||
use app\model\system\Cron;
|
*/
|
||||||
|
public function handle()
|
||||||
/**
|
{
|
||||||
* 应用安装
|
try {
|
||||||
*/
|
execute_sql('addon/coupon/data/install.sql');
|
||||||
class Install
|
|
||||||
{
|
$cron = new Cron();
|
||||||
/**
|
$cron->deleteCron([ ['event', '=', 'CronCouponEnd'] ]);
|
||||||
* 执行安装
|
$cron->addCron(2, 1, '优惠券过期自动关闭', 'CronCouponEnd', time(), 0);
|
||||||
*/
|
|
||||||
public function handle()
|
return success();
|
||||||
{
|
} catch (\Exception $e) {
|
||||||
try {
|
return error('', $e->getMessage());
|
||||||
execute_sql('addon/coupon/data/install.sql');
|
}
|
||||||
|
}
|
||||||
$cron = new Cron();
|
|
||||||
$cron->deleteCron([ ['event', '=', 'CronCouponEnd'] ]);
|
|
||||||
$cron->addCron(2, 1, '优惠券过期自动关闭', 'CronCouponEnd', time(), 0);
|
|
||||||
|
|
||||||
return success();
|
|
||||||
} catch (\Exception $e) {
|
|
||||||
return error('', $e->getMessage());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@@ -1,46 +1,38 @@
|
|||||||
<?php
|
<?php
|
||||||
/**
|
|
||||||
|
namespace addon\coupon\event;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 店铺活动
|
||||||
|
*/
|
||||||
|
class ShowPromotion
|
||||||
*/
|
{
|
||||||
|
|
||||||
namespace addon\coupon\event;
|
/**
|
||||||
|
* 活动展示
|
||||||
/**
|
* @return array
|
||||||
* 店铺活动
|
*/
|
||||||
*/
|
public function handle()
|
||||||
class ShowPromotion
|
{
|
||||||
{
|
$data = [
|
||||||
|
'shop' => [
|
||||||
/**
|
[
|
||||||
* 活动展示
|
//插件名称
|
||||||
* @return array
|
'name' => 'coupon',
|
||||||
*/
|
//展示分类(根据平台端设置,admin(平台营销),shop:店铺营销,member:会员营销, tool:应用工具)
|
||||||
public function handle()
|
'show_type' => 'shop',
|
||||||
{
|
//展示主题
|
||||||
$data = [
|
'title' => '优惠券',
|
||||||
'shop' => [
|
//展示介绍
|
||||||
[
|
'description' => '设置商家优惠券',
|
||||||
//插件名称
|
//展示图标
|
||||||
'name' => 'coupon',
|
'icon' => 'addon/coupon/icon.png',
|
||||||
//展示分类(根据平台端设置,admin(平台营销),shop:店铺营销,member:会员营销, tool:应用工具)
|
//跳转链接
|
||||||
'show_type' => 'shop',
|
'url' => 'coupon://shop/coupon/lists',
|
||||||
//展示主题
|
]
|
||||||
'title' => '优惠券',
|
]
|
||||||
//展示介绍
|
|
||||||
'description' => '设置商家优惠券',
|
];
|
||||||
//展示图标
|
return $data;
|
||||||
'icon' => 'addon/coupon/icon.png',
|
}
|
||||||
//跳转链接
|
|
||||||
'url' => 'coupon://shop/coupon/lists',
|
|
||||||
]
|
|
||||||
]
|
|
||||||
|
|
||||||
];
|
|
||||||
return $data;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@@ -1,32 +1,23 @@
|
|||||||
<?php
|
<?php
|
||||||
/**
|
|
||||||
|
namespace addon\coupon\event;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 应用卸载
|
||||||
|
*/
|
||||||
|
class UnInstall
|
||||||
|
{
|
||||||
*/
|
/**
|
||||||
|
* 执行卸载
|
||||||
namespace addon\coupon\event;
|
*/
|
||||||
|
public function handle()
|
||||||
/**
|
{
|
||||||
* 应用卸载
|
try {
|
||||||
*/
|
return error('', "系统插件不允许删除");
|
||||||
class UnInstall
|
//execute_sql('addon/coupon/data/uninstall.sql');
|
||||||
{
|
//return success();
|
||||||
/**
|
} catch (\Exception $e) {
|
||||||
* 执行卸载
|
return error('', $e->getMessage());
|
||||||
*/
|
}
|
||||||
public function handle()
|
}
|
||||||
{
|
|
||||||
try {
|
|
||||||
return error('', "系统插件不允许删除");
|
|
||||||
//execute_sql('addon/coupon/data/uninstall.sql');
|
|
||||||
//return success();
|
|
||||||
} catch (\Exception $e) {
|
|
||||||
return error('', $e->getMessage());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -1,64 +1,56 @@
|
|||||||
<?php
|
<?php
|
||||||
/**
|
|
||||||
|
namespace addon\coupon\model;
|
||||||
|
|
||||||
|
use app\model\BaseModel;
|
||||||
|
use app\model\system\Stat;
|
||||||
|
|
||||||
|
/**
|
||||||
*/
|
* 优惠券统计
|
||||||
|
*/
|
||||||
namespace addon\coupon\model;
|
class CouponStat extends BaseModel
|
||||||
|
{
|
||||||
use app\model\BaseModel;
|
|
||||||
use app\model\system\Stat;
|
/**
|
||||||
|
* 领取优惠券统计
|
||||||
/**
|
* @param $params
|
||||||
* 优惠券统计
|
* @return array
|
||||||
*/
|
*/
|
||||||
class CouponStat extends BaseModel
|
public function addReceiveCouponStat($params)
|
||||||
{
|
{
|
||||||
|
$coupon_id = $params[ 'coupon_id' ];
|
||||||
/**
|
$site_id = $params[ 'site_id' ] ?? 0;
|
||||||
* 领取优惠券统计
|
$order_condition = array (
|
||||||
* @param $params
|
[ 'coupon_id', '=', $coupon_id ],
|
||||||
* @return array
|
[ 'site_id', '=', $site_id ]
|
||||||
*/
|
);
|
||||||
public function addReceiveCouponStat($params)
|
$info = model('promotion_coupon')->getInfo($order_condition);
|
||||||
{
|
if (empty($info))
|
||||||
$coupon_id = $params[ 'coupon_id' ];
|
return $this->error();
|
||||||
$site_id = $params[ 'site_id' ] ?? 0;
|
|
||||||
$order_condition = array (
|
$stat_data = array (
|
||||||
[ 'coupon_id', '=', $coupon_id ],
|
'site_id' => $site_id,
|
||||||
[ 'site_id', '=', $site_id ]
|
'coupon_count' => 1
|
||||||
);
|
);
|
||||||
$info = model('promotion_coupon')->getInfo($order_condition);
|
$member_id = $info[ 'member_id' ];
|
||||||
if (empty($info))
|
//如果是第一笔订单才能累加下单会员数
|
||||||
return $this->error();
|
|
||||||
|
$time_region = getDayStartAndEndTime();
|
||||||
$stat_data = array (
|
$today_start_time = $time_region[ 'start_time' ];
|
||||||
'site_id' => $site_id,
|
$today_end_time = $time_region[ 'end_time' ];
|
||||||
'coupon_count' => 1
|
$today_order_condition = array (
|
||||||
);
|
[ 'member_id', '=', $member_id ],
|
||||||
$member_id = $info[ 'member_id' ];
|
[ 'fetch_time', 'between', [ $today_start_time, $today_end_time ] ],
|
||||||
//如果是第一笔订单才能累加下单会员数
|
[ 'coupon_id', '<>', $coupon_id ]
|
||||||
|
);
|
||||||
$time_region = getDayStartAndEndTime();
|
$count = model('promotion_coupon')->getCount($today_order_condition);
|
||||||
$today_start_time = $time_region[ 'start_time' ];
|
if ($count == 0) {
|
||||||
$today_end_time = $time_region[ 'end_time' ];
|
$stat_data[ 'coupon_member_count' ] = 1;
|
||||||
$today_order_condition = array (
|
}
|
||||||
[ 'member_id', '=', $member_id ],
|
|
||||||
[ 'fetch_time', 'between', [ $today_start_time, $today_end_time ] ],
|
//发布统计
|
||||||
[ 'coupon_id', '<>', $coupon_id ]
|
$stat_model = new Stat();
|
||||||
);
|
$result = $stat_model->addShopStat($stat_data);
|
||||||
$count = model('promotion_coupon')->getCount($today_order_condition);
|
return $result;
|
||||||
if ($count == 0) {
|
}
|
||||||
$stat_data[ 'coupon_member_count' ] = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
//发布统计
|
|
||||||
$stat_model = new Stat();
|
|
||||||
$result = $stat_model->addShopStat($stat_data);
|
|
||||||
return $result;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@@ -1,336 +1,328 @@
|
|||||||
<?php
|
<?php
|
||||||
/**
|
|
||||||
|
namespace addon\coupon\model;
|
||||||
|
|
||||||
|
use app\model\BaseModel;
|
||||||
|
use app\model\system\Config as ConfigModel;
|
||||||
|
use app\model\system\Cron;
|
||||||
|
use app\model\upload\Upload;
|
||||||
*/
|
|
||||||
|
/**
|
||||||
namespace addon\coupon\model;
|
* 优惠券活动
|
||||||
|
*/
|
||||||
use app\model\BaseModel;
|
class CouponType extends BaseModel
|
||||||
use app\model\system\Config as ConfigModel;
|
{
|
||||||
use app\model\system\Cron;
|
//优惠券类型状态
|
||||||
use app\model\upload\Upload;
|
private $coupon_type_status = [
|
||||||
|
1 => '进行中',
|
||||||
/**
|
2 => '已结束',
|
||||||
* 优惠券活动
|
-1 => '已关闭',
|
||||||
*/
|
];
|
||||||
class CouponType extends BaseModel
|
|
||||||
{
|
public function getCouponTypeStatus()
|
||||||
//优惠券类型状态
|
{
|
||||||
private $coupon_type_status = [
|
return $this->coupon_type_status;
|
||||||
1 => '进行中',
|
}
|
||||||
2 => '已结束',
|
|
||||||
-1 => '已关闭',
|
/**
|
||||||
];
|
* 添加优惠券活动
|
||||||
|
* @param $data
|
||||||
public function getCouponTypeStatus()
|
* @return array
|
||||||
{
|
*/
|
||||||
return $this->coupon_type_status;
|
public function addCouponType($data)
|
||||||
}
|
{
|
||||||
|
//只要创建了就是进行中
|
||||||
/**
|
$data[ 'status' ] = 1;
|
||||||
* 添加优惠券活动
|
$data[ 'create_time' ] = time();
|
||||||
* @param $data
|
//获取商品id
|
||||||
* @return array
|
if ($data[ 'goods_type' ] == 1) {//全部商品参与
|
||||||
*/
|
$data[ 'goods_ids' ] = '';
|
||||||
public function addCouponType($data)
|
}
|
||||||
{
|
|
||||||
//只要创建了就是进行中
|
$data[ 'goods_ids' ] = ',' . $data[ 'goods_ids' ] . ',';
|
||||||
$data[ 'status' ] = 1;
|
$res = model('promotion_coupon_type')->add($data);
|
||||||
$data[ 'create_time' ] = time();
|
if ($data[ 'validity_type' ] == 0) {
|
||||||
//获取商品id
|
$cron = new Cron();
|
||||||
if ($data[ 'goods_type' ] == 1) {//全部商品参与
|
$cron->addCron(1, 1, '优惠券活动定时结束', 'CronCouponTypeEnd', $data[ 'end_time' ], $res);
|
||||||
$data[ 'goods_ids' ] = '';
|
}
|
||||||
}
|
|
||||||
|
$this->qrcode($res, 'all', $data[ 'site_id' ]);
|
||||||
$data[ 'goods_ids' ] = ',' . $data[ 'goods_ids' ] . ',';
|
return $this->success($res);
|
||||||
$res = model('promotion_coupon_type')->add($data);
|
}
|
||||||
if ($data[ 'validity_type' ] == 0) {
|
|
||||||
$cron = new Cron();
|
/**
|
||||||
$cron->addCron(1, 1, '优惠券活动定时结束', 'CronCouponTypeEnd', $data[ 'end_time' ], $res);
|
* 编辑优惠券活动
|
||||||
}
|
* @param $data
|
||||||
|
* @param $coupon_type_id
|
||||||
$this->qrcode($res, 'all', $data[ 'site_id' ]);
|
* @return array
|
||||||
return $this->success($res);
|
*/
|
||||||
}
|
public function editCouponType($data, $coupon_type_id)
|
||||||
|
{
|
||||||
/**
|
$data[ 'update_time' ] = time();
|
||||||
* 编辑优惠券活动
|
|
||||||
* @param $data
|
//获取商品id
|
||||||
* @param $coupon_type_id
|
if ($data[ 'goods_type' ] == 1) {//全部商品参与
|
||||||
* @return array
|
$data[ 'goods_ids' ] = '';
|
||||||
*/
|
}
|
||||||
public function editCouponType($data, $coupon_type_id)
|
|
||||||
{
|
$coupon_info = model('promotion_coupon_type')->getInfo([ [ 'coupon_type_id', '=', $coupon_type_id ] ]);
|
||||||
$data[ 'update_time' ] = time();
|
if (!empty($coupon_info[ 'image' ]) && !empty($data[ 'image' ]) && $coupon_info[ 'image' ] != $data[ 'image' ]) {
|
||||||
|
$upload_model = new Upload();
|
||||||
//获取商品id
|
$upload_model->deletePic($coupon_info[ 'image' ], $coupon_info[ 'site_id' ]);
|
||||||
if ($data[ 'goods_type' ] == 1) {//全部商品参与
|
}
|
||||||
$data[ 'goods_ids' ] = '';
|
|
||||||
}
|
$data[ 'goods_ids' ] = ',' . $data[ 'goods_ids' ] . ',';
|
||||||
|
$res = model('promotion_coupon_type')->update($data, [ [ 'coupon_type_id', '=', $coupon_type_id ] ]);
|
||||||
$coupon_info = model('promotion_coupon_type')->getInfo([ [ 'coupon_type_id', '=', $coupon_type_id ] ]);
|
model('promotion_coupon')->update([ 'goods_ids' => $data[ 'goods_ids' ], 'goods_type' => $data[ 'goods_type' ] ], [ [ 'coupon_type_id', '=', $coupon_type_id ], [ 'state', '=', 1 ] ]);
|
||||||
if (!empty($coupon_info[ 'image' ]) && !empty($data[ 'image' ]) && $coupon_info[ 'image' ] != $data[ 'image' ]) {
|
$cron = new Cron();
|
||||||
$upload_model = new Upload();
|
$cron->deleteCron([ [ 'event', '=', 'CronCouponTypeEnd' ], [ 'relate_id', '=', $coupon_type_id ] ]);
|
||||||
$upload_model->deletePic($coupon_info[ 'image' ], $coupon_info[ 'site_id' ]);
|
if ($data[ 'validity_type' ] == 0) {
|
||||||
}
|
$cron->addCron(1, 1, '优惠券活动定时结束', 'CronCouponTypeEnd', $data[ 'end_time' ], $coupon_type_id);
|
||||||
|
}
|
||||||
$data[ 'goods_ids' ] = ',' . $data[ 'goods_ids' ] . ',';
|
return $this->success($res);
|
||||||
$res = model('promotion_coupon_type')->update($data, [ [ 'coupon_type_id', '=', $coupon_type_id ] ]);
|
}
|
||||||
model('promotion_coupon')->update([ 'goods_ids' => $data[ 'goods_ids' ], 'goods_type' => $data[ 'goods_type' ] ], [ [ 'coupon_type_id', '=', $coupon_type_id ], [ 'state', '=', 1 ] ]);
|
|
||||||
$cron = new Cron();
|
/**
|
||||||
$cron->deleteCron([ [ 'event', '=', 'CronCouponTypeEnd' ], [ 'relate_id', '=', $coupon_type_id ] ]);
|
* 关闭优惠券
|
||||||
if ($data[ 'validity_type' ] == 0) {
|
* @param $coupon_type_id
|
||||||
$cron->addCron(1, 1, '优惠券活动定时结束', 'CronCouponTypeEnd', $data[ 'end_time' ], $coupon_type_id);
|
* @param $site_id
|
||||||
}
|
* @return array
|
||||||
return $this->success($res);
|
*/
|
||||||
}
|
public function closeCouponType($coupon_type_id, $site_id)
|
||||||
|
{
|
||||||
/**
|
$res = model('promotion_coupon_type')->update([ 'status' => -1 ], [ [ 'coupon_type_id', '=', $coupon_type_id ], [ 'site_id', '=', $site_id ] ]);
|
||||||
* 关闭优惠券
|
// if ($res) {
|
||||||
* @param $coupon_type_id
|
// model("promotion_coupon")->update(['state' => 3], [['coupon_type_id', '=', $coupon_type_id], ['site_id', '=', $site_id]]);
|
||||||
* @param $site_id
|
// }
|
||||||
* @return array
|
$cron = new Cron();
|
||||||
*/
|
$cron->deleteCron([ [ 'event', '=', 'CronCouponTypeEnd' ], [ 'relate_id', '=', $coupon_type_id ] ]);
|
||||||
public function closeCouponType($coupon_type_id, $site_id)
|
return $this->success($res);
|
||||||
{
|
}
|
||||||
$res = model('promotion_coupon_type')->update([ 'status' => -1 ], [ [ 'coupon_type_id', '=', $coupon_type_id ], [ 'site_id', '=', $site_id ] ]);
|
|
||||||
// if ($res) {
|
/**
|
||||||
// model("promotion_coupon")->update(['state' => 3], [['coupon_type_id', '=', $coupon_type_id], ['site_id', '=', $site_id]]);
|
* 删除优惠券活动
|
||||||
// }
|
* @param $coupon_type_id
|
||||||
$cron = new Cron();
|
* @param $site_id
|
||||||
$cron->deleteCron([ [ 'event', '=', 'CronCouponTypeEnd' ], [ 'relate_id', '=', $coupon_type_id ] ]);
|
* @return array
|
||||||
return $this->success($res);
|
*/
|
||||||
}
|
public function deleteCouponType($coupon_type_id, $site_id)
|
||||||
|
{
|
||||||
/**
|
$coupon_info = model('promotion_coupon_type')->getInfo([ [ 'coupon_type_id', '=', $coupon_type_id ] ]);
|
||||||
* 删除优惠券活动
|
|
||||||
* @param $coupon_type_id
|
if ($coupon_info['status'] == 1) return $this->error('', '进行中的优惠卷无法删除,请先关闭');
|
||||||
* @param $site_id
|
|
||||||
* @return array
|
if (!empty($coupon_info[ 'image' ])) {
|
||||||
*/
|
$upload_model = new Upload();
|
||||||
public function deleteCouponType($coupon_type_id, $site_id)
|
$upload_model->deletePic($coupon_info[ 'image' ], $coupon_info[ 'site_id' ]);
|
||||||
{
|
}
|
||||||
$coupon_info = model('promotion_coupon_type')->getInfo([ [ 'coupon_type_id', '=', $coupon_type_id ] ]);
|
|
||||||
|
$res = model('promotion_coupon_type')->delete([ [ 'coupon_type_id', '=', $coupon_type_id ], [ 'site_id', '=', $site_id ] ]);
|
||||||
if ($coupon_info['status'] == 1) return $this->error('', '进行中的优惠卷无法删除,请先关闭');
|
if ($res) {
|
||||||
|
model('promotion_coupon')->delete([ [ 'coupon_type_id', '=', $coupon_type_id ] ]);
|
||||||
if (!empty($coupon_info[ 'image' ])) {
|
}
|
||||||
$upload_model = new Upload();
|
$cron = new Cron();
|
||||||
$upload_model->deletePic($coupon_info[ 'image' ], $coupon_info[ 'site_id' ]);
|
$cron->deleteCron([ [ 'event', '=', 'CronCouponTypeEnd' ], [ 'relate_id', '=', $coupon_type_id ] ]);
|
||||||
}
|
|
||||||
|
return $this->success($res);
|
||||||
$res = model('promotion_coupon_type')->delete([ [ 'coupon_type_id', '=', $coupon_type_id ], [ 'site_id', '=', $site_id ] ]);
|
}
|
||||||
if ($res) {
|
|
||||||
model('promotion_coupon')->delete([ [ 'coupon_type_id', '=', $coupon_type_id ] ]);
|
/**
|
||||||
}
|
* 获取优惠券活动详情
|
||||||
$cron = new Cron();
|
* @param $coupon_type_id
|
||||||
$cron->deleteCron([ [ 'event', '=', 'CronCouponTypeEnd' ], [ 'relate_id', '=', $coupon_type_id ] ]);
|
* @param $site_id
|
||||||
|
* @return array
|
||||||
return $this->success($res);
|
*/
|
||||||
}
|
public function getCouponTypeInfo($coupon_type_id, $site_id)
|
||||||
|
{
|
||||||
/**
|
$res = model('promotion_coupon_type')->getList([ [ 'coupon_type_id', 'in', $coupon_type_id ], [ 'site_id', '=', $site_id ] ]);
|
||||||
* 获取优惠券活动详情
|
if (!empty($res)) {
|
||||||
* @param $coupon_type_id
|
foreach ($res as $k => $v) {
|
||||||
* @param $site_id
|
if ($v[ 'goods_type' ] == 2 || $v[ 'goods_type' ] == 3) {
|
||||||
* @return array
|
$field[ $k ] = 'goods_id,goods_name,FLOOR(goods_stock) as goods_stock,goods_image,price,sort';
|
||||||
*/
|
$goods_ids[ $k ] = substr($v[ 'goods_ids' ], '1', '-1');
|
||||||
public function getCouponTypeInfo($coupon_type_id, $site_id)
|
$goods_list[ $k ] = model('goods')->getList([ [ 'goods_id', 'in', $goods_ids[ $k ] ] ], $field[ $k ]);
|
||||||
{
|
}
|
||||||
$res = model('promotion_coupon_type')->getList([ [ 'coupon_type_id', 'in', $coupon_type_id ], [ 'site_id', '=', $site_id ] ]);
|
$res[ $k ][ 'goods_list' ] = $goods_list[$k] ?? [];
|
||||||
if (!empty($res)) {
|
$res[ $k ][ 'goods_list_count' ] = count($res[ $k ][ 'goods_list' ]);
|
||||||
foreach ($res as $k => $v) {
|
}
|
||||||
if ($v[ 'goods_type' ] == 2 || $v[ 'goods_type' ] == 3) {
|
}
|
||||||
$field[ $k ] = 'goods_id,goods_name,FLOOR(goods_stock) as goods_stock,goods_image,price,sort';
|
return $this->success($res);
|
||||||
$goods_ids[ $k ] = substr($v[ 'goods_ids' ], '1', '-1');
|
}
|
||||||
$goods_list[ $k ] = model('goods')->getList([ [ 'goods_id', 'in', $goods_ids[ $k ] ] ], $field[ $k ]);
|
|
||||||
}
|
/**
|
||||||
$res[ $k ][ 'goods_list' ] = $goods_list[$k] ?? [];
|
* 获取优惠券活动信息
|
||||||
$res[ $k ][ 'goods_list_count' ] = count($res[ $k ][ 'goods_list' ]);
|
* @param array $where
|
||||||
}
|
* @param bool $field
|
||||||
}
|
* @param string $alias
|
||||||
return $this->success($res);
|
* @param null $join
|
||||||
}
|
* @param null $data
|
||||||
|
* @return array
|
||||||
/**
|
*/
|
||||||
* 获取优惠券活动信息
|
public function getInfo($where = [], $field = true, $alias = 'a', $join = null, $data = null)
|
||||||
* @param array $where
|
{
|
||||||
* @param bool $field
|
$res = model('promotion_coupon_type')->getInfo($where, $field, $alias, $join, $data);
|
||||||
* @param string $alias
|
return $this->success($res);
|
||||||
* @param null $join
|
}
|
||||||
* @param null $data
|
|
||||||
* @return array
|
/**
|
||||||
*/
|
* 获取优惠券类型列表
|
||||||
public function getInfo($where = [], $field = true, $alias = 'a', $join = null, $data = null)
|
* @param array $condition
|
||||||
{
|
* @param string $field
|
||||||
$res = model('promotion_coupon_type')->getInfo($where, $field, $alias, $join, $data);
|
* @param string $order
|
||||||
return $this->success($res);
|
* @param null $limit
|
||||||
}
|
* @return array
|
||||||
|
*/
|
||||||
/**
|
public function getCouponTypeList($condition = [], $field = '*', $order = 'create_time desc', $limit = null)
|
||||||
* 获取优惠券类型列表
|
{
|
||||||
* @param array $condition
|
$res = model('promotion_coupon_type')->getList($condition, $field, $order, '', '', '', $limit);
|
||||||
* @param string $field
|
return $this->success($res);
|
||||||
* @param string $order
|
}
|
||||||
* @param null $limit
|
|
||||||
* @return array
|
/**
|
||||||
*/
|
* 获取优惠券活动分页列表
|
||||||
public function getCouponTypeList($condition = [], $field = '*', $order = 'create_time desc', $limit = null)
|
* @param array $condition
|
||||||
{
|
* @param int $page
|
||||||
$res = model('promotion_coupon_type')->getList($condition, $field, $order, '', '', '', $limit);
|
* @param int $page_size
|
||||||
return $this->success($res);
|
* @param string $order
|
||||||
}
|
* @param string $field
|
||||||
|
* @return array
|
||||||
/**
|
*/
|
||||||
* 获取优惠券活动分页列表
|
public function getCouponTypePageList($condition = [], $page = 1, $page_size = PAGE_LIST_ROWS, $order = '', $field = '*')
|
||||||
* @param array $condition
|
{
|
||||||
* @param int $page
|
$condition[] = [ 'promotion_type', '=', 0 ];
|
||||||
* @param int $page_size
|
$list = model('promotion_coupon_type')->pageList($condition, $field, $order, $page, $page_size);
|
||||||
* @param string $order
|
return $this->success($list);
|
||||||
* @param string $field
|
}
|
||||||
* @return array
|
|
||||||
*/
|
/**
|
||||||
public function getCouponTypePageList($condition = [], $page = 1, $page_size = PAGE_LIST_ROWS, $order = '', $field = '*')
|
* 排序
|
||||||
{
|
* @param $coupon_type_id
|
||||||
$condition[] = [ 'promotion_type', '=', 0 ];
|
* @param $sort
|
||||||
$list = model('promotion_coupon_type')->pageList($condition, $field, $order, $page, $page_size);
|
* @return array
|
||||||
return $this->success($list);
|
*/
|
||||||
}
|
public function couponSort($coupon_type_id, $sort)
|
||||||
|
{
|
||||||
/**
|
$res = model('promotion_coupon_type')->update([ 'sort' => $sort ], [ [ 'coupon_type_id', '=', $coupon_type_id ] ]);
|
||||||
* 排序
|
return $this->success($res);
|
||||||
* @param $coupon_type_id
|
}
|
||||||
* @param $sort
|
|
||||||
* @return array
|
/**
|
||||||
*/
|
* 生成优惠券二维码
|
||||||
public function couponSort($coupon_type_id, $sort)
|
* @param $coupon_type_id
|
||||||
{
|
* @param string $app_type all为全部
|
||||||
$res = model('promotion_coupon_type')->update([ 'sort' => $sort ], [ [ 'coupon_type_id', '=', $coupon_type_id ] ]);
|
* @param string $type 类型 create创建 get获取
|
||||||
return $this->success($res);
|
* @return mixed|array
|
||||||
}
|
*/
|
||||||
|
public function qrcode($coupon_type_id, $app_type, $site_id, $type = 'create')
|
||||||
/**
|
{
|
||||||
* 生成优惠券二维码
|
$res = event('Qrcode', [
|
||||||
* @param $coupon_type_id
|
'site_id' => $site_id,
|
||||||
* @param string $app_type all为全部
|
'app_type' => $app_type,
|
||||||
* @param string $type 类型 create创建 get获取
|
'type' => $type,
|
||||||
* @return mixed|array
|
'data' => [
|
||||||
*/
|
'coupon_type_id' => $coupon_type_id
|
||||||
public function qrcode($coupon_type_id, $app_type, $site_id, $type = 'create')
|
],
|
||||||
{
|
'page' => '/pages_tool/goods/coupon_receive',
|
||||||
$res = event('Qrcode', [
|
'qrcode_path' => 'upload/qrcode/coupon',
|
||||||
'site_id' => $site_id,
|
'qrcode_name' => 'coupon_type_code_' . $coupon_type_id . '_' . $site_id,
|
||||||
'app_type' => $app_type,
|
], true);
|
||||||
'type' => $type,
|
return $res;
|
||||||
'data' => [
|
}
|
||||||
'coupon_type_id' => $coupon_type_id
|
|
||||||
],
|
/**
|
||||||
'page' => '/pages_tool/goods/coupon_receive',
|
* 优惠券定时结束
|
||||||
'qrcode_path' => 'upload/qrcode/coupon',
|
* @param $coupon_type_id
|
||||||
'qrcode_name' => 'coupon_type_code_' . $coupon_type_id . '_' . $site_id,
|
* @return array
|
||||||
], true);
|
*/
|
||||||
return $res;
|
public function couponCronEnd($coupon_type_id)
|
||||||
}
|
{
|
||||||
|
$res = model('promotion_coupon_type')->update([ 'status' => 2 ], [ [ 'coupon_type_id', '=', $coupon_type_id ] ]);
|
||||||
/**
|
return $this->success($res);
|
||||||
* 优惠券定时结束
|
}
|
||||||
* @param $coupon_type_id
|
|
||||||
* @return array
|
public function spread($coupon_type_id, $name, $site_id, $type = 'create')
|
||||||
*/
|
{
|
||||||
public function couponCronEnd($coupon_type_id)
|
$data = [
|
||||||
{
|
'site_id' => $site_id,
|
||||||
$res = model('promotion_coupon_type')->update([ 'status' => 2 ], [ [ 'coupon_type_id', '=', $coupon_type_id ] ]);
|
'app_type' => 'all', // all为全部
|
||||||
return $this->success($res);
|
'type' => $type, // 类型 create创建 get获取
|
||||||
}
|
'data' => [
|
||||||
|
'coupon_type_id' => $coupon_type_id
|
||||||
public function spread($coupon_type_id, $name, $site_id, $type = 'create')
|
],
|
||||||
{
|
'page' => '/pages_tool/goods/coupon_receive',
|
||||||
$data = [
|
'qrcode_path' => 'upload/qrcode/coupon',
|
||||||
'site_id' => $site_id,
|
'qrcode_name' => 'coupon_type_code_' . $coupon_type_id . '_' . $site_id,
|
||||||
'app_type' => 'all', // all为全部
|
];
|
||||||
'type' => $type, // 类型 create创建 get获取
|
event('Qrcode', $data, true);
|
||||||
'data' => [
|
$app_type_list = config('app_type');
|
||||||
'coupon_type_id' => $coupon_type_id
|
$path = [];
|
||||||
],
|
foreach ($app_type_list as $k => $v) {
|
||||||
'page' => '/pages_tool/goods/coupon_receive',
|
switch ( $k ) {
|
||||||
'qrcode_path' => 'upload/qrcode/coupon',
|
case 'h5':
|
||||||
'qrcode_name' => 'coupon_type_code_' . $coupon_type_id . '_' . $site_id,
|
$wap_domain = getH5Domain();
|
||||||
];
|
$path[ $k ][ 'status' ] = 1;
|
||||||
event('Qrcode', $data, true);
|
$path[ $k ][ 'url' ] = $wap_domain . $data[ 'page' ] . '?coupon_type_id=' . $coupon_type_id;
|
||||||
$app_type_list = config('app_type');
|
$path[ $k ][ 'img' ] = 'upload/qrcode/coupon/coupon_type_code_' . $coupon_type_id . '_' . $site_id . '_' . $k . '.png';
|
||||||
$path = [];
|
break;
|
||||||
foreach ($app_type_list as $k => $v) {
|
case 'weapp' :
|
||||||
switch ( $k ) {
|
$config = new ConfigModel();
|
||||||
case 'h5':
|
$res = $config->getConfig([ [ 'site_id', '=', $site_id ], [ 'app_module', '=', 'shop' ], [ 'config_key', '=', 'WEAPP_CONFIG' ] ]);
|
||||||
$wap_domain = getH5Domain();
|
if (!empty($res[ 'data' ])) {
|
||||||
$path[ $k ][ 'status' ] = 1;
|
if (empty($res[ 'data' ][ 'value' ][ 'qrcode' ])) {
|
||||||
$path[ $k ][ 'url' ] = $wap_domain . $data[ 'page' ] . '?coupon_type_id=' . $coupon_type_id;
|
$path[ $k ][ 'status' ] = 2;
|
||||||
$path[ $k ][ 'img' ] = 'upload/qrcode/coupon/coupon_type_code_' . $coupon_type_id . '_' . $site_id . '_' . $k . '.png';
|
$path[ $k ][ 'message' ] = '未配置微信小程序';
|
||||||
break;
|
} else {
|
||||||
case 'weapp' :
|
$path[ $k ][ 'status' ] = 1;
|
||||||
$config = new ConfigModel();
|
$path[ $k ][ 'img' ] = $res[ 'data' ][ 'value' ][ 'qrcode' ];
|
||||||
$res = $config->getConfig([ [ 'site_id', '=', $site_id ], [ 'app_module', '=', 'shop' ], [ 'config_key', '=', 'WEAPP_CONFIG' ] ]);
|
}
|
||||||
if (!empty($res[ 'data' ])) {
|
} else {
|
||||||
if (empty($res[ 'data' ][ 'value' ][ 'qrcode' ])) {
|
$path[ $k ][ 'status' ] = 2;
|
||||||
$path[ $k ][ 'status' ] = 2;
|
$path[ $k ][ 'message' ] = '未配置微信小程序';
|
||||||
$path[ $k ][ 'message' ] = '未配置微信小程序';
|
}
|
||||||
} else {
|
break;
|
||||||
$path[ $k ][ 'status' ] = 1;
|
|
||||||
$path[ $k ][ 'img' ] = $res[ 'data' ][ 'value' ][ 'qrcode' ];
|
case 'wechat' :
|
||||||
}
|
$config = new ConfigModel();
|
||||||
} else {
|
$res = $config->getConfig([ [ 'site_id', '=', $site_id ], [ 'app_module', '=', 'shop' ], [ 'config_key', '=', 'WECHAT_CONFIG' ] ]);
|
||||||
$path[ $k ][ 'status' ] = 2;
|
if (!empty($res[ 'data' ])) {
|
||||||
$path[ $k ][ 'message' ] = '未配置微信小程序';
|
if (empty($res[ 'data' ][ 'value' ][ 'qrcode' ])) {
|
||||||
}
|
$path[ $k ][ 'status' ] = 2;
|
||||||
break;
|
$path[ $k ][ 'message' ] = '未配置微信公众号';
|
||||||
|
} else {
|
||||||
case 'wechat' :
|
$path[ $k ][ 'status' ] = 1;
|
||||||
$config = new ConfigModel();
|
$path[ $k ][ 'img' ] = $res[ 'data' ][ 'value' ][ 'qrcode' ];
|
||||||
$res = $config->getConfig([ [ 'site_id', '=', $site_id ], [ 'app_module', '=', 'shop' ], [ 'config_key', '=', 'WECHAT_CONFIG' ] ]);
|
}
|
||||||
if (!empty($res[ 'data' ])) {
|
} else {
|
||||||
if (empty($res[ 'data' ][ 'value' ][ 'qrcode' ])) {
|
$path[ $k ][ 'status' ] = 2;
|
||||||
$path[ $k ][ 'status' ] = 2;
|
$path[ $k ][ 'message' ] = '未配置微信公众号';
|
||||||
$path[ $k ][ 'message' ] = '未配置微信公众号';
|
}
|
||||||
} else {
|
break;
|
||||||
$path[ $k ][ 'status' ] = 1;
|
}
|
||||||
$path[ $k ][ 'img' ] = $res[ 'data' ][ 'value' ][ 'qrcode' ];
|
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
$path[ $k ][ 'status' ] = 2;
|
$return = [
|
||||||
$path[ $k ][ 'message' ] = '未配置微信公众号';
|
'path' => $path,
|
||||||
}
|
'name' => $name,
|
||||||
break;
|
];
|
||||||
}
|
|
||||||
|
return $this->success($return);
|
||||||
}
|
}
|
||||||
|
|
||||||
$return = [
|
public function urlQrcode($page, $qrcode_param, $promotion_type, $app_type, $site_id)
|
||||||
'path' => $path,
|
{
|
||||||
'name' => $name,
|
$params = [
|
||||||
];
|
'site_id' => $site_id,
|
||||||
|
'data' => $qrcode_param,
|
||||||
return $this->success($return);
|
'page' => $page,
|
||||||
}
|
'promotion_type' => $promotion_type,
|
||||||
|
'app_type' => $app_type,
|
||||||
public function urlQrcode($page, $qrcode_param, $promotion_type, $app_type, $site_id)
|
'h5_path' => $page . '?coupon_type_id=' . $qrcode_param[ 'coupon_type_id' ],
|
||||||
{
|
'qrcode_path' => 'upload/qrcode/coupon',
|
||||||
$params = [
|
'qrcode_name' => 'coupon_type_code_' . $promotion_type . '_' . $qrcode_param[ 'coupon_type_id' ] . '_' . $site_id,
|
||||||
'site_id' => $site_id,
|
];
|
||||||
'data' => $qrcode_param,
|
|
||||||
'page' => $page,
|
$solitaire = event('PromotionQrcode', $params, true);
|
||||||
'promotion_type' => $promotion_type,
|
return $this->success($solitaire);
|
||||||
'app_type' => $app_type,
|
}
|
||||||
'h5_path' => $page . '?coupon_type_id=' . $qrcode_param[ 'coupon_type_id' ],
|
|
||||||
'qrcode_path' => 'upload/qrcode/coupon',
|
|
||||||
'qrcode_name' => 'coupon_type_code_' . $promotion_type . '_' . $qrcode_param[ 'coupon_type_id' ] . '_' . $site_id,
|
|
||||||
];
|
|
||||||
|
|
||||||
$solitaire = event('PromotionQrcode', $params, true);
|
|
||||||
return $this->success($solitaire);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@@ -1,243 +1,235 @@
|
|||||||
<?php
|
<?php
|
||||||
/**
|
|
||||||
|
namespace addon\coupon\model;
|
||||||
|
|
||||||
|
use addon\coupon\dict\CouponDict;
|
||||||
|
use app\model\BaseModel;
|
||||||
|
|
||||||
|
/**
|
||||||
*/
|
* 优惠券
|
||||||
|
*/
|
||||||
namespace addon\coupon\model;
|
class MemberCoupon extends BaseModel
|
||||||
|
{
|
||||||
use addon\coupon\dict\CouponDict;
|
|
||||||
use app\model\BaseModel;
|
/**
|
||||||
|
* 获取会员已领取优惠券
|
||||||
/**
|
* @param $member_id
|
||||||
* 优惠券
|
* @param $state
|
||||||
*/
|
* @param int $site_id
|
||||||
class MemberCoupon extends BaseModel
|
* @param string $order
|
||||||
{
|
* @return array
|
||||||
|
*/
|
||||||
/**
|
public function getMemberCouponList($member_id, $state, $site_id = 0, $order = "fetch_time desc")
|
||||||
* 获取会员已领取优惠券
|
{
|
||||||
* @param $member_id
|
$condition = array (
|
||||||
* @param $state
|
[ "member_id", "=", $member_id ],
|
||||||
* @param int $site_id
|
[ "state", "=", $state ],
|
||||||
* @param string $order
|
);
|
||||||
* @return array
|
if ($site_id > 0) {
|
||||||
*/
|
$condition[] = [ "site_id", "=", $site_id ];
|
||||||
public function getMemberCouponList($member_id, $state, $site_id = 0, $order = "fetch_time desc")
|
}
|
||||||
{
|
$list = model("promotion_coupon")->getList($condition, "*", $order, '', '', '', 0);
|
||||||
$condition = array (
|
return $this->success($list);
|
||||||
[ "member_id", "=", $member_id ],
|
}
|
||||||
[ "state", "=", $state ],
|
|
||||||
);
|
/**
|
||||||
if ($site_id > 0) {
|
* 使用优惠券
|
||||||
$condition[] = [ "site_id", "=", $site_id ];
|
* @param $coupon_id
|
||||||
}
|
* @param $member_id
|
||||||
$list = model("promotion_coupon")->getList($condition, "*", $order, '', '', '', 0);
|
* @param int $order_id
|
||||||
return $this->success($list);
|
* @return array
|
||||||
}
|
*/
|
||||||
|
public function useMemberCoupon($coupon_id, $member_id, $order_id = 0)
|
||||||
/**
|
{
|
||||||
* 使用优惠券
|
//优惠券处理方案
|
||||||
* @param $coupon_id
|
$result = model('promotion_coupon')->update([ 'use_order_id' => $order_id, 'state' => 2, 'use_time' => time() ], [ [ 'coupon_id', '=', $coupon_id ], [ "member_id", "=", $member_id ], [ 'state', '=', 1 ] ]);
|
||||||
* @param $member_id
|
if ($result === false) {
|
||||||
* @param int $order_id
|
return $this->error();
|
||||||
* @return array
|
}
|
||||||
*/
|
return $this->success();
|
||||||
public function useMemberCoupon($coupon_id, $member_id, $order_id = 0)
|
}
|
||||||
{
|
|
||||||
//优惠券处理方案
|
/**
|
||||||
$result = model('promotion_coupon')->update([ 'use_order_id' => $order_id, 'state' => 2, 'use_time' => time() ], [ [ 'coupon_id', '=', $coupon_id ], [ "member_id", "=", $member_id ], [ 'state', '=', 1 ] ]);
|
* 获取会员已领取优惠券数量
|
||||||
if ($result === false) {
|
* @param $member_id
|
||||||
return $this->error();
|
* @param $state
|
||||||
}
|
* @param int $site_id
|
||||||
return $this->success();
|
* @return array
|
||||||
}
|
*/
|
||||||
|
public function getMemberCouponNum($member_id, $state, $site_id = 0)
|
||||||
/**
|
{
|
||||||
* 获取会员已领取优惠券数量
|
$condition = array (
|
||||||
* @param $member_id
|
[ "member_id", "=", $member_id ],
|
||||||
* @param $state
|
[ "state", "=", $state ],
|
||||||
* @param int $site_id
|
);
|
||||||
* @return array
|
if ($site_id > 0) {
|
||||||
*/
|
$condition[] = [ "site_id", "=", $site_id ];
|
||||||
public function getMemberCouponNum($member_id, $state, $site_id = 0)
|
}
|
||||||
{
|
$num = model("promotion_coupon")->getCount($condition);
|
||||||
$condition = array (
|
return $this->success($num);
|
||||||
[ "member_id", "=", $member_id ],
|
}
|
||||||
[ "state", "=", $state ],
|
|
||||||
);
|
/**
|
||||||
if ($site_id > 0) {
|
* 会员是否可领取该优惠券
|
||||||
$condition[] = [ "site_id", "=", $site_id ];
|
* @param $coupon_type_id
|
||||||
}
|
* @param $member_id
|
||||||
$num = model("promotion_coupon")->getCount($condition);
|
* @return array
|
||||||
return $this->success($num);
|
*/
|
||||||
}
|
public function receivedNum($coupon_type_id, $member_id)
|
||||||
|
{
|
||||||
/**
|
$received_num = model('promotion_coupon')->getCount([ [ 'coupon_type_id', '=', $coupon_type_id ], [ 'member_id', '=', $member_id ] ]);
|
||||||
* 会员是否可领取该优惠券
|
return $this->success($received_num);
|
||||||
* @param $coupon_type_id
|
}
|
||||||
* @param $member_id
|
|
||||||
* @return array
|
/**
|
||||||
*/
|
* 获取编码
|
||||||
public function receivedNum($coupon_type_id, $member_id)
|
*/
|
||||||
{
|
public function getCode()
|
||||||
$received_num = model('promotion_coupon')->getCount([ [ 'coupon_type_id', '=', $coupon_type_id ], [ 'member_id', '=', $member_id ] ]);
|
{
|
||||||
return $this->success($received_num);
|
return random_keys(8);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取编码
|
* 会员批量发放优惠券
|
||||||
*/
|
* @param $coupon_type_ids
|
||||||
public function getCode()
|
* @param $site_id
|
||||||
{
|
* @param $member_id
|
||||||
return random_keys(8);
|
* @param int $get_type
|
||||||
}
|
* @param int $is_stock
|
||||||
|
* @param int $related_id
|
||||||
/**
|
* @return array
|
||||||
* 会员批量发放优惠券
|
*/
|
||||||
* @param $coupon_type_ids
|
public function sendCoupon($coupon_type_ids, $site_id, $member_id, $get_type = 4, $is_stock = 0, $related_id = 0)
|
||||||
* @param $site_id
|
{
|
||||||
* @param $member_id
|
//已选优惠券提交数组
|
||||||
* @param int $get_type
|
if (!empty($coupon_type_ids)) {
|
||||||
* @param int $is_stock
|
$res = 0;
|
||||||
* @param int $related_id
|
foreach ($coupon_type_ids as $coupon_type_id) {
|
||||||
* @return array
|
$coupon_type_info = model('promotion_coupon_type')->getInfo([ 'coupon_type_id' => $coupon_type_id, 'site_id' => $site_id, 'status' => 1 ]);
|
||||||
*/
|
if (!empty($coupon_type_info)) {
|
||||||
public function sendCoupon($coupon_type_ids, $site_id, $member_id, $get_type = 4, $is_stock = 0, $related_id = 0)
|
|
||||||
{
|
if ($coupon_type_info[ 'count' ] != -1 || $is_stock == 0) {
|
||||||
//已选优惠券提交数组
|
if ($coupon_type_info[ 'count' ] == $coupon_type_info[ 'lead_count' ]) {
|
||||||
if (!empty($coupon_type_ids)) {
|
return $this->error('', '来迟了该优惠券已被领取完了');
|
||||||
$res = 0;
|
}
|
||||||
foreach ($coupon_type_ids as $coupon_type_id) {
|
}
|
||||||
$coupon_type_info = model('promotion_coupon_type')->getInfo([ 'coupon_type_id' => $coupon_type_id, 'site_id' => $site_id, 'status' => 1 ]);
|
if ($coupon_type_info[ 'max_fetch' ] != 0 && $get_type == 2) {
|
||||||
if (!empty($coupon_type_info)) {
|
//限制领取
|
||||||
|
$member_receive_num = model('promotion_coupon')->getCount([
|
||||||
if ($coupon_type_info[ 'count' ] != -1 || $is_stock == 0) {
|
'coupon_type_id' => $coupon_type_id,
|
||||||
if ($coupon_type_info[ 'count' ] == $coupon_type_info[ 'lead_count' ]) {
|
'member_id' => $member_id,
|
||||||
return $this->error('', '来迟了该优惠券已被领取完了');
|
'get_type' => 2
|
||||||
}
|
]);
|
||||||
}
|
if ($member_receive_num >= $coupon_type_info[ 'max_fetch' ] ) {
|
||||||
if ($coupon_type_info[ 'max_fetch' ] != 0 && $get_type == 2) {
|
return $this->error('', '该优惠券领取已达到上限');
|
||||||
//限制领取
|
}
|
||||||
$member_receive_num = model('promotion_coupon')->getCount([
|
}
|
||||||
'coupon_type_id' => $coupon_type_id,
|
|
||||||
'member_id' => $member_id,
|
$data = [
|
||||||
'get_type' => 2
|
'coupon_type_id' => $coupon_type_id,
|
||||||
]);
|
'site_id' => $site_id,
|
||||||
if ($member_receive_num >= $coupon_type_info[ 'max_fetch' ] ) {
|
'coupon_code' => $this->getCode(),
|
||||||
return $this->error('', '该优惠券领取已达到上限');
|
'member_id' => $member_id,
|
||||||
}
|
'money' => $coupon_type_info[ 'money' ],
|
||||||
}
|
'state' => 1,
|
||||||
|
'get_type' => $get_type,
|
||||||
$data = [
|
'goods_type' => $coupon_type_info[ 'goods_type' ],
|
||||||
'coupon_type_id' => $coupon_type_id,
|
'fetch_time' => time(),
|
||||||
'site_id' => $site_id,
|
'coupon_name' => $coupon_type_info[ 'coupon_name' ],
|
||||||
'coupon_code' => $this->getCode(),
|
'at_least' => $coupon_type_info[ 'at_least' ],
|
||||||
'member_id' => $member_id,
|
'type' => $coupon_type_info[ 'type' ],
|
||||||
'money' => $coupon_type_info[ 'money' ],
|
'discount' => $coupon_type_info[ 'discount' ],
|
||||||
'state' => 1,
|
'discount_limit' => $coupon_type_info[ 'discount_limit' ],
|
||||||
'get_type' => $get_type,
|
'goods_ids' => $coupon_type_info[ 'goods_ids' ],
|
||||||
'goods_type' => $coupon_type_info[ 'goods_type' ],
|
'related_id' => $related_id
|
||||||
'fetch_time' => time(),
|
];
|
||||||
'coupon_name' => $coupon_type_info[ 'coupon_name' ],
|
|
||||||
'at_least' => $coupon_type_info[ 'at_least' ],
|
if ($coupon_type_info[ 'validity_type' ] == 0) {
|
||||||
'type' => $coupon_type_info[ 'type' ],
|
$data[ 'end_time' ] = $coupon_type_info[ 'end_time' ];
|
||||||
'discount' => $coupon_type_info[ 'discount' ],
|
} elseif ($coupon_type_info[ 'validity_type' ] == 1) {
|
||||||
'discount_limit' => $coupon_type_info[ 'discount_limit' ],
|
$data[ 'end_time' ] = ( time() + $coupon_type_info[ 'fixed_term' ] * 86400 );
|
||||||
'goods_ids' => $coupon_type_info[ 'goods_ids' ],
|
}
|
||||||
'related_id' => $related_id
|
|
||||||
];
|
$res = model('promotion_coupon')->add($data);
|
||||||
|
if ($is_stock == 0) {
|
||||||
if ($coupon_type_info[ 'validity_type' ] == 0) {
|
model('promotion_coupon_type')->setInc([ [ 'coupon_type_id', '=', $coupon_type_id ] ], 'lead_count');
|
||||||
$data[ 'end_time' ] = $coupon_type_info[ 'end_time' ];
|
}
|
||||||
} elseif ($coupon_type_info[ 'validity_type' ] == 1) {
|
}
|
||||||
$data[ 'end_time' ] = ( time() + $coupon_type_info[ 'fixed_term' ] * 86400 );
|
}
|
||||||
}
|
if ($res) {
|
||||||
|
return $this->success($res);
|
||||||
$res = model('promotion_coupon')->add($data);
|
} else {
|
||||||
if ($is_stock == 0) {
|
return $this->error();
|
||||||
model('promotion_coupon_type')->setInc([ [ 'coupon_type_id', '=', $coupon_type_id ] ], 'lead_count');
|
}
|
||||||
}
|
} else {
|
||||||
}
|
return $this->error();
|
||||||
}
|
}
|
||||||
if ($res) {
|
}
|
||||||
return $this->success($res);
|
|
||||||
} else {
|
/**
|
||||||
return $this->error();
|
* 回收优惠券
|
||||||
}
|
* @param array $coupon_list
|
||||||
} else {
|
* @param $site_id
|
||||||
return $this->error();
|
* @return array
|
||||||
}
|
*/
|
||||||
}
|
public function recoveryCoupon(array $coupon_list, $site_id)
|
||||||
|
{
|
||||||
/**
|
$coupon = [];
|
||||||
* 回收优惠券
|
foreach ($coupon_list as $coupon_item) {
|
||||||
* @param array $coupon_list
|
if (isset($coupon[ $coupon_item[ 'coupon_type_id' ] ])) {
|
||||||
* @param $site_id
|
$coupon[$coupon_item['coupon_type_id']][] = $coupon_item['coupon_id'];
|
||||||
* @return array
|
} else {
|
||||||
*/
|
$coupon[ $coupon_item[ 'coupon_type_id' ] ] = [ $coupon_item[ 'coupon_id' ] ];
|
||||||
public function recoveryCoupon(array $coupon_list, $site_id)
|
}
|
||||||
{
|
}
|
||||||
$coupon = [];
|
if (!count($coupon)) return $this->error();
|
||||||
foreach ($coupon_list as $coupon_item) {
|
|
||||||
if (isset($coupon[ $coupon_item[ 'coupon_type_id' ] ])) {
|
model('promotion_coupon')->startTrans();
|
||||||
$coupon[$coupon_item['coupon_type_id']][] = $coupon_item['coupon_id'];
|
try {
|
||||||
} else {
|
foreach ($coupon as $coupon_type_id => $coupon_ids) {
|
||||||
$coupon[ $coupon_item[ 'coupon_type_id' ] ] = [ $coupon_item[ 'coupon_id' ] ];
|
$num = model('promotion_coupon')->delete([ [ 'coupon_id', 'in', $coupon_ids ], [ 'site_id', '=', $site_id ], [ 'state', '=', 1 ] ]);
|
||||||
}
|
if ($num) model('promotion_coupon_type')->setDec([ [ 'coupon_type_id', '=', $coupon_type_id ] ], 'lead_count', $num);
|
||||||
}
|
}
|
||||||
if (!count($coupon)) return $this->error();
|
model('promotion_coupon')->commit();
|
||||||
|
return $this->success();
|
||||||
model('promotion_coupon')->startTrans();
|
} catch (\Exception $e) {
|
||||||
try {
|
model('promotion_coupon')->rollback();
|
||||||
foreach ($coupon as $coupon_type_id => $coupon_ids) {
|
return $this->error('', '回收失败');
|
||||||
$num = model('promotion_coupon')->delete([ [ 'coupon_id', 'in', $coupon_ids ], [ 'site_id', '=', $site_id ], [ 'state', '=', 1 ] ]);
|
}
|
||||||
if ($num) model('promotion_coupon_type')->setDec([ [ 'coupon_type_id', '=', $coupon_type_id ] ], 'lead_count', $num);
|
}
|
||||||
}
|
|
||||||
model('promotion_coupon')->commit();
|
/**
|
||||||
return $this->success();
|
* 专用于撤回活动赠送的优惠券
|
||||||
} catch (\Exception $e) {
|
* @return void
|
||||||
model('promotion_coupon')->rollback();
|
*/
|
||||||
return $this->error('', '回收失败');
|
public function cancelByPromotion($data){
|
||||||
}
|
$member_id = $data['member_id'];
|
||||||
}
|
$coupon_data = $data['coupon_data'];//优惠券id相关项
|
||||||
|
$coupon_ids = array_column($coupon_data, 'coupon_type_id');
|
||||||
/**
|
$member_coupon_list = model('promotion_coupon')->getList([
|
||||||
* 专用于撤回活动赠送的优惠券
|
['member_id', '=', $member_id],
|
||||||
* @return void
|
['coupon_type_id', 'in', $coupon_ids],
|
||||||
*/
|
['state', '=', CouponDict::normal]
|
||||||
public function cancelByPromotion($data){
|
], '*');
|
||||||
$member_id = $data['member_id'];
|
$member_coupon_type_group_list = [];
|
||||||
$coupon_data = $data['coupon_data'];//优惠券id相关项
|
foreach($member_coupon_list as $v){
|
||||||
$coupon_ids = array_column($coupon_data, 'coupon_type_id');
|
$member_coupon_type_group_list[$v['coupon_type_id']][] = $v['coupon_id'];
|
||||||
$member_coupon_list = model('promotion_coupon')->getList([
|
}
|
||||||
['member_id', '=', $member_id],
|
$cancel_ids = [];
|
||||||
['coupon_type_id', 'in', $coupon_ids],
|
foreach ($coupon_data as $item) {
|
||||||
['state', '=', CouponDict::normal]
|
$coupon_type_id = $item['coupon_type_id'];
|
||||||
], '*');
|
$num = $item['num'];
|
||||||
$member_coupon_type_group_list = [];
|
$item_coupon_type_group = $member_coupon_type_group_list[$coupon_type_id] ?? [];
|
||||||
foreach($member_coupon_list as $v){
|
if($item_coupon_type_group){
|
||||||
$member_coupon_type_group_list[$v['coupon_type_id']][] = $v['coupon_id'];
|
if(count($item_coupon_type_group) > $num){
|
||||||
}
|
$cancel_ids = array_merge($cancel_ids, array_slice($item_coupon_type_group, 0, $num));
|
||||||
$cancel_ids = [];
|
}else{
|
||||||
foreach ($coupon_data as $item) {
|
$cancel_ids = array_merge($cancel_ids, $item_coupon_type_group);
|
||||||
$coupon_type_id = $item['coupon_type_id'];
|
}
|
||||||
$num = $item['num'];
|
}
|
||||||
$item_coupon_type_group = $member_coupon_type_group_list[$coupon_type_id] ?? [];
|
}
|
||||||
if($item_coupon_type_group){
|
model('promotion_coupon')->update(['state' => CouponDict::close], [['coupon_id', 'in', $cancel_ids]]);
|
||||||
if(count($item_coupon_type_group) > $num){
|
return $this->success();
|
||||||
$cancel_ids = array_merge($cancel_ids, array_slice($item_coupon_type_group, 0, $num));
|
|
||||||
}else{
|
}
|
||||||
$cancel_ids = array_merge($cancel_ids, $item_coupon_type_group);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
model('promotion_coupon')->update(['state' => CouponDict::close], [['coupon_id', 'in', $cancel_ids]]);
|
|
||||||
return $this->success();
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@@ -1,103 +1,94 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
/**
|
namespace addon\coupon\model\share;
|
||||||
|
|
||||||
|
use app\model\share\WchatShareBase as BaseModel;
|
||||||
|
use app\model\system\Config as ConfigModel;
|
||||||
|
use app\model\system\Site as SiteModel;
|
||||||
|
|
||||||
|
/**
|
||||||
*/
|
* 分享
|
||||||
|
*/
|
||||||
namespace addon\coupon\model\share;
|
class WchatShare extends BaseModel
|
||||||
|
{
|
||||||
use app\model\share\WchatShareBase as BaseModel;
|
protected $config = [
|
||||||
use app\model\system\Config as ConfigModel;
|
[
|
||||||
use app\model\system\Site as SiteModel;
|
'title' => '领券中心',
|
||||||
|
'config_key' => 'WCHAT_SHARE_CONFIG_COUPON_LIST',
|
||||||
/**
|
'path' => [ '/pages_tool/goods/coupon' ],
|
||||||
* 分享
|
'method_prefix' => 'couponList',
|
||||||
*/
|
],
|
||||||
class WchatShare extends BaseModel
|
];
|
||||||
{
|
|
||||||
protected $config = [
|
/**
|
||||||
[
|
* 商品列表分享数据
|
||||||
'title' => '领券中心',
|
* @param $param
|
||||||
'config_key' => 'WCHAT_SHARE_CONFIG_COUPON_LIST',
|
* @return array
|
||||||
'path' => [ '/pages_tool/goods/coupon' ],
|
*/
|
||||||
'method_prefix' => 'couponList',
|
protected function couponListShareData($param)
|
||||||
],
|
{
|
||||||
];
|
$site_id = $param[ 'site_id' ] ?? 0;
|
||||||
|
|
||||||
/**
|
//站点设置
|
||||||
* 商品列表分享数据
|
$site_model = new SiteModel();
|
||||||
* @param $param
|
$site_info = $site_model->getSiteInfo([ [ 'site_id', '=', $site_id ] ])[ 'data' ];
|
||||||
* @return array
|
|
||||||
*/
|
//跳转路径
|
||||||
protected function couponListShareData($param)
|
$link = $this->getShareLink($param);
|
||||||
{
|
|
||||||
$site_id = $param[ 'site_id' ] ?? 0;
|
//获取和替换配置数据
|
||||||
|
$config_method = preg_replace('/Data$/', 'Config', __FUNCTION__);
|
||||||
//站点设置
|
$config_data = $this->$config_method($param);
|
||||||
$site_model = new SiteModel();
|
$title = $config_data[ 'value' ][ 'title' ];
|
||||||
$site_info = $site_model->getSiteInfo([ [ 'site_id', '=', $site_id ] ])[ 'data' ];
|
$desc = $config_data[ 'value' ][ 'desc' ];
|
||||||
|
$image_url = $config_data[ 'value' ][ 'imgUrl' ] ?: $site_info[ 'logo_square' ];
|
||||||
//跳转路径
|
|
||||||
$link = $this->getShareLink($param);
|
$data = [
|
||||||
|
'title' => $title,
|
||||||
//获取和替换配置数据
|
'desc' => $desc,
|
||||||
$config_method = preg_replace('/Data$/', 'Config', __FUNCTION__);
|
'link' => $link,
|
||||||
$config_data = $this->$config_method($param);
|
'imgUrl' => $image_url,
|
||||||
$title = $config_data[ 'value' ][ 'title' ];
|
];
|
||||||
$desc = $config_data[ 'value' ][ 'desc' ];
|
return [
|
||||||
$image_url = $config_data[ 'value' ][ 'imgUrl' ] ?: $site_info[ 'logo_square' ];
|
'permission' => [
|
||||||
|
'hideOptionMenu' => false,
|
||||||
$data = [
|
'hideMenuItems' => [],
|
||||||
'title' => $title,
|
],
|
||||||
'desc' => $desc,
|
'data' => $data,//分享内容
|
||||||
'link' => $link,
|
];
|
||||||
'imgUrl' => $image_url,
|
}
|
||||||
];
|
|
||||||
return [
|
/**
|
||||||
'permission' => [
|
* 商品列表分享配置
|
||||||
'hideOptionMenu' => false,
|
* @param $param
|
||||||
'hideMenuItems' => [],
|
* @return array
|
||||||
],
|
*/
|
||||||
'data' => $data,//分享内容
|
protected function couponListShareConfig($param)
|
||||||
];
|
{
|
||||||
}
|
$site_id = $param[ 'site_id' ];
|
||||||
|
$config = $param[ 'config' ];
|
||||||
/**
|
|
||||||
* 商品列表分享配置
|
$config_model = new ConfigModel();
|
||||||
* @param $param
|
$data = $config_model->getConfig([
|
||||||
* @return array
|
[ 'site_id', '=', $site_id ],
|
||||||
*/
|
[ 'app_module', '=', 'shop' ],
|
||||||
protected function couponListShareConfig($param)
|
[ 'config_key', '=', $config[ 'config_key' ] ],
|
||||||
{
|
])[ 'data' ];
|
||||||
$site_id = $param[ 'site_id' ];
|
if (empty($data[ 'value' ])) {
|
||||||
$config = $param[ 'config' ];
|
$data[ 'value' ] = [
|
||||||
|
'title' => '送你一张优惠券',
|
||||||
$config_model = new ConfigModel();
|
'desc' => "优惠多多\n好物多多",
|
||||||
$data = $config_model->getConfig([
|
'imgUrl' => '',
|
||||||
[ 'site_id', '=', $site_id ],
|
];
|
||||||
[ 'app_module', '=', 'shop' ],
|
}
|
||||||
[ 'config_key', '=', $config[ 'config_key' ] ],
|
if (empty($data[ 'value' ][ 'imgUrl' ])) {
|
||||||
])[ 'data' ];
|
$data[ 'value' ][ 'imgUrl' ] = img('addon/coupon/icon.png');
|
||||||
if (empty($data[ 'value' ])) {
|
}
|
||||||
$data[ 'value' ] = [
|
$variable = [];
|
||||||
'title' => '送你一张优惠券',
|
|
||||||
'desc' => "优惠多多\n好物多多",
|
return [
|
||||||
'imgUrl' => '',
|
'value' => $data[ 'value' ],
|
||||||
];
|
'variable' => $variable,
|
||||||
}
|
];
|
||||||
if (empty($data[ 'value' ][ 'imgUrl' ])) {
|
}
|
||||||
$data[ 'value' ][ 'imgUrl' ] = img('addon/coupon/icon.png');
|
}
|
||||||
}
|
|
||||||
$variable = [];
|
|
||||||
|
|
||||||
return [
|
|
||||||
'value' => $data[ 'value' ],
|
|
||||||
'variable' => $variable,
|
|
||||||
];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,91 +1,82 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
/**
|
namespace addon\coupon\model\share;
|
||||||
|
|
||||||
|
use app\model\share\WeappShareBase;
|
||||||
|
use app\model\system\Config as ConfigModel;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 分享
|
||||||
*/
|
*/
|
||||||
|
class WeappShare extends WeappShareBase
|
||||||
namespace addon\coupon\model\share;
|
{
|
||||||
|
protected $config = [
|
||||||
use app\model\share\WeappShareBase;
|
[
|
||||||
use app\model\system\Config as ConfigModel;
|
'title' => '领券中心',
|
||||||
|
'config_key' => 'WEAPP_SHARE_CONFIG_COUPON_LIST',
|
||||||
/**
|
'path' => [ '/pages_tool/goods/coupon' ],
|
||||||
* 分享
|
'method_prefix' => 'couponList',
|
||||||
*/
|
],
|
||||||
class WeappShare extends WeappShareBase
|
];
|
||||||
{
|
|
||||||
protected $config = [
|
protected $sort = 2;
|
||||||
[
|
|
||||||
'title' => '领券中心',
|
/***************************** 领券中心 ***************************/
|
||||||
'config_key' => 'WEAPP_SHARE_CONFIG_COUPON_LIST',
|
|
||||||
'path' => [ '/pages_tool/goods/coupon' ],
|
/**
|
||||||
'method_prefix' => 'couponList',
|
* 首页分享数据
|
||||||
],
|
* @param $param
|
||||||
];
|
* @return array
|
||||||
|
*/
|
||||||
protected $sort = 2;
|
protected function couponListShareData($param)
|
||||||
|
{
|
||||||
/***************************** 领券中心 ***************************/
|
//获取和替换配置数据
|
||||||
|
$config_data = $this->couponListShareConfig($param);
|
||||||
/**
|
$title = $config_data[ 'value' ][ 'title' ];
|
||||||
* 首页分享数据
|
$image_url = $config_data[ 'value' ][ 'imageUrl' ] ? img($config_data[ 'value' ][ 'imageUrl' ]) : '';
|
||||||
* @param $param
|
$path = $this->getSharePath($param);
|
||||||
* @return array
|
|
||||||
*/
|
$data = [
|
||||||
protected function couponListShareData($param)
|
'title' => $title,
|
||||||
{
|
'path' => $path,
|
||||||
//获取和替换配置数据
|
'imageUrl' => $image_url,
|
||||||
$config_data = $this->couponListShareConfig($param);
|
];
|
||||||
$title = $config_data[ 'value' ][ 'title' ];
|
return [
|
||||||
$image_url = $config_data[ 'value' ][ 'imageUrl' ] ? img($config_data[ 'value' ][ 'imageUrl' ]) : '';
|
'permission' => [
|
||||||
$path = $this->getSharePath($param);
|
'onShareAppMessage' => true,
|
||||||
|
'onShareTimeline' => true,
|
||||||
$data = [
|
],
|
||||||
'title' => $title,
|
'data' => $data,//分享内容
|
||||||
'path' => $path,
|
];
|
||||||
'imageUrl' => $image_url,
|
}
|
||||||
];
|
|
||||||
return [
|
/**
|
||||||
'permission' => [
|
* 首页分享配置
|
||||||
'onShareAppMessage' => true,
|
* @param $param
|
||||||
'onShareTimeline' => true,
|
* @return array
|
||||||
],
|
*/
|
||||||
'data' => $data,//分享内容
|
protected function couponListShareConfig($param)
|
||||||
];
|
{
|
||||||
}
|
$site_id = $param[ 'site_id' ];
|
||||||
|
$config = $param[ 'config' ];
|
||||||
/**
|
|
||||||
* 首页分享配置
|
$config_model = new ConfigModel();
|
||||||
* @param $param
|
$data = $config_model->getConfig([
|
||||||
* @return array
|
[ 'site_id', '=', $site_id ],
|
||||||
*/
|
[ 'app_module', '=', 'shop' ],
|
||||||
protected function couponListShareConfig($param)
|
[ 'config_key', '=', $config[ 'config_key' ] ],
|
||||||
{
|
])[ 'data' ];
|
||||||
$site_id = $param[ 'site_id' ];
|
if (empty($data[ 'value' ])) {
|
||||||
$config = $param[ 'config' ];
|
$data[ 'value' ] = [
|
||||||
|
'title' => '送你一张优惠券,快来领取吧',
|
||||||
$config_model = new ConfigModel();
|
'imageUrl' => '',
|
||||||
$data = $config_model->getConfig([
|
];
|
||||||
[ 'site_id', '=', $site_id ],
|
}
|
||||||
[ 'app_module', '=', 'shop' ],
|
$variable = [];
|
||||||
[ 'config_key', '=', $config[ 'config_key' ] ],
|
|
||||||
])[ 'data' ];
|
return [
|
||||||
if (empty($data[ 'value' ])) {
|
'value' => $data[ 'value' ],
|
||||||
$data[ 'value' ] = [
|
'variable' => $variable,
|
||||||
'title' => '送你一张优惠券,快来领取吧',
|
];
|
||||||
'imageUrl' => '',
|
}
|
||||||
];
|
}
|
||||||
}
|
|
||||||
$variable = [];
|
|
||||||
|
|
||||||
return [
|
|
||||||
'value' => $data[ 'value' ],
|
|
||||||
'variable' => $variable,
|
|
||||||
];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,110 +1,102 @@
|
|||||||
<?php
|
<?php
|
||||||
/**
|
|
||||||
|
namespace addon\coupon\shopapi\controller;
|
||||||
|
|
||||||
|
use addon\coupon\model\Coupon as CouponModel;
|
||||||
|
use addon\coupon\model\CouponType as CouponTypeModel;
|
||||||
|
use addon\coupon\model\MemberCoupon;
|
||||||
|
use app\shopapi\controller\BaseApi;
|
||||||
*/
|
|
||||||
|
/**
|
||||||
namespace addon\coupon\shopapi\controller;
|
* 优惠券
|
||||||
|
*/
|
||||||
use addon\coupon\model\Coupon as CouponModel;
|
class Coupon extends BaseApi
|
||||||
use addon\coupon\model\CouponType as CouponTypeModel;
|
{
|
||||||
use addon\coupon\model\MemberCoupon;
|
|
||||||
use app\shopapi\controller\BaseApi;
|
public function __construct()
|
||||||
|
{
|
||||||
/**
|
//执行父类构造函数
|
||||||
* 优惠券
|
parent::__construct();
|
||||||
*/
|
|
||||||
class Coupon extends BaseApi
|
$token = $this->checkToken();
|
||||||
{
|
if ($token[ 'code' ] < 0) {
|
||||||
|
echo $this->response($token);
|
||||||
public function __construct()
|
exit;
|
||||||
{
|
}
|
||||||
//执行父类构造函数
|
}
|
||||||
parent::__construct();
|
|
||||||
|
/**
|
||||||
$token = $this->checkToken();
|
* 活动列表
|
||||||
if ($token[ 'code' ] < 0) {
|
*/
|
||||||
echo $this->response($token);
|
public function lists()
|
||||||
exit;
|
{
|
||||||
}
|
$coupon_type_model = new CouponTypeModel();
|
||||||
}
|
|
||||||
|
$page = $this->params['page'] ?? 1;
|
||||||
/**
|
$page_size = $this->params['page_size'] ?? PAGE_LIST_ROWS;
|
||||||
* 活动列表
|
$coupon_name = $this->params['coupon_name'] ?? '';
|
||||||
*/
|
$status = $this->params['status'] ?? '';
|
||||||
public function lists()
|
|
||||||
{
|
$condition = [];
|
||||||
$coupon_type_model = new CouponTypeModel();
|
if ($status !== '') {
|
||||||
|
$condition[] = [ 'status', '=', $status ];
|
||||||
$page = $this->params['page'] ?? 1;
|
}
|
||||||
$page_size = $this->params['page_size'] ?? PAGE_LIST_ROWS;
|
$type = $this->params['type'] ?? '';
|
||||||
$coupon_name = $this->params['coupon_name'] ?? '';
|
if ($type) {
|
||||||
$status = $this->params['status'] ?? '';
|
$condition[] = [ 'type', '=', $type ];
|
||||||
|
}
|
||||||
$condition = [];
|
//类型
|
||||||
if ($status !== '') {
|
$validity_type = $this->params['validity_type'] ?? '';
|
||||||
$condition[] = [ 'status', '=', $status ];
|
if ($validity_type) {
|
||||||
}
|
$start_time = $this->params['start_time'] ?? '';
|
||||||
$type = $this->params['type'] ?? '';
|
$end_time = $this->params['end_time'] ?? '';
|
||||||
if ($type) {
|
switch ( $validity_type ) {
|
||||||
$condition[] = [ 'type', '=', $type ];
|
case 1: //固定
|
||||||
}
|
$condition[] = [ 'end_time', 'between', [ $start_time, $end_time ] ];
|
||||||
//类型
|
break;
|
||||||
$validity_type = $this->params['validity_type'] ?? '';
|
case 2:
|
||||||
if ($validity_type) {
|
$condition[] = [ 'fixed_term', 'between', [ $start_time, $end_time ] ];
|
||||||
$start_time = $this->params['start_time'] ?? '';
|
break;
|
||||||
$end_time = $this->params['end_time'] ?? '';
|
}
|
||||||
switch ( $validity_type ) {
|
}
|
||||||
case 1: //固定
|
|
||||||
$condition[] = [ 'end_time', 'between', [ $start_time, $end_time ] ];
|
$condition[] = [ 'site_id', '=', $this->site_id ];
|
||||||
break;
|
$condition[] = [ 'coupon_name', 'like', '%' . $coupon_name . '%' ];
|
||||||
case 2:
|
$order = 'create_time desc';
|
||||||
$condition[] = [ 'fixed_term', 'between', [ $start_time, $end_time ] ];
|
$field = '*';
|
||||||
break;
|
|
||||||
}
|
$res = $coupon_type_model->getCouponTypePageList($condition, $page, $page_size, $order, $field);
|
||||||
}
|
//获取优惠券状态
|
||||||
|
$coupon_type_status_arr = $coupon_type_model->getCouponTypeStatus();
|
||||||
$condition[] = [ 'site_id', '=', $this->site_id ];
|
foreach ($res[ 'data' ][ 'list' ] as $key => $val) {
|
||||||
$condition[] = [ 'coupon_name', 'like', '%' . $coupon_name . '%' ];
|
$res[ 'data' ][ 'list' ][ $key ][ 'status_name' ] = $coupon_type_status_arr[ $val[ 'status' ] ];
|
||||||
$order = 'create_time desc';
|
}
|
||||||
$field = '*';
|
|
||||||
|
return $this->response($res);
|
||||||
$res = $coupon_type_model->getCouponTypePageList($condition, $page, $page_size, $order, $field);
|
}
|
||||||
//获取优惠券状态
|
|
||||||
$coupon_type_status_arr = $coupon_type_model->getCouponTypeStatus();
|
/**
|
||||||
foreach ($res[ 'data' ][ 'list' ] as $key => $val) {
|
* 发放优惠券
|
||||||
$res[ 'data' ][ 'list' ][ $key ][ 'status_name' ] = $coupon_type_status_arr[ $val[ 'status' ] ];
|
*/
|
||||||
}
|
public function send()
|
||||||
|
{
|
||||||
return $this->response($res);
|
$member_id = $this->params['member_id'] ?? 0;
|
||||||
}
|
$coupon_type_ids = $this->params['parent'] ?? '';
|
||||||
|
$get_type = $this->params['get_type'] ?? 4;
|
||||||
/**
|
$site_id = $this->site_id;
|
||||||
* 发放优惠券
|
$parent = $coupon_type_ids;
|
||||||
*/
|
if (empty($parent)) {
|
||||||
public function send()
|
return $this->error('', 'REQUEST_COUPON_TYPE_ID');
|
||||||
{
|
}
|
||||||
$member_id = $this->params['member_id'] ?? 0;
|
$parent = explode(',', $parent);
|
||||||
$coupon_type_ids = $this->params['parent'] ?? '';
|
if (count($parent) == 1) {
|
||||||
$get_type = $this->params['get_type'] ?? 4;
|
$coupon_model = new CouponModel();
|
||||||
$site_id = $this->site_id;
|
$res = $coupon_model->receiveCoupon($parent[ 0 ], $site_id, $member_id, $get_type);
|
||||||
$parent = $coupon_type_ids;
|
} else {
|
||||||
if (empty($parent)) {
|
$member_coupon_model = new MemberCoupon();
|
||||||
return $this->error('', 'REQUEST_COUPON_TYPE_ID');
|
$res = $member_coupon_model->sendCoupon(explode(',', $coupon_type_ids), $site_id, $member_id, $get_type);
|
||||||
}
|
}
|
||||||
$parent = explode(',', $parent);
|
return $this->response($res);
|
||||||
if (count($parent) == 1) {
|
}
|
||||||
$coupon_model = new CouponModel();
|
|
||||||
$res = $coupon_model->receiveCoupon($parent[ 0 ], $site_id, $member_id, $get_type);
|
|
||||||
} else {
|
|
||||||
$member_coupon_model = new MemberCoupon();
|
|
||||||
$res = $member_coupon_model->sendCoupon(explode(',', $coupon_type_ids), $site_id, $member_id, $get_type);
|
|
||||||
}
|
|
||||||
return $this->response($res);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
File diff suppressed because it is too large
Load Diff
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user