Compare commits
25 Commits
73f67b4143
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| d0e34f36bd | |||
| 24273db8dd | |||
| 9393185f25 | |||
| a98d2b56ee | |||
| 4463f73efb | |||
| d75808951e | |||
| f0970446f9 | |||
| c092662ebe | |||
| 6c59e41b32 | |||
| b0e87b3fb3 | |||
| d0dc7930ad | |||
| 594aa137ef | |||
| 005c775694 | |||
| 26fb271a54 | |||
| dd6745fe24 | |||
| 7b5d43f0e8 | |||
| ffc1404baa | |||
| d98ac8f146 | |||
| 66bcd8061a | |||
| 8704434c36 | |||
| 70c4966aad | |||
| f598cc8157 | |||
| 92948fa856 | |||
| 0a45a8fbb9 | |||
| 873c7cf9c2 |
+8
-8
@@ -2,14 +2,14 @@
|
||||
debug = true
|
||||
[server]
|
||||
port=8585
|
||||
domain=www.wenjb.com
|
||||
domain=www.shun777.com
|
||||
https=false
|
||||
[mysql]
|
||||
host =127.0.0.1
|
||||
port = 3307
|
||||
database = questionnaire
|
||||
username = questionnaire
|
||||
password = ba9b492bda93ad4d
|
||||
port = 3306
|
||||
database = imadmin
|
||||
username = imadmin
|
||||
password = ejkFmaAXAHXyNXd2
|
||||
charset = utf8mb4
|
||||
collation = utf8mb4_general_ci
|
||||
prefix = wa_
|
||||
@@ -18,13 +18,13 @@ engine =
|
||||
[mongodb]
|
||||
host = 127.0.0.1
|
||||
port = 27017
|
||||
database = questionnaire_m
|
||||
username = commie
|
||||
database = imadmin_mongo
|
||||
username = root
|
||||
password = n1e5a6s6m7
|
||||
[redis]
|
||||
host = 127.0.0.1
|
||||
port = 6379
|
||||
database = 10
|
||||
database = 0
|
||||
password = n1e5a6s6m7
|
||||
prefix = q_
|
||||
|
||||
|
||||
@@ -9,3 +9,4 @@ app/command/Test.php
|
||||
runtime
|
||||
vendor
|
||||
public/shunliao.apk
|
||||
.user.ini
|
||||
|
||||
@@ -0,0 +1,232 @@
|
||||
---
|
||||
name: "webman-openim-admin"
|
||||
description: "专门针对基于webman的openim管理项目,使用了think-cache、think-orm、think-template、redis-queue、webman/event、tinywan/validate等依赖。用于项目的开发、维护和问题排查。"
|
||||
---
|
||||
|
||||
# Webman OpenIM Admin 技能
|
||||
|
||||
## 项目概述
|
||||
|
||||
本技能专门针对基于 webman 框架开发的 OpenIM 管理项目,该项目使用了以下核心依赖:
|
||||
|
||||
- **think-cache**: 缓存管理
|
||||
- **think-orm**: 数据库 ORM 框架
|
||||
- **think-template**: 模板引擎
|
||||
- **redis-queue**: Redis 队列管理
|
||||
- **webman/event**: 事件系统
|
||||
- **tinywan/validate**: 数据验证
|
||||
|
||||
## 功能特性
|
||||
|
||||
### 1. 项目结构分析
|
||||
- 分析项目目录结构
|
||||
- 识别核心模块和文件
|
||||
- 理解依赖关系
|
||||
|
||||
### 2. 代码开发与维护
|
||||
- 基于现有代码风格和模式进行开发
|
||||
- 提供符合项目规范的代码建议
|
||||
- 帮助排查和修复常见问题
|
||||
|
||||
### 3. 依赖管理
|
||||
- 分析 composer.json 配置
|
||||
- 提供依赖版本建议
|
||||
- 处理依赖冲突问题
|
||||
|
||||
### 4. 数据库操作
|
||||
- 基于 think-orm 的数据库操作指导
|
||||
- 模型定义和关系映射
|
||||
- 数据库迁移和种子数据管理
|
||||
|
||||
### 5. 缓存策略
|
||||
- 基于 think-cache 的缓存配置和使用
|
||||
- 缓存优化建议
|
||||
- 缓存一致性管理
|
||||
|
||||
### 6. 队列管理
|
||||
- redis-queue 的配置和使用
|
||||
- 队列任务的创建和监控
|
||||
- 队列性能优化
|
||||
|
||||
### 7. 事件系统
|
||||
- webman/event 的配置和使用
|
||||
- 事件监听和触发
|
||||
- 事件驱动架构设计
|
||||
|
||||
### 8. 数据验证
|
||||
- tinywan/validate 的配置和使用
|
||||
- 表单验证规则定义
|
||||
- 自定义验证规则开发
|
||||
|
||||
## 触发条件
|
||||
|
||||
当用户需要:
|
||||
- 了解项目结构和依赖
|
||||
- 开发新功能或修改现有功能
|
||||
- 排查项目中的问题
|
||||
- 优化项目性能
|
||||
- 配置或调整项目依赖
|
||||
- 进行数据库相关操作
|
||||
- 实现缓存策略
|
||||
- 管理队列任务
|
||||
- 使用事件系统
|
||||
- 进行数据验证
|
||||
|
||||
## 使用示例
|
||||
|
||||
### 示例 1: 分析项目结构
|
||||
```bash
|
||||
# 查看项目目录结构
|
||||
ls -la
|
||||
|
||||
# 查看 composer.json 了解依赖
|
||||
cat composer.json
|
||||
```
|
||||
|
||||
### 示例 2: 数据库操作
|
||||
```php
|
||||
// 使用 think-orm 进行数据库查询
|
||||
use think\Model;
|
||||
|
||||
class User extends Model
|
||||
{
|
||||
protected $table = 'user';
|
||||
}
|
||||
|
||||
// 查询用户列表
|
||||
$users = User::where('status', 1)->select();
|
||||
```
|
||||
|
||||
### 示例 3: 缓存使用
|
||||
```php
|
||||
// 使用 think-cache
|
||||
use think\facade\Cache;
|
||||
|
||||
// 设置缓存
|
||||
Cache::set('key', 'value', 3600);
|
||||
|
||||
// 获取缓存
|
||||
$value = Cache::get('key');
|
||||
```
|
||||
|
||||
### 示例 4: 队列任务
|
||||
```php
|
||||
// 使用 redis-queue
|
||||
use support\Queue;
|
||||
|
||||
// 推送任务
|
||||
Queue::push('App\\Jobs\\SendEmail', ['email' => 'user@example.com']);
|
||||
```
|
||||
|
||||
### 示例 5: 事件监听
|
||||
```php
|
||||
// 使用 webman/event
|
||||
use support\Event;
|
||||
|
||||
// 监听事件
|
||||
Event::listen('user.registered', function ($user) {
|
||||
// 处理用户注册事件
|
||||
});
|
||||
|
||||
// 触发事件
|
||||
Event::trigger('user.registered', $user);
|
||||
```
|
||||
|
||||
### 示例 6: 数据验证
|
||||
```php
|
||||
// 使用 tinywan/validate
|
||||
use Tinywan\Validate\Validate;
|
||||
|
||||
$validate = new Validate();
|
||||
$validate->rule([
|
||||
'name' => 'require|max:25',
|
||||
'email' => 'require|email',
|
||||
]);
|
||||
|
||||
if (!$validate->check($data)) {
|
||||
return $validate->getError();
|
||||
}
|
||||
```
|
||||
|
||||
## 项目配置建议
|
||||
|
||||
1. **composer.json** 配置:
|
||||
- 保持依赖版本的稳定性
|
||||
- 定期更新依赖以获取安全补丁
|
||||
|
||||
2. **数据库配置**:
|
||||
- 优化数据库连接池设置
|
||||
- 合理使用索引
|
||||
- 定期备份数据库
|
||||
|
||||
3. **缓存配置**:
|
||||
- 根据业务场景选择合适的缓存策略
|
||||
- 设置合理的缓存过期时间
|
||||
- 考虑缓存预热机制
|
||||
|
||||
4. **队列配置**:
|
||||
- 合理设置队列 worker 数量
|
||||
- 监控队列任务执行状态
|
||||
- 实现失败重试机制
|
||||
|
||||
5. **性能优化**:
|
||||
- 启用 OPcache
|
||||
- 优化数据库查询
|
||||
- 使用合适的缓存策略
|
||||
- 合理设计事件系统
|
||||
|
||||
## 常见问题与解决方案
|
||||
|
||||
1. **依赖冲突**:
|
||||
- 检查 composer.json 中的版本约束
|
||||
- 使用 `composer update` 解决版本冲突
|
||||
|
||||
2. **数据库连接问题**:
|
||||
- 检查数据库配置文件
|
||||
- 确认数据库服务是否正常运行
|
||||
|
||||
3. **缓存失效**:
|
||||
- 检查缓存配置
|
||||
- 确认缓存服务是否正常
|
||||
|
||||
4. **队列任务失败**:
|
||||
- 检查队列配置
|
||||
- 查看任务执行日志
|
||||
- 实现失败重试机制
|
||||
|
||||
5. **事件不触发**:
|
||||
- 检查事件监听注册
|
||||
- 确认事件触发代码
|
||||
|
||||
6. **验证失败**:
|
||||
- 检查验证规则
|
||||
- 确认输入数据格式
|
||||
|
||||
## 最佳实践
|
||||
|
||||
1. **代码组织**:
|
||||
- 遵循 PSR 代码规范
|
||||
- 合理使用命名空间
|
||||
- 保持代码结构清晰
|
||||
|
||||
2. **安全性**:
|
||||
- 防止 SQL 注入
|
||||
- 防止 XSS 攻击
|
||||
- 保护敏感信息
|
||||
|
||||
3. **可维护性**:
|
||||
- 编写清晰的注释
|
||||
- 使用一致的代码风格
|
||||
- 遵循设计模式
|
||||
|
||||
4. **性能**:
|
||||
- 优化数据库查询
|
||||
- 合理使用缓存
|
||||
- 减少不必要的计算
|
||||
|
||||
5. **扩展性**:
|
||||
- 采用模块化设计
|
||||
- 依赖注入
|
||||
- 接口分离
|
||||
|
||||
本技能旨在帮助开发者更高效地开发和维护基于 webman 的 OpenIM 管理项目,提供专业的技术支持和最佳实践建议。
|
||||
@@ -2,7 +2,6 @@
|
||||
namespace app\api\controller;
|
||||
|
||||
use support\Request;
|
||||
use taoser\facade\Validate;
|
||||
use app\model\Address as AddressModel;
|
||||
use hg\apidoc\annotation as Apidoc;
|
||||
|
||||
@@ -59,7 +58,7 @@ class AddressController extends BaseController{
|
||||
*/
|
||||
public function create()
|
||||
{
|
||||
//captcha_verfiy('image','create_address');
|
||||
//captcha_verify('image','create_address');
|
||||
//* @Apidoc\Param("code", type="string", require=true, desc="图形验证码 event=create_address")
|
||||
//$trade_password = input('trade_password');
|
||||
//\support\Jwt::verify_trade_password($trade_password);
|
||||
@@ -91,7 +90,7 @@ class AddressController extends BaseController{
|
||||
*/
|
||||
public function update()
|
||||
{
|
||||
//captcha_verfiy('image','update_address');
|
||||
//captcha_verify('image','update_address');
|
||||
//$trade_password = input('trade_password');
|
||||
//\support\Jwt::verify_trade_password($trade_password);
|
||||
$data = [
|
||||
|
||||
@@ -0,0 +1,110 @@
|
||||
<?php
|
||||
|
||||
namespace app\api\controller;
|
||||
|
||||
use support\Request;
|
||||
use support\Response;
|
||||
use hg\apidoc\annotation as Apidoc;
|
||||
use app\model\User;
|
||||
use app\model\Album as AlbumModel;
|
||||
|
||||
/**
|
||||
* 群相册
|
||||
*/
|
||||
class AlbumController extends BaseController
|
||||
{
|
||||
public $noNeedAuth = ['*'];
|
||||
public $noNeedLogin = [];
|
||||
/**
|
||||
* @Apidoc\Title("群相册列表")
|
||||
* @Apidoc\Method("POST")
|
||||
* @Apidoc\Param("groupID", type="string", require=true, desc="群ID")
|
||||
* @Apidoc\Param("offset", type="int", require=false, desc="偏移量,和页码二选一",default=0)
|
||||
* @Apidoc\Param("page", type="int", require=false, desc="页码",default=1)
|
||||
* @Apidoc\Param("limit", type="int", require=true, desc="分页大小",default=10)
|
||||
*/
|
||||
function list(Request $request): Response
|
||||
{
|
||||
$user = \support\Jwt::getUser();
|
||||
$limit = $request->post('limit',10);
|
||||
$offset = $request->post('offset',0);
|
||||
$groupID = $request->post('groupID') ?:0;
|
||||
if(!$groupID){
|
||||
return $this->error('groupID is invalid');
|
||||
}
|
||||
//$ls = $this->get_user_in_group(groupID);
|
||||
$query = AlbumModel::where('groupID',$groupID)
|
||||
->order('id','desc');
|
||||
if($offset){
|
||||
$list = $query->where('id','<',$offset)->limit($offset,$limit);
|
||||
}else{
|
||||
$list = $query->paginate($limit);
|
||||
}
|
||||
$list->each(function($item){
|
||||
if($item->image){
|
||||
$item['image_detail'] = \support\think\Db::name('gallery')->where('id',$item->image)->find();
|
||||
}
|
||||
return $item;
|
||||
});
|
||||
return $this->success('ok',$list);
|
||||
}
|
||||
/**
|
||||
* @Apidoc\Title("创建相册")
|
||||
* @Apidoc\Method("POST")
|
||||
* @Apidoc\Param("groupID", type="string", require=true, desc="群ID")
|
||||
* @Apidoc\Param("title", type="string", require=true, desc="标题")
|
||||
* @Apidoc\Param("image", type="int", require=false, desc="封面ID")
|
||||
*/
|
||||
function create(Request $request): Response
|
||||
{
|
||||
$user_id = \support\Jwt\JwtToken::getCurrentId();
|
||||
$data = [
|
||||
'userID' => $user_id,
|
||||
'groupID' => input('groupID'),
|
||||
'title' => input('title'),
|
||||
'image' => input('image'),
|
||||
];
|
||||
log_alert($data);
|
||||
|
||||
if(!$data['groupID']){
|
||||
return $this->error(__('Invalid parameters'));
|
||||
}
|
||||
$result = AlbumModel::create($data);
|
||||
return $this->success('ok',$result);
|
||||
}
|
||||
/**
|
||||
* @Apidoc\Title("更新")
|
||||
* @Apidoc\Method("POST")
|
||||
* @Apidoc\Param("id", type="string", require=true, desc="ID")
|
||||
* @Apidoc\Param("title", type="string", require=true, desc="标题")
|
||||
* @Apidoc\Param("image", type="int", require=false, desc="封面ID")
|
||||
*/
|
||||
function update(Request $request): Response
|
||||
{
|
||||
$id = $request->input('id');
|
||||
$image = $request->input('image');
|
||||
$title = $request->input('title');
|
||||
$album = AlbumModel::find($id);
|
||||
if($title){
|
||||
$album->title = $title;
|
||||
}
|
||||
if($image){
|
||||
$album->image = $image;
|
||||
}
|
||||
$album->save();
|
||||
return $this->success('ok',$album);
|
||||
}
|
||||
/**
|
||||
* @Apidoc\Title("删除")
|
||||
* @Apidoc\Method("POST")
|
||||
* @Apidoc\Param("id", type="string", require=true, desc="ID")
|
||||
*/
|
||||
function delete(Request $request): Response
|
||||
{
|
||||
$id = Input('id');
|
||||
$album = AlbumModel::whereIn('id',condition: $id)->find();
|
||||
$album->delete();
|
||||
|
||||
return $this->success('ok');
|
||||
}
|
||||
}
|
||||
@@ -2,143 +2,249 @@
|
||||
namespace app\api\controller;
|
||||
|
||||
use app\model\Archives as ArchivesModel;
|
||||
use app\model\Content;
|
||||
use support\Request;
|
||||
use taoser\facade\Validate;
|
||||
use hg\apidoc\annotation as Apidoc;
|
||||
use support\Jwt\JwtToken;
|
||||
use support\think\Db;
|
||||
|
||||
/**
|
||||
* 文章模块
|
||||
*/
|
||||
class ArticleController extends BaseController{
|
||||
class ArticleController extends BaseController
|
||||
{
|
||||
public $noNeedLogin = ['*'];
|
||||
|
||||
private const CACHE_PREFIX_READ = 'article_read_';
|
||||
private const CACHE_TTL = 86400;
|
||||
|
||||
/**
|
||||
* 列表
|
||||
* @Apidoc\Query("category_id", type="int", require=true, desc="分类ID",default=10)
|
||||
* @Apidoc\Query("page", type="int", require=true, desc="页码",default=1)
|
||||
* @Apidoc\Query("limit", type="int", require=true, desc="分页大小",default=10)
|
||||
* @Apidoc\Method("GET")
|
||||
* @Apidoc\Query("category_id", type="int", require=true, desc="分类ID", default=10)
|
||||
* @Apidoc\Query("page", type="int", require=true, desc="页码", default=1)
|
||||
* @Apidoc\Query("limit", type="int", require=true, desc="分页大小", default=10)
|
||||
*/
|
||||
public function list(){
|
||||
$limit = (int)input('limit',10);
|
||||
$category_id = (int)input('category_id',0);
|
||||
public function list()
|
||||
{
|
||||
$limit = (int)input('limit', 10);
|
||||
$category_id = (int)input('category_id', 0);
|
||||
|
||||
$model = ArchivesModel::where('status','1')->where('type','article');
|
||||
if($category_id){
|
||||
$model = $model->where('category_id',$category_id);
|
||||
$model = ArchivesModel::where('status', 'normal')->where('type', 'article');
|
||||
if ($category_id) {
|
||||
$model = $model->where('category_id', $category_id);
|
||||
}
|
||||
$list = $model->order('id','desc')->paginate($limit);
|
||||
$user_id=0;
|
||||
try {
|
||||
$user_id = \support\Jwt\JwtToken::getCurrentId();
|
||||
} catch (\Throwable $th) {
|
||||
}
|
||||
$list->each(function($item)use($user_id){
|
||||
if(!$user_id){
|
||||
$item->is_read = 0;
|
||||
}
|
||||
$item->is_read = cache('article_read_'.$item->id.'_'.$user_id)?:0;
|
||||
|
||||
$list = $model->order('id', 'desc')->paginate($limit);
|
||||
|
||||
$user_id = $this->getCurrentUserId();
|
||||
$list->each(function ($item) use ($user_id) {
|
||||
$item->is_read = $user_id ? $this->getReadStatus($item->id, $user_id) : 0;
|
||||
return $item;
|
||||
});
|
||||
return $this->success(__('successful'),$list->toArray());
|
||||
|
||||
|
||||
return $this->success(__('successful'), $list->toArray());
|
||||
}
|
||||
|
||||
/**
|
||||
* faq
|
||||
* @Apidoc\Query("page", type="int", require=true, desc="页码",default=1)
|
||||
* @Apidoc\Query("limit", type="int", require=true, desc="分页大小",default=10)
|
||||
* @Apidoc\method("GET")
|
||||
* @Apidoc\Query("page", type="int", require=true, desc="页码", default=1)
|
||||
* @Apidoc\Query("limit", type="int", require=true, desc="分页大小", default=10)
|
||||
*/
|
||||
public function faq(){
|
||||
$limit = (int)input('limit',10);
|
||||
$model = ArchivesModel::alias('a')
|
||||
public function faq()
|
||||
{
|
||||
$limit = (int)input('limit', 10);
|
||||
$list = ArchivesModel::alias('a')
|
||||
->join('content c', 'a.id = c.id')
|
||||
->where('a.status','1')
|
||||
->where('a.type','article')
|
||||
->where('a.category_id',9);
|
||||
$list = $model->Field('a.title,a.id,c.content')->order('a.id','desc')->paginate($limit);
|
||||
return $this->success(__('successful'),$list->toArray());
|
||||
|
||||
->where('a.status', 'normal')
|
||||
->where('a.type', 'article')
|
||||
->where('a.category_id', 9)
|
||||
->field('a.title, a.id, c.content')
|
||||
->order('a.id', 'desc')
|
||||
->paginate($limit);
|
||||
|
||||
return $this->success(__('successful'), $list->toArray());
|
||||
}
|
||||
|
||||
/**
|
||||
* 详情
|
||||
* @Apidoc\Method("GET")
|
||||
* @Apidoc\Query("id", type="int", require=true, desc="ID")
|
||||
*/
|
||||
public function detail(){
|
||||
$appid = input('id');
|
||||
/** @var ArchivesModel $vo */
|
||||
$vo = ArchivesModel::where('id',$appid)->find();
|
||||
if($vo) {
|
||||
$addon = \app\model\Content::where('id', $vo->id)->find()->toArray();
|
||||
if ($addon) {
|
||||
$vo->setAddonData($addon);
|
||||
}
|
||||
$user_id=0;
|
||||
try {
|
||||
$user_id = \support\Jwt\JwtToken::getCurrentId();
|
||||
} catch (\Throwable $th) {
|
||||
}
|
||||
if($user_id){
|
||||
cache('article_read_'.$vo->id.'_'.$user_id,1);
|
||||
}
|
||||
return $this->success(__('successful'),$vo->toArray());
|
||||
}else{
|
||||
public function detail()
|
||||
{
|
||||
$id = (int)input('id');
|
||||
if (!$id) {
|
||||
return $this->error(__("Invalid parameters"));
|
||||
}
|
||||
|
||||
$vo = ArchivesModel::where('id', $id)->find();
|
||||
if (!$vo) {
|
||||
return $this->error(__("Article does not exist"));
|
||||
}
|
||||
}
|
||||
|
||||
$this->appendContent($vo);
|
||||
|
||||
$user_id = $this->getCurrentUserId();
|
||||
if ($user_id) {
|
||||
$this->markAsRead($vo->id, $user_id);
|
||||
}
|
||||
|
||||
return $this->success(__('successful'), $vo->toArray());
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取最新公告
|
||||
* @Apidoc\Method("GET")
|
||||
*/
|
||||
public function last_notie()
|
||||
{
|
||||
$vo = ArchivesModel::where('type', 'article')
|
||||
->where('status', 'normal')
|
||||
->order('id', 'desc')
|
||||
->find();
|
||||
|
||||
if (!$vo) {
|
||||
return $this->success(__("successful"), []);
|
||||
}
|
||||
|
||||
$this->appendContent($vo);
|
||||
|
||||
$user_id = $this->getCurrentUserId();
|
||||
if ($user_id) {
|
||||
$this->markAsRead($vo->id, $user_id);
|
||||
}
|
||||
|
||||
return $this->success(__('successful'), $vo->toArray());
|
||||
}
|
||||
|
||||
/**
|
||||
* 单页详情
|
||||
* @Apidoc\Method("GET")
|
||||
* @Apidoc\Query("id", type="int", require=true, desc="ID")
|
||||
* @Apidoc\Query("name", type="string", require=true, desc="二选1")
|
||||
*/
|
||||
public function singpage(){
|
||||
$appid = input('id');
|
||||
public function singpage()
|
||||
{
|
||||
$id = (int)input('id');
|
||||
$name = input('name');
|
||||
/** @var ArchivesModel $vo */
|
||||
if($name){
|
||||
$vo = ArchivesModel::where('name',$name)->find();
|
||||
}else{
|
||||
if($appid){
|
||||
$vo = ArchivesModel::where('id',$appid)->find();
|
||||
}
|
||||
|
||||
$vo = null;
|
||||
if ($name) {
|
||||
$vo = ArchivesModel::where('name', $name)->find();
|
||||
} elseif ($id) {
|
||||
$vo = ArchivesModel::where('id', $id)->find();
|
||||
}
|
||||
if($vo) {
|
||||
$addon = \app\model\Content::where('id', $vo->id)->find()->toArray();
|
||||
if ($addon) {
|
||||
$vo->setAddonData($addon);
|
||||
}
|
||||
return $this->success(__('successful'),$vo->toArray());
|
||||
}else{
|
||||
|
||||
if (!$vo) {
|
||||
return $this->error(__("Article does not exist"));
|
||||
}
|
||||
|
||||
$this->appendContent($vo);
|
||||
|
||||
return $this->success(__('successful'), $vo->toArray());
|
||||
}
|
||||
|
||||
/**
|
||||
* 幻灯片
|
||||
* @Apidoc\Query("id", type="int", require=true, desc="ID")
|
||||
*/
|
||||
public function slide(){
|
||||
public function slide()
|
||||
{
|
||||
$list = [
|
||||
['image'=>domain().'/storage/slide/1.jpg','title'=>''],
|
||||
['image'=>domain().'/storage/slide/2.webp','title'=>''],
|
||||
['image'=>domain().'/storage/slide/3.webp','title'=>''],
|
||||
['image'=>domain().'/storage/slide/4.jpg','title'=>''],
|
||||
['image' => domain() . '/storage/slide/1.jpg', 'title' => ''],
|
||||
['image' => domain() . '/storage/slide/2.webp', 'title' => ''],
|
||||
['image' => domain() . '/storage/slide/3.webp', 'title' => ''],
|
||||
['image' => domain() . '/storage/slide/4.jpg', 'title' => ''],
|
||||
];
|
||||
return $this->success(__('successful'),$list);
|
||||
return $this->success(__('successful'), $list);
|
||||
}
|
||||
|
||||
/**
|
||||
* 设为已读
|
||||
* @Apidoc\Query("id", type="int", require=true, desc="ID,多个逗号隔开")
|
||||
*/
|
||||
function mask_as_read(){
|
||||
public function mask_as_read()
|
||||
{
|
||||
$ids = input('id');
|
||||
$user_id = \support\Jwt\JwtToken::getCurrentId();
|
||||
if(!$user_id){
|
||||
$user_id = $this->getCurrentUserId();
|
||||
|
||||
if (!$user_id) {
|
||||
return $this->success(__('successful'));
|
||||
}
|
||||
$ids = explode(',',$ids);
|
||||
|
||||
$ids = array_filter(explode(',', $ids));
|
||||
foreach ($ids as $id) {
|
||||
$key = 'article_read_'.$id.'_'.$user_id;
|
||||
cache($key,1);
|
||||
$this->markAsRead((int)$id, $user_id);
|
||||
}
|
||||
|
||||
return $this->success(__('successful'));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前用户ID
|
||||
*/
|
||||
protected function getCurrentUserId(): int
|
||||
{
|
||||
try {
|
||||
return (int)JwtToken::getCurrentId();
|
||||
} catch (\Throwable $e) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取阅读状态
|
||||
*/
|
||||
protected function getReadStatus(int $articleId, int $userId): int
|
||||
{
|
||||
return (int)(cache_get($this->getCacheKey($articleId, $userId)) ?: 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* 标记为已读
|
||||
*/
|
||||
protected function markAsRead(int $articleId, int $userId = null): void
|
||||
{
|
||||
if (!$articleId) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!$userId) {
|
||||
$userId = $this->getCurrentUserId();
|
||||
}
|
||||
|
||||
if (!$userId) {
|
||||
return;
|
||||
}
|
||||
|
||||
$cacheKey = $this->getCacheKey($articleId, $userId);
|
||||
if (!cache_get($cacheKey)) {
|
||||
Db::name('archives_read')->insert([
|
||||
'user_id' => $userId,
|
||||
'source_id' => $articleId,
|
||||
'value' => 1
|
||||
]);
|
||||
cache($cacheKey, 1, self::CACHE_TTL);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 追加内容到文章
|
||||
*/
|
||||
protected function appendContent(ArchivesModel $article): void
|
||||
{
|
||||
$content = Content::where('id', $article->id)->find();
|
||||
if ($content) {
|
||||
$article->setAddonData($content->toArray());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成缓存键
|
||||
*/
|
||||
protected function getCacheKey(int $articleId, int $userId): string
|
||||
{
|
||||
return self::CACHE_PREFIX_READ . $articleId . '_' . $userId;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -39,10 +39,13 @@ class BalanceLogController extends BaseController{
|
||||
$list->each(function($item)use($BalanceTypeList){
|
||||
if($item->type == \app\enum\BalanceType::TRANSFER->value && $item->memo){
|
||||
$item['target'] = UserModel::where('id',$item->memo)->value('username');
|
||||
$item->memo = idEncode($item->memo);
|
||||
$item->memo = \support\Encrypt::userIDencode($item->memo);
|
||||
}
|
||||
$item->_type= $item->type;
|
||||
$item->type= $BalanceTypeList[$item->type];
|
||||
if(ctype_digit($item->created_at)){
|
||||
$item->created_at = datetime($item->created_at);
|
||||
}
|
||||
return $item;
|
||||
});
|
||||
return $this->success(__('successful'),$list);
|
||||
|
||||
@@ -5,7 +5,7 @@ use support\Response;
|
||||
use Shopwwi\WebmanFilesystem\FilesystemFactory;
|
||||
use Shopwwi\WebmanFilesystem\Facade\Storage;
|
||||
use hg\apidoc\annotation as Apidoc;
|
||||
use taoser\facade\Validate;
|
||||
use Tinywan\Validate\Facade\Validate;
|
||||
/**
|
||||
* 基础控制器
|
||||
* @Apidoc\NotParse()
|
||||
@@ -40,7 +40,7 @@ class BaseController
|
||||
*/
|
||||
protected function json(int $code, string $msg = 'ok', array|object|null $data = []): Response
|
||||
{
|
||||
return json(['code' => $code, 'data' => $data, 'msg' => $msg]);
|
||||
return json(['code' => $code, 'data' => $data, 'msg' => __($msg)]);
|
||||
}
|
||||
|
||||
protected function success(string $msg = '成功', array|object|null $data = []): Response
|
||||
@@ -107,6 +107,8 @@ class BaseController
|
||||
/**
|
||||
* @Apidoc\Title("上传")
|
||||
* @Apidoc\Method("POST")
|
||||
* @Apidoc\NotParse()
|
||||
* @Apidoc\NotDebug()
|
||||
*/
|
||||
function upload(Request $request,$return = false)
|
||||
{
|
||||
|
||||
@@ -5,7 +5,6 @@ use app\model\Card as CardModel;
|
||||
use app\model\Cdkey as CdkeyModel;
|
||||
use app\model\User as UserModel;
|
||||
use support\think\Db;
|
||||
use taoser\facade\Validate;
|
||||
use hg\apidoc\annotation as Apidoc;
|
||||
|
||||
/**
|
||||
|
||||
@@ -0,0 +1,106 @@
|
||||
<?php
|
||||
namespace app\api\controller;
|
||||
use app\model\User as UserModel;
|
||||
use app\model\Collection as CollectionModel;
|
||||
use support\Request;
|
||||
use support\Response;
|
||||
use support\think\Db;
|
||||
use hg\apidoc\annotation as Apidoc;
|
||||
|
||||
/**
|
||||
* 收藏
|
||||
*/
|
||||
class CollectionController extends BaseController{
|
||||
/**
|
||||
* 不需要鉴权的方法
|
||||
* @var array
|
||||
*/
|
||||
public $noNeedAuth = ['*'];
|
||||
|
||||
/**
|
||||
* 无需登录及鉴权的方法
|
||||
* @var array
|
||||
*/
|
||||
public $noNeedLogin = [];
|
||||
/**
|
||||
* @Apidoc\Title("列表")
|
||||
* @Apidoc\Method("GET")
|
||||
* @Apidoc\Query("content_type", type="string", require=false, desc="内容类型 enum('text', 'image', 'file', 'video', 'link','audio')")
|
||||
* @Apidoc\Query("kw", type="string", require=false, desc="关键字")
|
||||
* @Apidoc\Query("page", type="int", require=true, desc="页码",default=1)
|
||||
* @Apidoc\Query("limit", type="int", require=true, desc="分页大小",default=10)
|
||||
*/
|
||||
function list(Request $request): Response
|
||||
{
|
||||
$user = \support\Jwt::getUser();
|
||||
$page = (int)Input('page', 1);
|
||||
$content_type = Input('content_type');
|
||||
$kw = Input('kw');
|
||||
$limit = (int)Input('limit', 10);
|
||||
$query = CollectionModel::where('status', 1)
|
||||
->whereIn('user_id',$user->id)
|
||||
->order('created_at', 'desc');
|
||||
if($content_type){
|
||||
$query->where('content_type',$content_type);
|
||||
}
|
||||
if($kw){
|
||||
$query->whereLike('content','%'.$kw.'%');
|
||||
}
|
||||
|
||||
$list = $query->paginate([
|
||||
'list_rows' => $limit,
|
||||
'page' => $page,
|
||||
]);
|
||||
return $this->success('ok', $list);
|
||||
}
|
||||
/**
|
||||
* 创建收藏
|
||||
* @Apidoc\Param("content_type", type="string",require=true, desc="内容类型 enum('text', 'image', 'file', 'video', 'link','audio')")
|
||||
* @Apidoc\Param("content", type="string",require=true, desc="json结构化收藏内容本体")
|
||||
* @Apidoc\Param("tags", type="string",require=true, desc="用户自定义标签,多个用逗号隔开,或者使用数组")
|
||||
* @Apidoc\Param("is_pinned", type="int",require=true, desc="是否置顶")
|
||||
* @param Request $request
|
||||
* @return Response
|
||||
*/
|
||||
function create(Request $request): Response
|
||||
{
|
||||
$user = \support\Jwt::getUser();
|
||||
$content = $request->post('content');
|
||||
$content_type = $request->post('content_type', '');
|
||||
$tags = $request->post('tags', '');
|
||||
$is_pinned = $request->post('is_pinned', 0);
|
||||
// 验证内容
|
||||
if (empty($content_type)) {
|
||||
return $this->fail(__('The field %field% must be not empty. ',['field'=>'content_type']));
|
||||
}
|
||||
if (empty($content)) {
|
||||
return $this->fail(__('The field %field% must be not empty. ',['field'=>'content']));
|
||||
}
|
||||
if(is_array($content)) {
|
||||
$content = json_encode($content);
|
||||
}
|
||||
// 创建朋友圈动态
|
||||
$collection = CollectionModel::create([
|
||||
'user_id' => $user->id,
|
||||
'content_type' => $content_type,
|
||||
'content' => $content,
|
||||
'tags' => $tags,
|
||||
'is_pinned' => $is_pinned
|
||||
]);
|
||||
|
||||
return $this->success('发布成功', ['collection' => $collection]);
|
||||
}
|
||||
/**
|
||||
* 删除收藏
|
||||
* @Apidoc\Param("id", type="int",require=true, desc="收藏id")
|
||||
* @param Request $request
|
||||
* @return Response
|
||||
*/
|
||||
function delete(Request $request): Response
|
||||
{
|
||||
$user = \support\Jwt::getUser();
|
||||
$id = $request->post('id');
|
||||
CollectionModel::where('id',$id)->where('user_id',$user->id)->delete();
|
||||
return $this->success('删除成功');
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
<?php
|
||||
namespace app\api\controller;
|
||||
|
||||
use taoser\facade\Validate;
|
||||
use Tinywan\Validate\Facade\Validate;
|
||||
use app\model\User as UserModel;
|
||||
|
||||
use support\Request;
|
||||
@@ -29,7 +29,6 @@ class CommonController extends BaseController{
|
||||
*/
|
||||
public $noNeedLogin = ['*'];
|
||||
|
||||
|
||||
/**
|
||||
* 加载初始化
|
||||
*
|
||||
@@ -37,7 +36,7 @@ class CommonController extends BaseController{
|
||||
*/
|
||||
public function init(Request $request)
|
||||
{
|
||||
$lang = input('lang','en-US');
|
||||
$lang = $request->header('lang','zh-Hans');
|
||||
locale( $lang);
|
||||
$config = Config('site');
|
||||
$disallowFields = [
|
||||
@@ -57,6 +56,33 @@ class CommonController extends BaseController{
|
||||
$config['recharge_status_list'] = \app\enum\RechargeStatus::toArray();
|
||||
$config['withdrawl_status_list'] = \app\enum\WithdrawlStatus::toArray();
|
||||
$config['server_status_list'] = \app\enum\ServerStatus::toArray();
|
||||
$config['see_point_awards'] = [
|
||||
[
|
||||
'name'=>'S1',
|
||||
'award'=>0.05,
|
||||
'total'=>50,
|
||||
],
|
||||
[
|
||||
'name'=>'S2',
|
||||
'award'=>0.1,
|
||||
'total'=>100,
|
||||
],
|
||||
[
|
||||
'name'=>'S3',
|
||||
'award'=>0.15,
|
||||
'total'=>1000,
|
||||
],
|
||||
[
|
||||
'name'=>'S4',
|
||||
'award'=>0.2,
|
||||
'total'=>5000,
|
||||
],
|
||||
[
|
||||
'name'=>'S5',
|
||||
'award'=>0.25,
|
||||
'total'=>20000,
|
||||
]
|
||||
];
|
||||
//$config['getFriendList'] = $request->IM->friend->getFriendList('100006');
|
||||
return $this->success(__('successful'), $config);
|
||||
}
|
||||
@@ -98,7 +124,9 @@ class CommonController extends BaseController{
|
||||
* 注册会员
|
||||
*
|
||||
* @Apidoc\Method ("POST")
|
||||
* @Apidoc\Param("type", type="string",require=true, desc="注册方式:email,mobile")
|
||||
* @Apidoc\Param("email", type="string",require=true, desc="邮箱")
|
||||
* @Apidoc\Param("mobile", type="string",require=true, desc="手机号码")
|
||||
* @Apidoc\Param("password", type="string",require=true, desc="密码")
|
||||
* @Apidoc\Param("trade_password", type="string",require=true, desc="交易密码")
|
||||
* @Apidoc\Param("invite_code", type="string",require=true, desc="推荐码")
|
||||
@@ -122,7 +150,7 @@ class CommonController extends BaseController{
|
||||
}
|
||||
$username = $email;
|
||||
unset($mobile);
|
||||
//captcha_verfiy('email','register',$email,false);
|
||||
captcha_verify('email','register',$email,false);
|
||||
}
|
||||
if ($type == 'mobile') {
|
||||
if(!$mobile || !Validate::regex($mobile, "^1\d{10}$")){
|
||||
@@ -130,10 +158,10 @@ class CommonController extends BaseController{
|
||||
}
|
||||
$username = $mobile;
|
||||
unset($email);
|
||||
//captcha_verfiy('mobile','register',$mobile,false);
|
||||
captcha_verify('mobile','register',$mobile,false);
|
||||
}
|
||||
if ($type == 'username') {
|
||||
if(!$email || !Validate::is($email, "email")){
|
||||
if(!$username){
|
||||
return $this->error(__('Username is incorrect'));
|
||||
}
|
||||
}
|
||||
@@ -148,13 +176,24 @@ class CommonController extends BaseController{
|
||||
|
||||
//邀请码
|
||||
//$invite_code = 'TEAJXLEE';
|
||||
$region = Input('region','+86');
|
||||
$region = str_replace('+','',$region);
|
||||
$extends = [
|
||||
'role_id' => 1,
|
||||
'group_id' => 0,
|
||||
'region' => '86',
|
||||
'region' => $region,
|
||||
'nickname' => input('nickname'),
|
||||
'avatar' => '/static/avatar/'.rand(0,17).'.png',
|
||||
];
|
||||
if(empty($extends['nickname'])){
|
||||
if($type == 'mobile'){
|
||||
$extends['nickname'] = '用户_'.substr($username,7);
|
||||
}else if($type == 'email'){
|
||||
$extends['nickname'] = '用户_'.substr(explode('@',$username)[0],7);
|
||||
}else{
|
||||
$extends['nickname'] = $username;
|
||||
}
|
||||
}
|
||||
if ($invite_code) {
|
||||
if(strlen($invite_code) == 12){
|
||||
//系统生产的一次性推荐吗
|
||||
@@ -189,11 +228,11 @@ class CommonController extends BaseController{
|
||||
}
|
||||
$data = ['userinfo' => $user];
|
||||
// if ($type == 'email') {
|
||||
// captcha_verfiy('email','register',$email,true);
|
||||
// captcha_verify('email','register',$email,true);
|
||||
// }else if ($type == 'mobile') {
|
||||
// captcha_verfiy('mobile','register',$mobile,true);
|
||||
// captcha_verify('mobile','register',$mobile,true);
|
||||
// }else{
|
||||
// captcha_verfiy('image','register',$mobile,true);
|
||||
// captcha_verify('image','register',$mobile,true);
|
||||
// }
|
||||
return $this->success(__('Sign up successful'), $data);
|
||||
} catch (\Exception $e) {
|
||||
@@ -204,10 +243,12 @@ class CommonController extends BaseController{
|
||||
* 登录
|
||||
* @Apidoc\Method("POST")
|
||||
* @Apidoc\Param("username", type="string",require=false, desc="用户名登录必填")
|
||||
* @Apidoc\Param("email", type="string",require=false, desc="邮箱登录必填")
|
||||
* @Apidoc\Param("mobile", type="string",require=false, desc="手机号登录必填")
|
||||
* @Apidoc\Param("type", type="string",require=true,default="mobile",desc="登录方式,username,mobile,email")
|
||||
* @Apidoc\Param("password", type="string",require=false, desc="密码的登录必填")
|
||||
* @Apidoc\Param("code", type="string",require=false, desc="验证码登录必填")
|
||||
* @Apidoc\Param("platform", type="string",require=false, desc="平台",default="web")
|
||||
* @Apidoc\Param("region", type="string",require=false,default="86", desc="区域,手机号登录必填")
|
||||
*/
|
||||
public function login(Request $request){
|
||||
@@ -284,20 +325,19 @@ class CommonController extends BaseController{
|
||||
try{
|
||||
$user = \support\Jwt::getUser();
|
||||
}catch(\Exception $e){
|
||||
//log_alert($e->getMessage());
|
||||
$user = false;
|
||||
}
|
||||
if($user){
|
||||
captcha_verfiy('mobile','reset_pwd',$user->mobile);
|
||||
captcha_verify('mobile','reset_pwd',$user->mobile);
|
||||
}
|
||||
}else{
|
||||
if ($mobile && Validate::regex($mobile, "^1\d{10}$")) {
|
||||
captcha_verfiy('mobile','reset_pwd',$mobile);
|
||||
$region = Input('region');
|
||||
captcha_verify('mobile','reset_pwd',$mobile);
|
||||
$region = Input('region','+86');
|
||||
$region = str_replace('+','',$region);
|
||||
$user = UserModel::where('region',$region)->where('mobile',$mobile)->find();
|
||||
}else if ($email && Validate::is($email, "email")) {
|
||||
captcha_verfiy('email','reset_pwd',$email);
|
||||
captcha_verify('email','reset_pwd',$email);
|
||||
$user = UserModel::getByEmail($email);
|
||||
}
|
||||
}
|
||||
@@ -323,11 +363,13 @@ class CommonController extends BaseController{
|
||||
* @Apidoc\Param("email", type="string",require=true, desc="邮箱")
|
||||
* @Apidoc\Param("newpassword", type="string",require=true, desc="新密码")
|
||||
* @Apidoc\Param("code", type="string",require=true, desc="邮箱验证码,event=reset_trade_pwd")
|
||||
* @Apidoc\Param("verify_type", type="string",require=true, desc="验证方式,email,mobile")
|
||||
*/
|
||||
public function reset_trade_pwd()
|
||||
{
|
||||
$email = input("email");
|
||||
$mobile = input("mobile");
|
||||
$verify_type = input("verify_type");
|
||||
$newpassword = input("newpassword");
|
||||
if (!$newpassword) {
|
||||
return $this->error(__('Invalid parameters'));
|
||||
@@ -336,23 +378,28 @@ class CommonController extends BaseController{
|
||||
if (!Validate::check(['newpassword' => $newpassword], ['newpassword' => 'require|regex:\S{6,32}'])) {
|
||||
return $this->error(__('Trade password must be 6-32 characters'));
|
||||
}
|
||||
|
||||
if (!$mobile && !$email){
|
||||
try{
|
||||
$user = \support\Jwt::getUser();
|
||||
}catch(\Exception $e){
|
||||
log_alert($e->getMessage());
|
||||
$user = false;
|
||||
}
|
||||
if($user){
|
||||
captcha_verfiy('mobile','reset_trade_pwd',$user->mobile);
|
||||
|
||||
if($verify_type == 'email'){
|
||||
captcha_verify('email','reset_trade_pwd',$user->email);
|
||||
}else if($verify_type == 'mobile'){
|
||||
captcha_verify('mobile','reset_trade_pwd',$user->mobile);
|
||||
}else{
|
||||
return $this->error(__('Unknown verify type'));
|
||||
}
|
||||
}
|
||||
}else{
|
||||
if ($mobile && Validate::regex($mobile, "^1\d{10}$")) {
|
||||
captcha_verfiy('mobile','reset_trade_pwd',$mobile);
|
||||
captcha_verify('mobile','reset_trade_pwd',$mobile);
|
||||
$user = UserModel::getByMobile($mobile);
|
||||
}elseif ($email && Validate::is($email, "email")) {
|
||||
captcha_verfiy('email','reset_trade_pwd',$email);
|
||||
captcha_verify('email','reset_trade_pwd',$email);
|
||||
$user = UserModel::getByEmail($email);
|
||||
}
|
||||
}
|
||||
@@ -362,7 +409,6 @@ class CommonController extends BaseController{
|
||||
//模拟一次登录,需不需要充值登录信息?????
|
||||
//\support\Jwt::direct($user->id);
|
||||
try{
|
||||
log_alert($user->id.' 重置交易密码'.$newpassword);
|
||||
UserModel::where('id',$user->id)->save([
|
||||
'trade_password' => \plugin\admin\app\common\Util::passwordHash($newpassword)
|
||||
]);
|
||||
@@ -390,7 +436,7 @@ class CommonController extends BaseController{
|
||||
$user = \support\Jwt::getUser();
|
||||
$email = $user->email;
|
||||
} catch (\Exception $th) {
|
||||
return $this->error(__('Incoret param'));
|
||||
return $this->error(__('Invalid parameter'));
|
||||
}
|
||||
}
|
||||
$key = 'captcha_'.$event.'_'.$email;
|
||||
@@ -406,12 +452,13 @@ class CommonController extends BaseController{
|
||||
$list[$code] = time();
|
||||
cache($key,$list);
|
||||
cache('exp_'.$key,time());
|
||||
// addJob([
|
||||
// 'email' => $email,
|
||||
// 'title' => __("Mt email code"),
|
||||
// 'event' => $event,
|
||||
// 'code' => $code
|
||||
// ],'Email');
|
||||
addJob([
|
||||
'email' => $email,
|
||||
'title' => __(Config('site.name').' 验证码'),
|
||||
'event' => $event,
|
||||
'code' => $code
|
||||
],'Email');
|
||||
\support\Log::channel('mail')->alert("邮件验证码:".$code.',邮箱:'.$email);
|
||||
return $this->success(__('Email sent successfully'),[
|
||||
'code'=> $debug ? $code : ''
|
||||
]);
|
||||
@@ -422,7 +469,7 @@ class CommonController extends BaseController{
|
||||
$user = \support\Jwt::getUser();
|
||||
$mobile = $user->mobile;
|
||||
} catch (\Exception $th) {
|
||||
return $this->error(__('Incoret param'));
|
||||
return $this->error(__('Invalid parameter'));
|
||||
}
|
||||
}
|
||||
if (!Validate::regex($mobile, "^1\d{10}$")) {
|
||||
@@ -447,6 +494,7 @@ class CommonController extends BaseController{
|
||||
'event' => $event,
|
||||
'code' => $code
|
||||
],'Sms');
|
||||
\support\Log::channel('mail')->alert("短信验证码:".$code.',手机号:'.$mobile);
|
||||
return $this->success(__('SMS sent successfully'),[
|
||||
'code'=> $debug ? $code : ''
|
||||
]);
|
||||
@@ -488,11 +536,11 @@ class CommonController extends BaseController{
|
||||
$event = $request->post('event');
|
||||
try {
|
||||
if($type == 'email'){
|
||||
$result = captcha_verfiy('email', $event , $email,false);
|
||||
$result = captcha_verify('email', $event , $email,false);
|
||||
}elseif($type == 'mobile'){
|
||||
$result = captcha_verfiy('mobile', $event , $mobile,false);
|
||||
$result = captcha_verify('mobile', $event , $mobile,false);
|
||||
}else{
|
||||
$result = captcha_verfiy('image', $event , '',false);
|
||||
$result = captcha_verify('image', $event , '',false);
|
||||
}
|
||||
if(!$result){
|
||||
return $this->fail(__('Captcha is incorrect'));
|
||||
|
||||
@@ -5,7 +5,6 @@ use app\model\UserRemark as UserRemarkModel;
|
||||
use app\model\GroupRemark as GroupRemarkModel;
|
||||
use support\Request;
|
||||
use support\Response;
|
||||
use taoser\facade\Validate;
|
||||
use support\think\Db;
|
||||
use hg\apidoc\annotation as Apidoc;
|
||||
/**
|
||||
@@ -31,7 +30,7 @@ class ContactController extends BaseController{
|
||||
{
|
||||
$current_user = \support\Jwt::getUser();
|
||||
$user_id = $current_user->id;
|
||||
$userID = idEncode($user_id);
|
||||
$userID = \support\Encrypt::userIDencode($user_id);
|
||||
$res = $request->IM->friend()->getFriendList($userID,1,10000);
|
||||
return $this->success('ok',$res['data']['friendsInfo']);
|
||||
}
|
||||
@@ -46,8 +45,7 @@ class ContactController extends BaseController{
|
||||
if(!$userID){
|
||||
return $this->error('UserID is Empty');
|
||||
}
|
||||
$userID = idDecode($userID);
|
||||
$res = \app\model\User::where('id',$userID)->find();
|
||||
$res = \app\model\User::where('userID',$userID)->find();
|
||||
return $this->success('ok',$res);
|
||||
}
|
||||
/**
|
||||
@@ -61,11 +59,6 @@ class ContactController extends BaseController{
|
||||
if(!$userIDs){
|
||||
return $this->error('UserID is Empty');
|
||||
}
|
||||
//$userIDs = explode(',',$userIDs);
|
||||
//$userIDs = idDecode($userIDs);
|
||||
//$current_user = \support\Jwt::getUser();
|
||||
//$user_id = $current_user->id;
|
||||
//$userID = idEncode($user_id);
|
||||
$res = \app\model\User::whereIn('userID',$userIDs)->select();
|
||||
return $this->success('ok',$res);
|
||||
}
|
||||
|
||||
@@ -10,7 +10,6 @@ use app\model\FriendCircleLike as FriendCircleLikeModel;
|
||||
use app\model\FriendCircleComment as FriendCircleCommentModel;
|
||||
use support\Request;
|
||||
use support\Response;
|
||||
use taoser\facade\Validate;
|
||||
use support\think\Db;
|
||||
use hg\apidoc\annotation as Apidoc;
|
||||
|
||||
@@ -36,12 +35,12 @@ class FriendCircleController extends BaseController{
|
||||
function info(Request $request): Response{
|
||||
$user_id = Input('user_id');
|
||||
if($user_id){
|
||||
$user_id = idDecode($user_id);
|
||||
$user_id = \support\Encrypt::userIDDecode($user_id);
|
||||
$json= [
|
||||
'top_unread_items' =>[],
|
||||
'unread_item_ids' =>[],
|
||||
'unread_count' =>0,
|
||||
'settings' => Db::name('friend_circle_setting')->where('user_id',$user_id)->order('id','desc')->findOrEmpty()
|
||||
'settings' => Db::name('user_extend')->where('user_id',$user_id)->findOrEmpty()
|
||||
];
|
||||
return $this->success('ok',$json);
|
||||
}else{
|
||||
@@ -53,7 +52,7 @@ class FriendCircleController extends BaseController{
|
||||
$res = $this->newcount($request);
|
||||
$response = $res->rawBody();
|
||||
$json = json_decode($response,true);
|
||||
$json['data']['settings'] = Db::name('friend_circle_setting')->where('user_id',$user_id)->order('id','desc')->findOrEmpty();
|
||||
$json['data']['settings'] = Db::name('user_extend')->where('user_id',$user_id)->findOrEmpty();
|
||||
// [
|
||||
// 'bg' => '',
|
||||
// ];
|
||||
@@ -84,7 +83,7 @@ class FriendCircleController extends BaseController{
|
||||
$limit = (int)Input('limit', 10);
|
||||
$user_id = Input('user_id', 0);
|
||||
if($user_id){
|
||||
$user_id = idDecode($user_id);
|
||||
$user_id = \support\Encrypt::userIDDecode($user_id);
|
||||
}
|
||||
|
||||
$query = FriendCircleModel::where('status', 1)
|
||||
@@ -393,10 +392,10 @@ class FriendCircleController extends BaseController{
|
||||
$cache_key = 'friend_id_list_'.$user_id;
|
||||
$result = cache($cache_key) ?: [];
|
||||
if(count($result) === 0){
|
||||
$res = request()->IM->friend->getFriendList(idEncode($user_id));
|
||||
$res = request()->IM->friend->getFriendList(\support\Encrypt::userIDencode($user_id));
|
||||
$friendsInfo = $res['friendsInfo'];
|
||||
foreach($friendsInfo as $k=>$v){
|
||||
array_push($result,$v['friendUser']['userID']);
|
||||
array_push($result,\support\Encrypt::userIDDecode($v['friendUser']['userID']));
|
||||
}
|
||||
cache($cache_key,$result,3600);
|
||||
}
|
||||
@@ -426,11 +425,8 @@ class FriendCircleController extends BaseController{
|
||||
return $this->fail( $res);
|
||||
}
|
||||
|
||||
Db::name('friend_circle_setting')->replace()->insert([
|
||||
'user_id' => $user->id,
|
||||
'bg' => $res[0]['file_name'],
|
||||
'allow_days'=>0,
|
||||
'created_at'=>0
|
||||
Db::name('user_extend')->where('user_id',$user->id)->save([
|
||||
'moments_banner' => $res[0]['file_name']
|
||||
]);
|
||||
//$result->ss = cdnurl($result->url);
|
||||
//P($result);
|
||||
|
||||
@@ -5,7 +5,6 @@ use app\model\UserRemark as UserRemarkModel;
|
||||
use app\model\GroupRemark as GroupRemarkModel;
|
||||
use support\Request;
|
||||
use support\Response;
|
||||
use taoser\facade\Validate;
|
||||
use support\think\Db;
|
||||
use hg\apidoc\annotation as Apidoc;
|
||||
/**
|
||||
|
||||
@@ -0,0 +1,120 @@
|
||||
<?php
|
||||
|
||||
namespace app\api\controller;
|
||||
|
||||
use support\Request;
|
||||
use support\Response;
|
||||
use hg\apidoc\annotation as Apidoc;
|
||||
use app\model\User;
|
||||
use app\model\Gallery as GalleryModel;
|
||||
use app\model\Album as AlbumModel;
|
||||
|
||||
/**
|
||||
* 相册的相片
|
||||
*/
|
||||
class GalleryController extends BaseController
|
||||
{
|
||||
public $noNeedAuth = ['*'];
|
||||
public $noNeedLogin = [];
|
||||
/**
|
||||
* @Apidoc\Title("列表")
|
||||
* @Apidoc\Method("POST")
|
||||
* @Apidoc\Param("album_id", type="string", require=true, desc="相册ID")
|
||||
* @Apidoc\Param("offset", type="int", require=false, desc="偏移量,和页码二选一",default=0)
|
||||
* @Apidoc\Param("page", type="int", require=false, desc="页码",default=1)
|
||||
* @Apidoc\Param("limit", type="int", require=true, desc="分页大小",default=10)
|
||||
*/
|
||||
function list(Request $request): Response
|
||||
{
|
||||
$user = \support\Jwt::getUser();
|
||||
$limit = $request->post('limit',10);
|
||||
$offset = $request->post('offset',0);
|
||||
$album_id = $request->post('album_id') ?: 0;
|
||||
//$ls = $this->get_user_in_group($group_id);
|
||||
$query = GalleryModel::where('album_id',$album_id)->order('id','desc');
|
||||
if($offset){
|
||||
$list = $query->where('id','<',$offset)->limit(0,$limit);
|
||||
}else{
|
||||
$list = $query->paginate($limit);
|
||||
}
|
||||
return $this->success('ok',$list);
|
||||
}
|
||||
/**
|
||||
* @Apidoc\Title("上传")
|
||||
* @Apidoc\Method("POST")
|
||||
* @Apidoc\Param("album_id", type="string", require=true, desc="相册ID",default=0)
|
||||
* @Apidoc\Param("title", type="string", require=true, desc="标题")
|
||||
* @Apidoc\Param("url", type="string", require=true, desc="图片")
|
||||
* @Apidoc\Param("file", type="file", require=true, desc="图片,没有url得时候必传")
|
||||
*/
|
||||
function create(Request $request): Response
|
||||
{
|
||||
$user_id = \support\Jwt\JwtToken::getCurrentId();
|
||||
$res = $this->_upload($request);
|
||||
if(is_string($res)){
|
||||
return $this->fail( $res);
|
||||
}
|
||||
$album_id = $request->post('album_id') ?: 0;
|
||||
$album = AlbumModel::find($album_id);
|
||||
if(!$album){
|
||||
return $this->fail('相册不存在');
|
||||
}
|
||||
$insert_data = [];
|
||||
foreach($res as $item){
|
||||
$insert_data[] = [
|
||||
'user_id' => $user_id,
|
||||
'group_id' => $album->groupID,
|
||||
'album_id' => $album_id,
|
||||
'title' => $item['origin_name'],
|
||||
'url' => $item['file_name'],
|
||||
];
|
||||
}
|
||||
$result = GalleryModel::saveAll($insert_data);
|
||||
return $this->success('ok',$result[0]);
|
||||
}
|
||||
/**
|
||||
* @Apidoc\Title("更新")
|
||||
* @Apidoc\Method("POST")
|
||||
* @Apidoc\Param("id", type="string", require=true, desc="ID")
|
||||
* @Apidoc\Param("title", type="string", require=true, desc="标题")
|
||||
* @Apidoc\Param("url", type="string", require=true, desc="图片")
|
||||
*/
|
||||
function update(Request $request): Response
|
||||
{
|
||||
$id = $request->input('id');
|
||||
$title = $request->input('title');
|
||||
$url = $request->input('url');
|
||||
$album = GalleryModel::find($id);
|
||||
if($album){
|
||||
if($title){
|
||||
$album->title = $title;
|
||||
}
|
||||
if($url){
|
||||
$album->url = $url;
|
||||
}
|
||||
$album->save();
|
||||
}
|
||||
return $this->success('ok',$album);
|
||||
}
|
||||
/**
|
||||
* @Apidoc\Title("删除")
|
||||
* @Apidoc\Method("POST")
|
||||
* @Apidoc\Param("id", type="string", require=true, desc="ID")
|
||||
*/
|
||||
function delete(Request $request): Response
|
||||
{
|
||||
$ids = Input('ids');
|
||||
GalleryModel::whereIn('id',$ids)->delete();
|
||||
return $this->success('ok');
|
||||
}
|
||||
/**
|
||||
* 获取在群里的角色
|
||||
* @Apidoc\NotParse()
|
||||
* @Apidoc\NotDebug()
|
||||
*/
|
||||
private function get_user_in_group($groupID='',$userID='')
|
||||
{
|
||||
$list = request()->IM->group->getGroupMemberList($groupID,$userID);
|
||||
return $list;
|
||||
}
|
||||
}
|
||||
@@ -5,7 +5,6 @@ use app\model\Gift as GiftModel;
|
||||
use app\model\GiftOrder as GiftOrderModel;
|
||||
use app\model\User as UserModel;
|
||||
use support\think\Db;
|
||||
use taoser\facade\Validate;
|
||||
use hg\apidoc\annotation as Apidoc;
|
||||
|
||||
/**
|
||||
|
||||
@@ -6,7 +6,7 @@ use support\Request;
|
||||
use support\Response;
|
||||
use hg\apidoc\annotation as Apidoc;
|
||||
use app\model\User;
|
||||
use app\model\Album as AlbumModel;
|
||||
use app\model\Gallery as AlbumModel;
|
||||
|
||||
/**
|
||||
* 群组管理
|
||||
@@ -19,7 +19,7 @@ class GroupController extends BaseController
|
||||
* @Apidoc\Title("群相片列表")
|
||||
* @Apidoc\Method("POST")
|
||||
* @Apidoc\Param("group_id", type="string", require=true, desc="群ID")
|
||||
* @Apidoc\Param("page", type="int", require=true, desc="页码",default=1)
|
||||
* @Apidoc\Param("offset", type="int", require=true, desc="偏移量",default=99999999999999)
|
||||
* @Apidoc\Param("limit", type="int", require=true, desc="分页大小",default=10)
|
||||
*/
|
||||
function album_list(Request $request): Response
|
||||
@@ -29,8 +29,6 @@ class GroupController extends BaseController
|
||||
$offset = $request->post('offset',0);
|
||||
$group_id = $request->post('groupID') ?:$request->post('group_id');
|
||||
//$ls = $this->get_user_in_group($group_id);
|
||||
//log_alert($ls);
|
||||
log_alert([$offset,$group_id,$limit]);
|
||||
$list = AlbumModel::where('group_id',$group_id)
|
||||
->where('id','<',$offset)
|
||||
->order('id','desc')
|
||||
@@ -113,7 +111,7 @@ class GroupController extends BaseController
|
||||
//单文件上传
|
||||
$groupID = $request->post('groupID');
|
||||
if(!$groupID){
|
||||
return $this->fail(__('参数错误'));
|
||||
return $this->fail(__('Invalid parameter'));
|
||||
}
|
||||
$res = $this->_upload($request);
|
||||
if(is_string($res)){
|
||||
|
||||
@@ -3,7 +3,6 @@ namespace app\api\controller;
|
||||
use app\model\User as UserModel;
|
||||
use support\Request;
|
||||
use support\Response;
|
||||
use taoser\facade\Validate;
|
||||
use support\think\Db;
|
||||
use hg\apidoc\annotation as Apidoc;
|
||||
|
||||
@@ -22,7 +21,7 @@ class MessageController extends BaseController{
|
||||
*/
|
||||
function delete(Request $request):Response{
|
||||
$im = $request->IM;
|
||||
$data = $im->message->sendBusinessNotification('system',idEncode(100007),[
|
||||
$data = $im->message->sendBusinessNotification('system',\support\Encrypt::userIDencode(100007),[
|
||||
'contentType' => 101,
|
||||
'textElem' => [
|
||||
'content' => '欢迎使用4'.Config('site.name')
|
||||
|
||||
@@ -0,0 +1,473 @@
|
||||
<?php
|
||||
namespace app\api\controller;
|
||||
|
||||
use app\model\User as UserModel;
|
||||
use app\model\FriendCircle as FriendCircleModel;
|
||||
use app\model\FriendCircleLike as FriendCircleLikeModel;
|
||||
use app\model\FriendCircleComment as FriendCircleCommentModel;
|
||||
use support\Request;
|
||||
use support\Response;
|
||||
use support\think\Db;
|
||||
use hg\apidoc\annotation as Apidoc;
|
||||
|
||||
/**
|
||||
* 新朋友圈
|
||||
*/
|
||||
class MomentsController extends BaseController
|
||||
{
|
||||
public $noNeedAuth = ['*'];
|
||||
public $noNeedLogin = [];
|
||||
public $user_display_fields = 'id,userID,nickname,avatar';
|
||||
|
||||
protected function getUserSettings(int $user_id): array
|
||||
{
|
||||
$result = Db::name('user_extend')
|
||||
->where('user_id', $user_id)
|
||||
->field('moments_allow_view_days,moments_banner')
|
||||
->findOrEmpty();
|
||||
return $result;
|
||||
}
|
||||
|
||||
protected function processAvatar($data): void
|
||||
{
|
||||
if (is_array($data)) {
|
||||
if (!empty($data['avatar'])) {
|
||||
$data['avatar'] = cdnurl($data['avatar']);
|
||||
}
|
||||
} elseif (is_object($data) && !empty($data->avatar)) {
|
||||
$data->avatar = cdnurl($data->avatar);
|
||||
}
|
||||
}
|
||||
|
||||
function info(Request $request): Response
|
||||
{
|
||||
$userID = Input('userID');
|
||||
if ($userID) {
|
||||
$user_id = \support\Encrypt::userIDDecode($userID);
|
||||
$json = [
|
||||
'top_unread_items' => [],
|
||||
'unread_item_ids' => [],
|
||||
'unread_count' => 0,
|
||||
'settings' => $this->getUserSettings($user_id)
|
||||
];
|
||||
return $this->success('ok', $json);
|
||||
}
|
||||
|
||||
$user = \support\Jwt::getUser();
|
||||
if (!$user) {
|
||||
return $this->fail('请先登录');
|
||||
}
|
||||
|
||||
$res = $this->newcount($request);
|
||||
$response = $res->rawBody();
|
||||
$json = json_decode($response, true);
|
||||
$json['data']['settings'] = $this->getUserSettings($user->id);
|
||||
|
||||
$top_unread_items = FriendCircleModel::whereIn('id', $json['data']['unread_item_ids'])
|
||||
->with(['user' => function ($query) {
|
||||
$query->field($this->user_display_fields);
|
||||
}])
|
||||
->order('id', 'desc')
|
||||
->limit(0, 3)
|
||||
->select();
|
||||
$json['data']['top_unread_items'] = $top_unread_items ?: [];
|
||||
$res->withBody(json_encode($json));
|
||||
return $res;
|
||||
}
|
||||
|
||||
/**
|
||||
* @Apidoc\Title("列表")
|
||||
* @Apidoc\Method("GET")
|
||||
* @Apidoc\Query("userID", type="string", require=false, desc="用户userID,不传则获取所有")
|
||||
* @Apidoc\Query("page", type="int", require=true, desc="页码", default=1)
|
||||
* @Apidoc\Query("limit", type="int", require=true, desc="分页大小", default=10)
|
||||
*/
|
||||
function list(Request $request): Response
|
||||
{
|
||||
$current_user = \support\Jwt::getUser();
|
||||
$current_user_id = $current_user ? $current_user->id : 0;
|
||||
$page = (int)Input('page', 1);
|
||||
$limit = (int)Input('limit', 10);
|
||||
$userID = Input('userID');
|
||||
|
||||
$query = FriendCircleModel::where('status', 1)
|
||||
->with(['user' => function ($query) {
|
||||
$query->field($this->user_display_fields);
|
||||
}])
|
||||
->order('created_at', 'desc');
|
||||
|
||||
if ($userID) {
|
||||
$user_id = \support\Encrypt::userIDDecode($userID);
|
||||
$query->where('user_id', $user_id);
|
||||
} else {
|
||||
$current_userID = $current_user_id > 0 ? \support\Encrypt::userIDencode($current_user_id) : '';
|
||||
$friendIds = $this->getFriendUserIds($current_userID);
|
||||
if (empty($friendIds)) {
|
||||
return $this->success('ok', []);
|
||||
}
|
||||
$query->whereIn('user_id', $friendIds);
|
||||
}
|
||||
|
||||
$list = $query->paginate([
|
||||
'list_rows' => $limit,
|
||||
'page' => $page,
|
||||
]);
|
||||
|
||||
if (!$userID && $list->count() > 0) {
|
||||
cache('circle_last_read_id_' . $current_user_id, $list[0]['id']);
|
||||
}
|
||||
|
||||
$list->each(function ($item) use ($current_user_id) {
|
||||
$likes = Db::name('friend_circle_like')->alias('f')
|
||||
->join('user u', 'u.id=f.user_id')
|
||||
->where('f.circle_id', $item->id)
|
||||
->field('f.*,u.userID,u.avatar,u.nickname')
|
||||
->order('f.created_at', 'desc')
|
||||
->limit(20)
|
||||
->select();
|
||||
$likes = $likes ? $likes->toArray() : [];
|
||||
|
||||
$is_liked = false;
|
||||
if ($current_user_id > 0) {
|
||||
$is_liked = null !== array_find($likes, function ($like) use ($current_user_id) {
|
||||
return $like['user_id'] == $current_user_id;
|
||||
});
|
||||
}
|
||||
|
||||
$comments = FriendCircleCommentModel::where('circle_id', $item->id)
|
||||
->where('status', 1)
|
||||
->with(['user' => function ($query) {
|
||||
$query->field($this->user_display_fields);
|
||||
}, 'replyUser' => function ($query) {
|
||||
$query->field($this->user_display_fields);
|
||||
}])
|
||||
->order('created_at', 'asc')
|
||||
->limit(10)
|
||||
->select();
|
||||
|
||||
$item->is_liked = $is_liked;
|
||||
$item->likes = $likes;
|
||||
$item->comments = $comments;
|
||||
|
||||
if (!empty($item->files)) {
|
||||
$files = is_array($item->files) ? $item->files : json_decode($item->files, true);
|
||||
$item->files = is_array($files) ? array_map('cdnurl', $files) : [];
|
||||
} else {
|
||||
$item->files = [];
|
||||
}
|
||||
|
||||
if ($item->user && $item->user->avatar) {
|
||||
$item->user->avatar = cdnurl($item->user->avatar);
|
||||
}
|
||||
|
||||
$likes = $item->likes;
|
||||
if (is_array($likes) || $likes instanceof \Traversable) {
|
||||
foreach ($likes as &$like) {
|
||||
if (!empty($like['avatar'])) {
|
||||
$like['avatar'] = cdnurl($like['avatar']);
|
||||
}
|
||||
}
|
||||
unset($like);
|
||||
$item->likes = $likes;
|
||||
}
|
||||
|
||||
foreach ($item->comments as $comment) {
|
||||
if ($comment->user && $comment->user->avatar) {
|
||||
$comment->user->avatar = cdnurl($comment->user->avatar);
|
||||
}
|
||||
if ($comment->replyUser && $comment->replyUser->avatar) {
|
||||
$comment->replyUser->avatar = cdnurl($comment->replyUser->avatar);
|
||||
}
|
||||
}
|
||||
|
||||
return $item;
|
||||
});
|
||||
|
||||
return $this->success('ok', $list);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Apidoc\Title("最近更新的数量")
|
||||
* @Apidoc\Method("POST")
|
||||
* @Apidoc\Param("last_see", type="string", require=false, desc="最近查看的时间戳")
|
||||
*/
|
||||
function newcount(Request $request): Response
|
||||
{
|
||||
$user = \support\Jwt::getUser();
|
||||
if (!$user) {
|
||||
return $this->fail('请先登录');
|
||||
}
|
||||
|
||||
$user_id = $user->id;
|
||||
$circle_last_read_id = cache('circle_last_read_id_' . $user_id) ?: 0;
|
||||
$userID = \support\Encrypt::userIDencode($user_id);
|
||||
|
||||
$unread_item_ids = FriendCircleModel::where('status', 1)
|
||||
->whereIn('user_id', $this->getFriendUserIds($userID))
|
||||
->where('id', '>', $circle_last_read_id)
|
||||
->order('id', 'desc')
|
||||
->column('id');
|
||||
|
||||
return $this->success('ok', [
|
||||
'unread_count' => count($unread_item_ids),
|
||||
'unread_item_ids' => $unread_item_ids
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Apidoc\Title("发布朋友圈")
|
||||
* @Apidoc\Method("POST")
|
||||
* @Apidoc\Param("body", type="string", require=false, desc="内容")
|
||||
* @Apidoc\Param("files", type="string", require=false, desc="图片列表(JSON数组)")
|
||||
*/
|
||||
function create(Request $request): Response
|
||||
{
|
||||
$user = \support\Jwt::getUser();
|
||||
if (!$user) {
|
||||
return $this->fail('请先登录');
|
||||
}
|
||||
|
||||
$body = $request->post('content', '');
|
||||
$files = $request->post('files', '');
|
||||
$address = $request->post('address', '');
|
||||
$releaseType = $request->post('releaseType', '');
|
||||
|
||||
if (empty($body)) {
|
||||
return $this->fail('什么内容都木有啊');
|
||||
}
|
||||
|
||||
$files_array = [];
|
||||
if (!empty($files)) {
|
||||
if (is_string($files)) {
|
||||
$files_array = json_decode($files, true);
|
||||
} elseif (is_array($files)) {
|
||||
$files_array = $files;
|
||||
}
|
||||
|
||||
if (!is_array($files_array)) {
|
||||
return $this->fail('图片列表格式错误');
|
||||
}
|
||||
|
||||
if (count($files_array) > 9) {
|
||||
return $this->fail('最多只能上传9张图片');
|
||||
}
|
||||
}
|
||||
|
||||
$circle = FriendCircleModel::create([
|
||||
'user_id' => $user->id,
|
||||
'releaseType' => $releaseType,
|
||||
'body' => $body,
|
||||
'files' => $files_array,
|
||||
'address' => $address,
|
||||
'status' => 1,
|
||||
]);
|
||||
|
||||
return $this->success('发布成功', ['id' => $circle->id, 'data' => $circle]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Apidoc\Title("发表评论")
|
||||
* @Apidoc\Method("POST")
|
||||
* @Apidoc\Param("body", type="string", require=true, desc="内容")
|
||||
* @Apidoc\Param("id", type="int", require=true, desc="朋友圈动态ID")
|
||||
* @Apidoc\Param("reply_userID", type="string", require=false, desc="回复的用户userID(回复评论时使用)")
|
||||
*/
|
||||
function comment(Request $request): Response
|
||||
{
|
||||
$user = \support\Jwt::getUser();
|
||||
if (!$user) {
|
||||
return $this->fail('请先登录');
|
||||
}
|
||||
|
||||
$body = $request->post('body', '');
|
||||
$circle_id = (int)$request->post('id');
|
||||
$reply_userID = $request->post('reply_userID', '');
|
||||
|
||||
if (empty($body)) {
|
||||
return $this->fail('评论内容不能为空');
|
||||
}
|
||||
|
||||
if ($circle_id <= 0) {
|
||||
return $this->fail('朋友圈动态ID错误');
|
||||
}
|
||||
|
||||
$circle = FriendCircleModel::where('id', $circle_id)
|
||||
->where('status', 1)
|
||||
->find();
|
||||
|
||||
if (!$circle) {
|
||||
return $this->fail('朋友圈动态不存在');
|
||||
}
|
||||
|
||||
$reply_user_id = 0;
|
||||
if (!empty($reply_userID)) {
|
||||
$reply_user_id = (int)\support\Encrypt::userIDDecode($reply_userID);
|
||||
}
|
||||
|
||||
if ($reply_user_id > 0) {
|
||||
$reply_user = UserModel::where('id', $reply_user_id)->find();
|
||||
if (!$reply_user) {
|
||||
return $this->fail('被回复的用户不存在');
|
||||
}
|
||||
}
|
||||
|
||||
$comment = FriendCircleCommentModel::create([
|
||||
'circle_id' => $circle_id,
|
||||
'user_id' => $user->id,
|
||||
'reply_user_id' => $reply_user_id,
|
||||
'body' => $body,
|
||||
'status' => 1,
|
||||
]);
|
||||
|
||||
$circle->comment_count = FriendCircleCommentModel::where('circle_id', $circle_id)
|
||||
->where('status', 1)
|
||||
->count();
|
||||
$circle->save();
|
||||
|
||||
$comment->user = Db::name('user')->field($this->user_display_fields)->where('id', $comment->user_id)->find();
|
||||
$comment->replyUser = null;
|
||||
if ($comment->reply_user_id) {
|
||||
$comment->replyUser = Db::name('user')->field($this->user_display_fields)->where('id', $comment->reply_user_id)->find();
|
||||
}
|
||||
|
||||
return $this->success('评论成功', $comment);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Apidoc\Title("点赞")
|
||||
* @Apidoc\Method("POST")
|
||||
* @Apidoc\Param("id", type="int", require=true, desc="朋友圈动态ID")
|
||||
*/
|
||||
function like(Request $request): Response
|
||||
{
|
||||
$user = \support\Jwt::getUser();
|
||||
if (!$user) {
|
||||
return $this->fail('请先登录');
|
||||
}
|
||||
|
||||
$circle_id = (int)$request->post('id', 0);
|
||||
|
||||
if ($circle_id <= 0) {
|
||||
return $this->fail('朋友圈动态ID错误');
|
||||
}
|
||||
|
||||
$circle = FriendCircleModel::where('id', $circle_id)
|
||||
->where('status', 1)
|
||||
->find();
|
||||
|
||||
if (!$circle) {
|
||||
return $this->fail('朋友圈动态不存在');
|
||||
}
|
||||
|
||||
$like = FriendCircleLikeModel::where('circle_id', $circle_id)
|
||||
->where('user_id', $user->id)
|
||||
->find();
|
||||
|
||||
if ($like) {
|
||||
$like->delete();
|
||||
$circle->like_count = max(0, $circle->like_count - 1);
|
||||
$circle->save();
|
||||
return $this->success('取消点赞成功', ['is_liked' => false]);
|
||||
}
|
||||
|
||||
FriendCircleLikeModel::create([
|
||||
'circle_id' => $circle_id,
|
||||
'user_id' => $user->id,
|
||||
]);
|
||||
$circle->like_count = $circle->like_count + 1;
|
||||
$circle->save();
|
||||
return $this->success('点赞成功', ['is_liked' => true]);
|
||||
}
|
||||
|
||||
protected function getFriendUserIds($userID): array
|
||||
{
|
||||
if (!$userID) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$cache_key = 'friend_id_list_' . $userID;
|
||||
$result = cache($cache_key) ?: [];
|
||||
|
||||
if (count($result) === 0) {
|
||||
$res = request()->IM->friend->getFriendList($userID);
|
||||
$friendsInfo = $res['friendsInfo'] ?? [];
|
||||
foreach ($friendsInfo as $v) {
|
||||
$result[] = \support\Encrypt::userIDDecode($v['friendUser']['userID']);
|
||||
}
|
||||
cache($cache_key, $result, 3600);
|
||||
}
|
||||
|
||||
$result[] = \support\Encrypt::userIDDecode($userID);
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除朋友圈
|
||||
* @Apidoc\Method("POST")
|
||||
* @Apidoc\Param("id", type="int", require=true, desc="朋友圈动态ID")
|
||||
* @return Response
|
||||
*/
|
||||
function delete(Request $request): Response
|
||||
{
|
||||
$id = (int)$request->post('id');
|
||||
$user = \support\Jwt::getUser();
|
||||
if (!$user) {
|
||||
return $this->fail('请先登录');
|
||||
}
|
||||
|
||||
if ($id > 0) {
|
||||
FriendCircleModel::where('id', $id)->where('user_id', $user->id)->delete();
|
||||
}
|
||||
|
||||
return $this->success('删除成功');
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置朋友圈背景
|
||||
* @param Request $request
|
||||
* @return Response
|
||||
*/
|
||||
function upload_bg(Request $request): Response
|
||||
{
|
||||
return $this->setBanner($request);
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置朋友圈背景
|
||||
* @Apidoc\Method("POST")
|
||||
* @Apidoc\Param("file", type="File", require=true, desc="文件")
|
||||
* @return Response
|
||||
*/
|
||||
function setBanner(Request $request): Response
|
||||
{
|
||||
try {
|
||||
$user = \support\Jwt::getUser();
|
||||
if (!$user) {
|
||||
return $this->fail('请先登录');
|
||||
}
|
||||
|
||||
$res = $this->_upload($request);
|
||||
if (is_string($res)) {
|
||||
return $this->fail($res);
|
||||
}
|
||||
|
||||
$exist = Db::name('user_extend')->where('user_id', $user->id)->find();
|
||||
if ($exist) {
|
||||
Db::name('user_extend')
|
||||
->where('user_id', $user->id)
|
||||
->update(['moments_banner' => $res[0]['file_name']]);
|
||||
} else {
|
||||
Db::name('user_extend')->insert([
|
||||
'user_id' => $user->id,
|
||||
'moments_banner' => $res[0]['file_name'],
|
||||
]);
|
||||
}
|
||||
|
||||
return $this->success(__('successful'), [
|
||||
'url' => $res[0]['file_name']
|
||||
]);
|
||||
} catch (\Exception $e) {
|
||||
return $this->error($e->getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,190 @@
|
||||
<?php
|
||||
namespace app\api\controller;
|
||||
|
||||
use app\model\User as UserModel;
|
||||
use app\model\Card;
|
||||
use app\model\Cdkey;
|
||||
use support\think\Db;
|
||||
use hg\apidoc\annotation as Apidoc;
|
||||
use Tinywan\Validate\Facade\Validate;
|
||||
|
||||
/**
|
||||
* 通行证
|
||||
*/
|
||||
class PassportController extends BaseController{
|
||||
/**
|
||||
* 不需要鉴权的方法
|
||||
* @var array
|
||||
*/
|
||||
public $noNeedAuth = ['*'];
|
||||
public $noNeedLogin = [];
|
||||
/**
|
||||
* 安全验证
|
||||
* @Apidoc\Method("POST")
|
||||
* @Apidoc\Param("verify_type", type="string", require=true, desc="验证类型,email或mobile")
|
||||
* @Apidoc\Param("code", type="string", require=true, desc="验证码,event=verify")
|
||||
*/
|
||||
public function security_verify()
|
||||
{
|
||||
$user = \support\Jwt::getUser();
|
||||
$verify_type = input('verify_type');
|
||||
if($verify_type=='mobile'){
|
||||
captcha_verify('mobile', 'verify', $user->mobile);
|
||||
}else if($verify_type == 'email'){
|
||||
captcha_verify('email', 'verify', $user->email);
|
||||
}else{
|
||||
return $this->error(__('Invalid verify type'));
|
||||
}
|
||||
return $this->success(__('Security verify successfully'));
|
||||
}
|
||||
/**
|
||||
* 绑定手机号
|
||||
* @Apidoc\Method("POST")
|
||||
* @Apidoc\Param("region", type="string", require=true, desc="区域代码")
|
||||
* @Apidoc\Param("mobile", type="string", require=true, desc="手机号")
|
||||
* @Apidoc\Param("code", type="string", require=true, desc="验证码,event=bind_mobile")
|
||||
*/
|
||||
public function bind_mobile()
|
||||
{
|
||||
$user = \support\Jwt::getUser();
|
||||
$mobile = input('mobile');
|
||||
$region = input('region','+86');
|
||||
$region = str_replace('+','',$region);
|
||||
|
||||
// 验证手机号格式
|
||||
if (!$mobile || !Validate::regex($mobile, "^1\d{10}$")) {
|
||||
return $this->error(__('Incorrect mobile number format'));
|
||||
}
|
||||
|
||||
// 验证手机号唯一性
|
||||
if (UserModel::where('mobile', $mobile)->where('region',$region)->where('id', '<>', $user->id)->find()) {
|
||||
return $this->error(__('Mobile number already exists'));
|
||||
}
|
||||
|
||||
// 验证验证码
|
||||
captcha_verify('mobile', 'bind_mobile', $mobile);
|
||||
|
||||
// 更新用户信息
|
||||
$user->mobile = $mobile;
|
||||
$user->region = $region;
|
||||
//$user->mobile_verify = 1;
|
||||
$user->save();
|
||||
|
||||
return $this->success(__('Mobile number bound successfully'));
|
||||
}
|
||||
|
||||
/**
|
||||
* 绑定邮箱
|
||||
* @Apidoc\Method("POST")
|
||||
* @Apidoc\Param("email", type="string", require=true, desc="邮箱")
|
||||
* @Apidoc\Param("code", type="string", require=true, desc="验证码,event=bind_email")
|
||||
*/
|
||||
public function bind_email()
|
||||
{
|
||||
$user = \support\Jwt::getUser();
|
||||
$email = input('email');
|
||||
// 验证邮箱格式
|
||||
if (!$email || !Validate::email($email)) {
|
||||
return $this->error(__('Incorrect email format'));
|
||||
}
|
||||
|
||||
// 验证邮箱唯一性
|
||||
if (UserModel::where('email', $email)->where('id', '<>', $user->id)->find()) {
|
||||
return $this->error(__('Email already exists'));
|
||||
}
|
||||
captcha_verify('email', 'bind_email', $email);
|
||||
|
||||
|
||||
// 更新用户信息
|
||||
$user->email = $email;
|
||||
//$user->email_verify = 1;
|
||||
$user->save();
|
||||
|
||||
return $this->success(__('Email bound successfully'));
|
||||
}
|
||||
|
||||
/**
|
||||
* 绑定用户名
|
||||
* @Apidoc\Method("POST")
|
||||
* @Apidoc\Param("username", type="string", require=true, desc="用户名")
|
||||
* @Apidoc\Param("verify_type", type="string", require=true, desc="验证类型,email或mobile")
|
||||
* @Apidoc\Param("code", type="string", require=true, desc="验证码,event=bind_username")
|
||||
*/
|
||||
public function bind_username()
|
||||
{
|
||||
$user = \support\Jwt::getUser();
|
||||
$username = input('username');
|
||||
$verify_type = input('verify_type');
|
||||
|
||||
// 验证用户名格式
|
||||
if (!$username || strlen($username) < 3 || strlen($username) > 20) {
|
||||
return $this->error(__('Username length must be between 3 and 20 characters'));
|
||||
}
|
||||
|
||||
// 验证用户名唯一性
|
||||
if (UserModel::where('username', $username)->where('id', '<>', $user->id)->find()) {
|
||||
return $this->error(__('Username already exists'));
|
||||
}
|
||||
|
||||
if($verify_type == 'mobile'){
|
||||
captcha_verify('mobile', 'bind_username', $user->mobile);
|
||||
}else if($verify_type == 'email'){
|
||||
captcha_verify('email', 'bind_username', $user->email);
|
||||
}
|
||||
|
||||
// 更新用户信息
|
||||
$user->username = $username;
|
||||
$user->save();
|
||||
|
||||
return $this->success(__('Username bound successfully'));
|
||||
}
|
||||
|
||||
/**
|
||||
* 解绑手机号
|
||||
* @Apidoc\Method("POST")
|
||||
* @Apidoc\Param("code", type="string", require=true, desc="验证码,event=unbind_mobile")
|
||||
*/
|
||||
public function unbind_mobile()
|
||||
{
|
||||
$user = \support\Jwt::getUser();
|
||||
|
||||
if (!$user->mobile) {
|
||||
return $this->error(__('Mobile number not bound'));
|
||||
}
|
||||
|
||||
// 验证验证码
|
||||
captcha_verify('mobile', 'unbind_mobile', $user->mobile);
|
||||
|
||||
// 更新用户信息
|
||||
$user->mobile = '';
|
||||
$user->mobile_verify = 0;
|
||||
$user->save();
|
||||
|
||||
return $this->success(__('Mobile number unbound successfully'));
|
||||
}
|
||||
|
||||
/**
|
||||
* 解绑邮箱
|
||||
* @Apidoc\Method("POST")
|
||||
* @Apidoc\Param("code", type="string", require=true, desc="验证码,event=unbind_email")
|
||||
*/
|
||||
public function unbind_email()
|
||||
{
|
||||
$user = \support\Jwt::getUser();
|
||||
|
||||
if (!$user->email) {
|
||||
return $this->error(__('Email not bound'));
|
||||
}
|
||||
|
||||
// 验证验证码
|
||||
captcha_verify('email', 'unbind_email', $user->email);
|
||||
|
||||
// 更新用户信息
|
||||
$user->email = '';
|
||||
$user->email_verify = 0;
|
||||
$user->save();
|
||||
|
||||
return $this->success(__('Email unbound successfully'));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,731 @@
|
||||
<?php
|
||||
namespace app\api\controller;
|
||||
|
||||
use support\Request;
|
||||
use support\Response;
|
||||
use Yansongda\Pay\Pay;
|
||||
use Yansongda\Pay\Log;
|
||||
use think\facade\Db;
|
||||
use support\Log as WebmanLog;
|
||||
use hg\apidoc\annotation as Apidoc;
|
||||
use app\enum\Payment\Method;
|
||||
use app\enum\Payment\Status;
|
||||
use app\enum\Payment\Type;
|
||||
|
||||
/**
|
||||
* 支付控制器
|
||||
* @Apidoc\Title("支付管理")
|
||||
* @Apidoc\Group("payment")
|
||||
*/
|
||||
class PaymentController extends BaseController
|
||||
{
|
||||
/**
|
||||
* 支付宝支付下单
|
||||
* @Apidoc\Title("支付宝支付下单")
|
||||
* @Apidoc\Url("/api/payment/alipay/order")
|
||||
* @Apidoc\Method("POST")
|
||||
* @Apidoc\Param("amount", "float", "支付金额", true, "0.01")
|
||||
* @Apidoc\Param("order_no", "string", "订单号", true, "20260409001")
|
||||
* @Apidoc\Param("subject", "string", "订单标题", true, "测试商品")
|
||||
* @Apidoc\Param("body", "string", "订单描述", false, "测试商品描述")
|
||||
* @Apidoc\Param("type", "string", "订单类型: recharge(充值), goods(商品), service(服务), other(其他)", false, "goods")
|
||||
* @Apidoc\Return("success", "object", "成功响应", "pay_url|string|支付链接,order_no|string|订单号")
|
||||
* @Apidoc\Return("error", "object", "失败响应", "code|int|错误码,msg|string|错误信息")
|
||||
* @param Request $request
|
||||
* @return Response
|
||||
*/
|
||||
public function alipay_order(Request $request): Response
|
||||
{
|
||||
try {
|
||||
$params = $request->post();
|
||||
|
||||
// 验证参数
|
||||
if (!isset($params['amount']) || !isset($params['order_no']) || !isset($params['subject'])) {
|
||||
return $this->error('缺少必要参数');
|
||||
}
|
||||
|
||||
$amount = $params['amount'];
|
||||
$orderNo = $params['order_no'];
|
||||
$subject = $params['subject'];
|
||||
$body = $params['body'] ?? $subject;
|
||||
$type = $params['type'] ?? 'goods';
|
||||
|
||||
// 获取支付宝配置
|
||||
$config = config('payment.alipay.default');
|
||||
|
||||
// 构建支付参数
|
||||
$payOrder = [
|
||||
'out_trade_no' => $orderNo,
|
||||
'total_amount' => $amount,
|
||||
'subject' => $subject,
|
||||
'body' => $body,
|
||||
];
|
||||
|
||||
// 发起支付
|
||||
$result = Pay::alipay($config)->web($payOrder);
|
||||
|
||||
// 记录支付订单
|
||||
$this->record_payment_order($orderNo, 'alipay', $amount, $subject, $type);
|
||||
|
||||
return $this->success('ok',[
|
||||
'pay_url' => $result->getTargetUrl(),
|
||||
'order_no' => $orderNo
|
||||
]);
|
||||
|
||||
} catch (\Exception $e) {
|
||||
WebmanLog::error('支付宝支付下单失败: ' . $e->getMessage());
|
||||
return $this->error('支付下单失败: ' . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 支付宝同步回调
|
||||
* @Apidoc\Title("支付宝同步回调")
|
||||
* @Apidoc\Url("/api/payment/alipay/return")
|
||||
* @Apidoc\Method("GET")
|
||||
* @Apidoc\Param("out_trade_no", "string", "订单号", true)
|
||||
* @Apidoc\Param("trade_no", "string", "支付宝交易号", true)
|
||||
* @Apidoc\Param("trade_status", "string", "交易状态", true)
|
||||
* @Apidoc\Return("redirect", "string", "跳转到成功或失败页面")
|
||||
* @param Request $request
|
||||
* @return Response
|
||||
*/
|
||||
public function alipay_return(Request $request): Response
|
||||
{
|
||||
try {
|
||||
$config = config('payment.alipay.default');
|
||||
$data = $request->all();
|
||||
|
||||
// 验证回调数据
|
||||
$result = Pay::alipay($config)->verify($data);
|
||||
|
||||
// 处理支付结果
|
||||
$orderNo = $result->out_trade_no;
|
||||
$tradeNo = $result->trade_no;
|
||||
$status = $result->trade_status;
|
||||
|
||||
// 更新订单状态
|
||||
$this->update_payment_order($orderNo, $tradeNo, $status);
|
||||
|
||||
// 跳转到成功页面
|
||||
return redirect('/api/payment/success?order_no=' . $orderNo);
|
||||
|
||||
} catch (\Exception $e) {
|
||||
WebmanLog::error('支付宝同步回调失败: ' . $e->getMessage());
|
||||
return redirect('/api/payment/fail?error=' . urlencode($e->getMessage()));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 支付宝异步回调
|
||||
* @Apidoc\Title("支付宝异步回调")
|
||||
* @Apidoc\Url("/api/payment/alipay/notify")
|
||||
* @Apidoc\Method("POST")
|
||||
* @Apidoc\Param("out_trade_no", "string", "订单号", true)
|
||||
* @Apidoc\Param("trade_no", "string", "支付宝交易号", true)
|
||||
* @Apidoc\Param("trade_status", "string", "交易状态", true)
|
||||
* @Apidoc\Return("string", "string", "返回success或fail")
|
||||
* @param Request $request
|
||||
* @return string
|
||||
*/
|
||||
public function alipay_notify(Request $request): string
|
||||
{
|
||||
try {
|
||||
$config = config('payment.alipay.default');
|
||||
$data = $request->all();
|
||||
|
||||
// 验证回调数据
|
||||
$result = Pay::alipay($config)->verify($data);
|
||||
|
||||
// 处理支付结果
|
||||
$orderNo = $result->out_trade_no;
|
||||
$tradeNo = $result->trade_no;
|
||||
$status = $result->trade_status;
|
||||
|
||||
// 更新订单状态
|
||||
$this->update_payment_order($orderNo, $tradeNo, $status);
|
||||
|
||||
// 返回成功
|
||||
return 'success';
|
||||
|
||||
} catch (\Exception $e) {
|
||||
WebmanLog::error('支付宝异步回调失败: ' . $e->getMessage());
|
||||
return 'fail';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 微信支付下单
|
||||
* @Apidoc\Title("微信支付下单")
|
||||
* @Apidoc\Url("/api/payment/wechat/order")
|
||||
* @Apidoc\Method("POST")
|
||||
* @Apidoc\Param("amount", "float", "支付金额", true, "0.01")
|
||||
* @Apidoc\Param("order_no", "string", "订单号", true, "20260409001")
|
||||
* @Apidoc\Param("body", "string", "订单描述", true, "测试商品描述")
|
||||
* @Apidoc\Param("trade_type", "string", "交易类型: JSAPI, NATIVE, APP, MWEB", false, "JSAPI")
|
||||
* @Apidoc\Param("openid", "string", "微信用户openid(JSAPI模式需要)", false, "o123456")
|
||||
* @Apidoc\Param("type", "string", "订单类型: recharge(充值), goods(商品), service(服务), other(其他)", false, "goods")
|
||||
* @Apidoc\Return("success", "object", "成功响应", "pay_data|object|支付数据,order_no|string|订单号")
|
||||
* @Apidoc\Return("error", "object", "失败响应", "code|int|错误码,msg|string|错误信息")
|
||||
* @param Request $request
|
||||
* @return Response
|
||||
*/
|
||||
public function wechat_order(Request $request): Response
|
||||
{
|
||||
try {
|
||||
$params = $request->post();
|
||||
|
||||
// 验证参数
|
||||
if (!isset($params['amount']) || !isset($params['order_no']) || !isset($params['body'])) {
|
||||
return $this->error('缺少必要参数');
|
||||
}
|
||||
|
||||
$amount = $params['amount'];
|
||||
$orderNo = $params['order_no'];
|
||||
$body = $params['body'];
|
||||
$tradeType = $params['trade_type'] ?? 'JSAPI';
|
||||
$openid = $params['openid'] ?? '';
|
||||
$type = $params['type'] ?? 'goods';
|
||||
|
||||
// 获取微信支付配置
|
||||
$config = config('payment.wechat.default');
|
||||
|
||||
// 构建支付参数
|
||||
$payOrder = [
|
||||
'out_trade_no' => $orderNo,
|
||||
'total_fee' => $amount * 100, // 微信支付金额单位为分
|
||||
'body' => $body,
|
||||
'trade_type' => $tradeType,
|
||||
];
|
||||
|
||||
// JSAPI支付需要openid
|
||||
if ($tradeType === 'JSAPI' && $openid) {
|
||||
$payOrder['openid'] = $openid;
|
||||
}
|
||||
|
||||
// 发起支付
|
||||
$result = Pay::wechat($config)->order($payOrder);
|
||||
|
||||
// 记录支付订单
|
||||
$this->record_payment_order($orderNo, 'wechat', $amount, $body, $type);
|
||||
|
||||
return $this->success('ok',[
|
||||
'pay_data' => $result->toArray(),
|
||||
'order_no' => $orderNo
|
||||
]);
|
||||
|
||||
} catch (\Exception $e) {
|
||||
WebmanLog::error('微信支付下单失败: ' . $e->getMessage());
|
||||
return $this->error('支付下单失败: ' . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 微信支付回调
|
||||
* @Apidoc\Title("微信支付回调")
|
||||
* @Apidoc\Url("/api/payment/wechat/notify")
|
||||
* @Apidoc\Method("POST")
|
||||
* @Apidoc\Param("out_trade_no", "string", "订单号", true)
|
||||
* @Apidoc\Param("transaction_id", "string", "微信交易号", true)
|
||||
* @Apidoc\Param("result_code", "string", "业务结果", true)
|
||||
* @Apidoc\Return("xml", "string", "返回XML格式的成功或失败响应")
|
||||
* @param Request $request
|
||||
* @return string
|
||||
*/
|
||||
public function wechat_notify(Request $request): string
|
||||
{
|
||||
try {
|
||||
$config = config('payment.wechat.default');
|
||||
$data = $request->all();
|
||||
|
||||
// 验证回调数据
|
||||
$result = Pay::wechat($config)->verify($data);
|
||||
|
||||
// 处理支付结果
|
||||
$orderNo = $result->out_trade_no;
|
||||
$tradeNo = $result->transaction_id;
|
||||
$status = $result->result_code === 'SUCCESS' ? 'SUCCESS' : 'FAIL';
|
||||
|
||||
// 更新订单状态
|
||||
$this->update_payment_order($orderNo, $tradeNo, $status);
|
||||
|
||||
// 返回成功
|
||||
return Pay::wechat($config)->success();
|
||||
|
||||
} catch (\Exception $e) {
|
||||
WebmanLog::error('微信支付回调失败: ' . $e->getMessage());
|
||||
return Pay::wechat(config('payment.wechat.default'))->fail();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 记录支付订单
|
||||
* @param string $orderNo
|
||||
* @param string $payType
|
||||
* @param float $amount
|
||||
* @param string $subject
|
||||
* @param string $type
|
||||
*/
|
||||
private function record_payment_order(string $orderNo, string $payType, float $amount, string $subject, string $type = 'goods'): void
|
||||
{
|
||||
try {
|
||||
Db::name('payment_order')->insert([
|
||||
'order_no' => $orderNo,
|
||||
'pay_type' => $payType,
|
||||
'type' => $type,
|
||||
'amount' => $amount,
|
||||
'subject' => $subject,
|
||||
'status' => Status::CREATED->value,
|
||||
'created_at' => time(),
|
||||
'updated_at' => time()
|
||||
]);
|
||||
} catch (\Exception $e) {
|
||||
WebmanLog::error('记录支付订单失败: ' . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新支付订单状态
|
||||
* @param string $orderNo
|
||||
* @param string $tradeNo
|
||||
* @param string $status
|
||||
*/
|
||||
private function update_payment_order(string $orderNo, string $tradeNo, string $status): void
|
||||
{
|
||||
try {
|
||||
// 映射支付状态到我们的状态枚举
|
||||
$mappedStatus = $this->map_payment_status($status);
|
||||
|
||||
Db::name('payment_order')->where('order_no', $orderNo)->update([
|
||||
'trade_no' => $tradeNo,
|
||||
'status' => $mappedStatus,
|
||||
'updated_at' => time()
|
||||
]);
|
||||
} catch (\Exception $e) {
|
||||
WebmanLog::error('更新支付订单状态失败: ' . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 映射支付状态到枚举
|
||||
* @param string $status
|
||||
* @return string
|
||||
*/
|
||||
private function map_payment_status(string $status): string
|
||||
{
|
||||
// 支付宝状态映射
|
||||
$alipayStatusMap = [
|
||||
'TRADE_SUCCESS' => Status::SUCCESS->value,
|
||||
'TRADE_FINISHED' => Status::COMPLETE->value,
|
||||
'TRADE_CLOSED' => Status::FAIL->value,
|
||||
'WAIT_BUYER_PAY' => Status::CREATED->value
|
||||
];
|
||||
|
||||
// 微信支付状态映射
|
||||
$wechatStatusMap = [
|
||||
'SUCCESS' => Status::SUCCESS->value,
|
||||
'FAIL' => Status::FAIL->value,
|
||||
'REFUND' => Status::REFUNDED->value
|
||||
];
|
||||
|
||||
// 先尝试支付宝映射
|
||||
if (isset($alipayStatusMap[$status])) {
|
||||
return $alipayStatusMap[$status];
|
||||
}
|
||||
|
||||
// 再尝试微信映射
|
||||
if (isset($wechatStatusMap[$status])) {
|
||||
return $wechatStatusMap[$status];
|
||||
}
|
||||
|
||||
// 默认返回成功
|
||||
return Status::SUCCESS->value;
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询支付订单状态
|
||||
* @Apidoc\Title("查询支付订单状态")
|
||||
* @Apidoc\Url("/api/payment/order/query")
|
||||
* @Apidoc\Method("GET")
|
||||
* @Apidoc\Param("order_no", "string", "订单号", true, "20260409001")
|
||||
* @Apidoc\Param("pay_type", "string", "支付类型: alipay, wechat", true, "alipay")
|
||||
* @Apidoc\Return("success", "object", "成功响应", "order|object|订单信息,pay_status|object|支付状态")
|
||||
* @Apidoc\Return("error", "object", "失败响应", "code|int|错误码,msg|string|错误信息")
|
||||
* @param Request $request
|
||||
* @return Response
|
||||
*/
|
||||
public function query_order(Request $request): Response
|
||||
{
|
||||
try {
|
||||
$orderNo = $request->get('order_no');
|
||||
$payType = $request->get('pay_type');
|
||||
|
||||
if (!$orderNo || !$payType) {
|
||||
return $this->error('缺少必要参数');
|
||||
}
|
||||
|
||||
// 查询订单
|
||||
$order = Db::name('payment_order')->where('order_no', $orderNo)->find();
|
||||
if (!$order) {
|
||||
return $this->error('订单不存在');
|
||||
}
|
||||
|
||||
// 根据支付类型查询支付状态
|
||||
if ($payType === \app\enum\Payment\Method::ALIPAY->value) {
|
||||
$config = config('payment.alipay.default');
|
||||
$result = Pay::alipay($config)->find($orderNo);
|
||||
} elseif ($payType === \app\enum\Payment\Method::WECHAT->value) {
|
||||
$config = config('payment.wechat.default');
|
||||
$result = Pay::wechat($config)->find($orderNo);
|
||||
} else {
|
||||
return $this->error('不支持的支付类型');
|
||||
}
|
||||
|
||||
return $this->success('ok',[
|
||||
'order' => $order,
|
||||
'pay_status' => $result->toArray()
|
||||
]);
|
||||
|
||||
} catch (\Exception $e) {
|
||||
WebmanLog::error('查询支付订单失败: ' . $e->getMessage());
|
||||
return $this->error('查询失败: ' . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 关闭支付订单
|
||||
* @Apidoc\Title("关闭支付订单")
|
||||
* @Apidoc\Url("/api/payment/order/close")
|
||||
* @Apidoc\Method("POST")
|
||||
* @Apidoc\Param("order_no", "string", "订单号", true, "20260409001")
|
||||
* @Apidoc\Param("pay_type", "string", "支付类型: alipay, wechat", true, "alipay")
|
||||
* @Apidoc\Return("success", "object", "成功响应", "msg|string|成功信息")
|
||||
* @Apidoc\Return("error", "object", "失败响应", "code|int|错误码,msg|string|错误信息")
|
||||
* @param Request $request
|
||||
* @return Response
|
||||
*/
|
||||
public function close_order(Request $request): Response
|
||||
{
|
||||
try {
|
||||
$orderNo = $request->post('order_no');
|
||||
$payType = $request->post('pay_type');
|
||||
|
||||
if (!$orderNo || !$payType) {
|
||||
return $this->error('缺少必要参数');
|
||||
}
|
||||
|
||||
// 关闭订单
|
||||
if ($payType === 'alipay') {
|
||||
$config = config('payment.alipay.default');
|
||||
$result = Pay::alipay($config)->close($orderNo);
|
||||
} elseif ($payType === 'wechat') {
|
||||
$config = config('payment.wechat.default');
|
||||
$result = Pay::wechat($config)->close($orderNo);
|
||||
} else {
|
||||
return $this->error('不支持的支付类型');
|
||||
}
|
||||
|
||||
// 更新订单状态
|
||||
Db::name('payment_order')->where('order_no', $orderNo)->update([
|
||||
'status' => \app\enum\Payment\Status::COMPLETE->value,
|
||||
'updated_at' => time()
|
||||
]);
|
||||
|
||||
return $this->success('订单关闭成功');
|
||||
|
||||
} catch (\Exception $e) {
|
||||
WebmanLog::error('关闭支付订单失败: ' . $e->getMessage());
|
||||
return $this->error('关闭失败: ' . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 退款
|
||||
* @Apidoc\Title("退款")
|
||||
* @Apidoc\Url("/api/payment/refund")
|
||||
* @Apidoc\Method("POST")
|
||||
* @Apidoc\Param("order_no", "string", "订单号", true, "20260409001")
|
||||
* @Apidoc\Param("pay_type", "string", "支付类型: alipay, wechat", true, "alipay")
|
||||
* @Apidoc\Param("amount", "float", "退款金额", true, "0.01")
|
||||
* @Apidoc\Param("refund_no", "string", "退款单号", false, "20260409001_refund")
|
||||
* @Apidoc\Param("reason", "string", "退款原因", false, "退款")
|
||||
* @Apidoc\Return("success", "object", "成功响应", "msg|string|成功信息")
|
||||
* @Apidoc\Return("error", "object", "失败响应", "code|int|错误码,msg|string|错误信息")
|
||||
* @param Request $request
|
||||
* @return Response
|
||||
*/
|
||||
public function refund(Request $request): Response
|
||||
{
|
||||
try {
|
||||
$params = $request->post();
|
||||
|
||||
if (!isset($params['order_no']) || !isset($params['pay_type']) || !isset($params['amount'])) {
|
||||
return $this->error('缺少必要参数');
|
||||
}
|
||||
|
||||
$orderNo = $params['order_no'];
|
||||
$payType = $params['pay_type'];
|
||||
$amount = $params['amount'];
|
||||
$refundNo = $params['refund_no'] ?? $orderNo . '_' . time();
|
||||
$reason = $params['reason'] ?? '退款';
|
||||
|
||||
// 发起退款
|
||||
if ($payType === Method::ALIPAY->value) {
|
||||
$config = config('payment.alipay.default');
|
||||
$result = Pay::alipay($config)->refund([
|
||||
'out_trade_no' => $orderNo,
|
||||
'refund_amount' => $amount,
|
||||
'out_request_no' => $refundNo,
|
||||
'refund_reason' => $reason,
|
||||
]);
|
||||
} elseif ($payType === Method::WECHAT->value) {
|
||||
$config = config('payment.wechat.default');
|
||||
$result = Pay::wechat($config)->refund([
|
||||
'out_trade_no' => $orderNo,
|
||||
'out_refund_no' => $refundNo,
|
||||
'total_fee' => $amount * 100,
|
||||
'refund_fee' => $amount * 100,
|
||||
'refund_desc' => $reason,
|
||||
]);
|
||||
} else {
|
||||
return $this->error('不支持的支付类型');
|
||||
}
|
||||
|
||||
// 记录退款信息
|
||||
Db::name('payment_refund')->insert([
|
||||
'order_no' => $orderNo,
|
||||
'refund_no' => $refundNo,
|
||||
'pay_type' => $payType,
|
||||
'amount' => $amount,
|
||||
'reason' => $reason,
|
||||
'status' => 'SUCCESS',
|
||||
'created_at' => time()
|
||||
]);
|
||||
|
||||
// 更新订单状态
|
||||
Db::name('payment_order')->where('order_no', $orderNo)->update([
|
||||
'status' => Status::REFUNDED->value,
|
||||
'updated_at' => time()
|
||||
]);
|
||||
|
||||
return $this->success('退款成功');
|
||||
|
||||
} catch (\Exception $e) {
|
||||
WebmanLog::error('退款失败: ' . $e->getMessage());
|
||||
return $this->error('退款失败: ' . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 统一支付接口
|
||||
* @Apidoc\Title("统一支付接口")
|
||||
* @Apidoc\Url("/api/payment/unified/order")
|
||||
* @Apidoc\Method("POST")
|
||||
* @Apidoc\Param("payment", "string", "支付方式: alipay, wechat", true, "alipay")
|
||||
* @Apidoc\Param("amount", "float", "支付金额", true, "0.01")
|
||||
* @Apidoc\Param("order_no", "string", "订单号", true, "20260409001")
|
||||
* @Apidoc\Param("subject", "string", "订单标题", true, "测试商品")
|
||||
* @Apidoc\Param("body", "string", "订单描述", false, "测试商品描述")
|
||||
* @Apidoc\Param("type", "string", "订单类型: recharge(充值), goods(商品), service(服务), other(其他)", false, "goods")
|
||||
* @Apidoc\Param("trade_type", "string", "交易类型(微信支付需要): JSAPI, NATIVE, APP, MWEB", false, "JSAPI")
|
||||
* @Apidoc\Param("openid", "string", "微信用户openid(JSAPI模式需要)", false, "o123456")
|
||||
* @Apidoc\Return("success", "object", "成功响应", "pay_url|string|支付宝支付链接,pay_data|object|微信支付数据,order_no|string|订单号")
|
||||
* @Apidoc\Return("error", "object", "失败响应", "code|int|错误码,msg|string|错误信息")
|
||||
* @param Request $request
|
||||
* @return Response
|
||||
*/
|
||||
public function unified_order(Request $request): Response
|
||||
{
|
||||
try {
|
||||
$params = $request->post();
|
||||
|
||||
// 验证必要参数
|
||||
if (!isset($params['payment']) || !isset($params['amount']) || !isset($params['order_no']) || !isset($params['subject'])) {
|
||||
return $this->error('缺少必要参数');
|
||||
}
|
||||
|
||||
$payment = strtolower($params['payment']);
|
||||
$amount = $params['amount'];
|
||||
$orderNo = $params['order_no'];
|
||||
$subject = $params['subject'];
|
||||
$body = $params['body'] ?? $subject;
|
||||
$type = $params['type'] ?? 'goods';
|
||||
|
||||
// 根据payment参数选择支付方式
|
||||
switch ($payment) {
|
||||
case Method::ALIPAY->value:
|
||||
// 调用支付宝支付
|
||||
return $this->alipay_order($request);
|
||||
|
||||
case Method::WECHAT->value:
|
||||
// 调用微信支付
|
||||
return $this->wechat_order($request);
|
||||
|
||||
default:
|
||||
return $this->error('不支持的支付方式');
|
||||
}
|
||||
|
||||
} catch (\Exception $e) {
|
||||
WebmanLog::error('统一支付接口失败: ' . $e->getMessage());
|
||||
return $this->error('支付失败: ' . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 统一支付回调接口
|
||||
* @Apidoc\Title("统一支付回调接口")
|
||||
* @Apidoc\Url("/api/payment/unified/notify")
|
||||
* @Apidoc\Method("ANY")
|
||||
* @Apidoc\Param("payment", "string", "支付方式: alipay, wechat", false)
|
||||
* @Apidoc\Param("out_trade_no", "string", "订单号", true)
|
||||
* @Apidoc\Param("trade_no|transaction_id", "string", "支付交易号", true)
|
||||
* @Apidoc\Param("trade_status|result_code", "string", "交易状态", true)
|
||||
* @Apidoc\Return("string", "string", "返回success/fail或XML格式响应")
|
||||
* @param Request $request
|
||||
* @return Response
|
||||
*/
|
||||
public function unified_notify(Request $request): Response
|
||||
{
|
||||
try {
|
||||
$payment = $request->get('payment') ?? $request->post('payment');
|
||||
|
||||
if (!$payment) {
|
||||
// 尝试从请求参数中自动识别
|
||||
$data = $request->all();
|
||||
if (isset($data['app_id']) && strpos($data['app_id'], '20') === 0) {
|
||||
$payment = 'alipay';
|
||||
} elseif (isset($data['mch_id'])) {
|
||||
$payment = 'wechat';
|
||||
} else {
|
||||
return $this->error('无法识别支付方式');
|
||||
}
|
||||
}
|
||||
|
||||
$payment = strtolower($payment);
|
||||
|
||||
// 根据payment参数选择回调处理
|
||||
switch ($payment) {
|
||||
case Method::ALIPAY->value:
|
||||
// 调用支付宝回调
|
||||
return $this->alipay_notify($request);
|
||||
|
||||
case Method::WECHAT->value:
|
||||
// 调用微信回调
|
||||
return $this->wechat_notify($request);
|
||||
|
||||
default:
|
||||
return $this->error('不支持的支付方式');
|
||||
}
|
||||
|
||||
} catch (\Exception $e) {
|
||||
WebmanLog::error('统一支付回调接口失败: ' . $e->getMessage());
|
||||
return $this->error('回调处理失败: ' . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 统一支付查询接口
|
||||
* @Apidoc\Title("统一支付查询接口")
|
||||
* @Apidoc\Url("/api/payment/unified/query")
|
||||
* @Apidoc\Method("GET")
|
||||
* @Apidoc\Param("order_no", "string", "订单号", true, "20260409001")
|
||||
* @Apidoc\Param("payment", "string", "支付方式: alipay, wechat", true, "alipay")
|
||||
* @Apidoc\Return("success", "object", "成功响应", "order|object|订单信息,pay_status|object|支付状态")
|
||||
* @Apidoc\Return("error", "object", "失败响应", "code|int|错误码,msg|string|错误信息")
|
||||
* @param Request $request
|
||||
* @return Response
|
||||
*/
|
||||
public function unified_query(Request $request): Response
|
||||
{
|
||||
try {
|
||||
$params = $request->all();
|
||||
|
||||
// 验证必要参数
|
||||
if (!isset($params['order_no']) || !isset($params['payment'])) {
|
||||
return $this->error('缺少必要参数');
|
||||
}
|
||||
|
||||
// 设置pay_type参数
|
||||
$request->withAddedHeader('pay_type', $params['payment']);
|
||||
|
||||
// 调用查询接口
|
||||
return $this->query_order($request);
|
||||
|
||||
} catch (\Exception $e) {
|
||||
WebmanLog::error('统一支付查询接口失败: ' . $e->getMessage());
|
||||
return $this->error('查询失败: ' . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 统一支付关闭接口
|
||||
* @Apidoc\Title("统一支付关闭接口")
|
||||
* @Apidoc\Url("/api/payment/unified/close")
|
||||
* @Apidoc\Method("POST")
|
||||
* @Apidoc\Param("order_no", "string", "订单号", true, "20260409001")
|
||||
* @Apidoc\Param("payment", "string", "支付方式: alipay, wechat", true, "alipay")
|
||||
* @Apidoc\Return("success", "object", "成功响应", "msg|string|成功信息")
|
||||
* @Apidoc\Return("error", "object", "失败响应", "code|int|错误码,msg|string|错误信息")
|
||||
* @param Request $request
|
||||
* @return Response
|
||||
*/
|
||||
public function unified_close(Request $request): Response
|
||||
{
|
||||
try {
|
||||
$params = $request->post();
|
||||
|
||||
// 验证必要参数
|
||||
if (!isset($params['order_no']) || !isset($params['payment'])) {
|
||||
return $this->error('缺少必要参数');
|
||||
}
|
||||
|
||||
// 设置pay_type参数
|
||||
$request->withAddedHeader('pay_type', $params['payment']);
|
||||
|
||||
// 调用关闭接口
|
||||
return $this->close_order($request);
|
||||
|
||||
} catch (\Exception $e) {
|
||||
WebmanLog::error('统一支付关闭接口失败: ' . $e->getMessage());
|
||||
return $this->error('关闭失败: ' . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 统一退款接口
|
||||
* @Apidoc\Title("统一退款接口")
|
||||
* @Apidoc\Url("/api/payment/unified/refund")
|
||||
* @Apidoc\Method("POST")
|
||||
* @Apidoc\Param("order_no", "string", "订单号", true, "20260409001")
|
||||
* @Apidoc\Param("payment", "string", "支付方式: alipay, wechat", true, "alipay")
|
||||
* @Apidoc\Param("amount", "float", "退款金额", true, "0.01")
|
||||
* @Apidoc\Param("refund_no", "string", "退款单号", false, "20260409001_refund")
|
||||
* @Apidoc\Param("reason", "string", "退款原因", false, "退款")
|
||||
* @Apidoc\Return("success", "object", "成功响应", "msg|string|成功信息")
|
||||
* @Apidoc\Return("error", "object", "失败响应", "code|int|错误码,msg|string|错误信息")
|
||||
* @param Request $request
|
||||
* @return Response
|
||||
*/
|
||||
public function unified_refund(Request $request): Response
|
||||
{
|
||||
try {
|
||||
$params = $request->post();
|
||||
|
||||
// 验证必要参数
|
||||
if (!isset($params['order_no']) || !isset($params['payment']) || !isset($params['amount'])) {
|
||||
return $this->error('缺少必要参数');
|
||||
}
|
||||
|
||||
// 设置pay_type参数
|
||||
$params['pay_type'] = $params['payment'];
|
||||
$request->withAddedHeader('pay_type', $params['payment']);
|
||||
|
||||
// 调用退款接口
|
||||
return $this->refund($request);
|
||||
|
||||
} catch (\Exception $e) {
|
||||
WebmanLog::error('统一退款接口失败: ' . $e->getMessage());
|
||||
return $this->error('退款失败: ' . $e->getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4,7 +4,6 @@ namespace app\api\controller;
|
||||
use app\model\Product as ProductModel;
|
||||
use app\model\ProductOrder as ProductOrderModel;
|
||||
use support\think\Db;
|
||||
use taoser\facade\Validate;
|
||||
use hg\apidoc\annotation as Apidoc;
|
||||
|
||||
/**
|
||||
|
||||
@@ -5,7 +5,6 @@ use app\model\ProductOrder as ProductOrderModel;
|
||||
use app\model\Product as ProductModel;
|
||||
use app\model\User as UserModel;
|
||||
use support\think\Db;
|
||||
use taoser\facade\Validate;
|
||||
use hg\apidoc\annotation as Apidoc;
|
||||
|
||||
/**
|
||||
|
||||
@@ -3,7 +3,6 @@ namespace app\api\controller;
|
||||
|
||||
use support\Request;
|
||||
use support\think\Db;
|
||||
use taoser\facade\Validate;
|
||||
use app\model\Recharge as RechargeModel;
|
||||
use app\model\User as UserModel;
|
||||
use hg\apidoc\annotation as Apidoc;
|
||||
@@ -114,7 +113,7 @@ class RechargeController extends BaseController{
|
||||
return $this->error(__('Failed to create recharge order, please try again later'));
|
||||
}
|
||||
}else{
|
||||
$sign = aesencode(json_encode($data));
|
||||
$sign = \support\Encrypt::aesencode(json_encode($data));
|
||||
return $this->success(__('successful'),[
|
||||
'order' => [
|
||||
'id' => 0,
|
||||
@@ -306,7 +305,7 @@ class RechargeController extends BaseController{
|
||||
* @Apidoc\NotDebug()
|
||||
*/
|
||||
public function notify(){
|
||||
$data = aesdecode(input('data'));
|
||||
$data = \support\Encrypt::aesdecode(input('data'));
|
||||
$data = json_decode($data,true);
|
||||
/** @var RechargeModel $vo */
|
||||
$vo = RechargeModel::where('id',$data['out_trade_no'])->find();
|
||||
|
||||
@@ -31,22 +31,23 @@ class TeamController extends BaseController{
|
||||
$user = \support\Jwt::getUserinfo();
|
||||
$user_id = $user['id'];
|
||||
$user= Hook('user.profile',$user);
|
||||
$team_ids = UserTeamModel::where('ancestor_id',$user_id)->where('depth','>',0)->column('descendant_id');
|
||||
//$team_ids = UserTeamModel::where('ancestor_id',$user_id)->where('depth','>',0)->column('descendant_id');
|
||||
|
||||
$result=[
|
||||
'total_count' => count($team_ids),//团队总人数
|
||||
'direct_total' => cache('team_direct_total_'.$user_id)??0,//直属团队人数
|
||||
'recharge_total' => cache('team_recharge_total_'.$user_id)??0,
|
||||
'withdrawl_total' => cache('team_withdrawl_total_'.$user_id)??0,
|
||||
'income_total' => cache('team_income_total_'.$user_id)??0,
|
||||
'today_income_total' => cache('user_today_income_total_'.$user_id)??0,
|
||||
'promotion_income_total' => cache('user_promotion_income_total_'.$user_id)??0,
|
||||
'consume_total' => Db::name('user_extend')->where('user_id',$user_id)->value('sales'),//cache('team_consume_total_'.$user_id)??0,//团队总业绩
|
||||
'user_sales_reward' => cache('user_sales_reward_'.$user_id)??0,//销售奖
|
||||
'user_output_reward' => cache('user_output_reward_'.$user_id)??0,//产值奖
|
||||
'user_withdrawl_reward' => cache('user_withdrawl_reward'.$user_id)??0,//提现奖
|
||||
'user' => $user[0],
|
||||
|
||||
'level' => $user['level'],
|
||||
'total_count' => cache_get('team_user_count_'.$user_id),//团队总人数
|
||||
'direct_total' => cache_get('team_direct_total_'.$user_id),//直属团队人数
|
||||
'vip_total' => cache_get('team_vip_total_'.$user_id),//旗下会员总数
|
||||
// 'recharge_total' => cache('team_recharge_total_'.$user_id)??0,
|
||||
// 'withdrawl_total' => cache('team_withdrawl_total_'.$user_id)??0,
|
||||
// 'income_total' => cache('team_income_total_'.$user_id)??0,
|
||||
// 'today_income_total' => cache('user_today_income_total_'.$user_id)??0,
|
||||
// 'promotion_income_total' => cache('user_promotion_income_total_'.$user_id)??0,
|
||||
// 'consume_total' => cache('team_consume_total_'.$user_id)??0,//团队总业绩
|
||||
// 'user_sales_reward' => cache('user_sales_reward_'.$user_id)??0,//销售奖
|
||||
// 'user_output_reward' => cache('user_output_reward_'.$user_id)??0,//产值奖
|
||||
// 'user_withdrawl_reward' => cache('user_withdrawl_reward'.$user_id)??0,//提现奖
|
||||
'user' => $user[0]
|
||||
];
|
||||
return $this->success(__('successful'),$result);
|
||||
|
||||
@@ -120,7 +121,7 @@ class TeamController extends BaseController{
|
||||
->join('user_extend ue', 'u.id = ue.user_id')
|
||||
->where('u.parent_id', $user['id'])
|
||||
//->where('ue.active', 1)
|
||||
->field('u.id, u.username,u.money,u.score,u.role_id, u.group,u.avatar, u.created_at')
|
||||
->field('u.id,u.userID, u.username,u.nickname,u.money,u.score,u.role_id,u.avatar, u.created_at')
|
||||
->order('u.created_at desc');
|
||||
if($kw){
|
||||
$model = $model->whereLike("u.username",'%'.$kw.'%');
|
||||
@@ -139,34 +140,22 @@ class TeamController extends BaseController{
|
||||
}else{
|
||||
$result = $model->paginate($limit);
|
||||
}
|
||||
$role_arr = [
|
||||
'0' => __('普通用户'),
|
||||
'1' => __('V1'),
|
||||
'2' => __('V2'),
|
||||
'3' => __('V3'),
|
||||
'4' => __('V4'),
|
||||
'5' => __('V5'),
|
||||
];
|
||||
$result = $result->toArray();
|
||||
foreach($result['data'] as $k=>$item){
|
||||
$result['data'][$k]['avatar'] = cdnurl($item['avatar'] ?: '/storage/avatar/default.png');
|
||||
$result['data'][$k]['recharge_total'] = cache('user_recharge_total_'.$item['id'])??0;
|
||||
$result['data'][$k]['withdrawl_total'] = cache('user_withdrawl_total_'.$item['id'])??0;
|
||||
$result['data'][$k]['withdrawl_reward'] = cache('user_withdrawl_reward_'.$item['id'])??0;
|
||||
$result['data'][$k]['income_total'] = cache('user_income_total_'.$item['id'])??0;
|
||||
$result['data'][$k]['consume_total'] = cache('user_consume_total_'.$item['id'])??0;
|
||||
$result['data'][$k]['play_count'] = cache('user_play_count_'.$item['id'])??0;
|
||||
//$result['data'][$k]['recharge_total'] = cache('user_recharge_total_'.$item['id'])??0;
|
||||
//$result['data'][$k]['withdrawl_total'] = cache('user_withdrawl_total_'.$item['id'])??0;
|
||||
//$result['data'][$k]['withdrawl_reward'] = cache('user_withdrawl_reward_'.$item['id'])??0;
|
||||
//$result['data'][$k]['income_total'] = cache('user_income_total_'.$item['id'])??0;
|
||||
//$result['data'][$k]['consume_total'] = cache('user_consume_total_'.$item['id'])??0;
|
||||
//$result['data'][$k]['created_at'] = date('Y-m-d H:i:s', $item['created_at']);
|
||||
$result['data'][$k]['total_count'] = UserTeamModel::where('ancestor_id',$item['id'])->where('status',1)->where('depth','>',0)->count('descendant_id');
|
||||
$result['data'][$k]['direct_total'] = cache('team_direct_total_'.$item['id'])??0;
|
||||
$result['data'][$k]['role'] = isset($role_arr[$item['role_id']]) ? $role_arr[$item['role_id']] : __('普通用户');
|
||||
//$result['data'][$k]['questionnaire_count'] = WorkRecordModel::where('user_id',$item['id'])->count('id');
|
||||
$result['data'][$k]['id'] = idEncode($item['id']);
|
||||
//return $item;
|
||||
}
|
||||
return $this->success(__('successful'),$result);
|
||||
}
|
||||
/**
|
||||
* @Apidoc\NotParse()
|
||||
* @Apidoc\NotDebug()
|
||||
* @Apidoc\Title("改变用户等级")
|
||||
* @Apidoc\Method("POST")
|
||||
* @Apidoc\Param("id", type="string",require=false, desc="ID")
|
||||
@@ -176,7 +165,7 @@ class TeamController extends BaseController{
|
||||
$user = \support\Jwt::getUser();
|
||||
$id = $request->post('id');
|
||||
$level = $request->post('level');
|
||||
$id = idDecode($id);
|
||||
$id = \support\Encrypt::userIDDecode($id);
|
||||
if(!$id || !$level){
|
||||
return $this->error(__('Invalid parameters'));
|
||||
}
|
||||
|
||||
@@ -113,6 +113,11 @@ class ThaliController extends BaseController{
|
||||
if($quantity == 12){
|
||||
$price = $thali->year_price;
|
||||
}
|
||||
//新开通
|
||||
$isNew=false;
|
||||
if(is_null($user->role_id)){
|
||||
$isNew = true;
|
||||
}
|
||||
//升级
|
||||
$isUpgrade=true;
|
||||
//续费
|
||||
@@ -145,14 +150,15 @@ class ThaliController extends BaseController{
|
||||
\app\model\User::score($user->id,-$amount,\app\enum\BalanceType::PURCHASE_ROLE,json_encode(['role_id'=>$role_id,'quantity'=>$quantity,'role_name'=>$thali->title]));
|
||||
|
||||
cache('user_rights_'.$user->id,null);
|
||||
//Hook('user.roleup', $user);
|
||||
// $data = [
|
||||
// 'role_id' => $role_id,
|
||||
// 'user_id' => $user->id,
|
||||
// 'parent_id' => $user->parent_id,
|
||||
// 'amount' => $amount,
|
||||
// ];
|
||||
// Hook('role.buy', $data);
|
||||
if($isNew){
|
||||
Hook('user.role_up', $user);
|
||||
}
|
||||
$data = [
|
||||
'role_id' => $role_id,
|
||||
'user_id' => $user->id,
|
||||
'amount' => $amount,
|
||||
];
|
||||
Hook('user.role_buy', $data);
|
||||
return $this->success(__('successful'),$user);
|
||||
}
|
||||
}
|
||||
@@ -6,7 +6,6 @@ use app\model\User as UserModel;
|
||||
use app\model\Realname as RealnameModel;
|
||||
use support\Request;
|
||||
use support\Response;
|
||||
use taoser\facade\Validate;
|
||||
use support\think\Db;
|
||||
use hg\apidoc\annotation as Apidoc;
|
||||
|
||||
@@ -32,7 +31,8 @@ class UserController extends BaseController{
|
||||
* @Apidoc\Desc("GET为获取用户信息,POST为修改数据")
|
||||
* @Apidoc\Param("nickname", type="string",require=true, desc="昵称")
|
||||
*/
|
||||
public function profile(){
|
||||
public function profile()
|
||||
{
|
||||
$data = \support\Jwt::getUser();
|
||||
if(Request()->method() == 'POST'){
|
||||
$nickname = input('nickname');
|
||||
@@ -61,6 +61,7 @@ class UserController extends BaseController{
|
||||
}
|
||||
return $this->success(__('successful'));
|
||||
}
|
||||
$data = \support\Jwt::getUserInfo($data);
|
||||
$data= Hook('user.profile',$data);
|
||||
return $this->success(__('successful'),$data[0]);
|
||||
}
|
||||
@@ -94,20 +95,46 @@ class UserController extends BaseController{
|
||||
* @Apidoc\Param("password", type="string",require=true, desc="旧密码(新设时可用为空)")
|
||||
* @Apidoc\Param("newpassword", type="string",require=true, desc="新密码")
|
||||
* @Apidoc\Param("renewpassword", type="string",require=true, desc="新密码")
|
||||
* @Apidoc\Param("code", type="string",require=true, desc="验证码")
|
||||
* @Apidoc\Param("verify_type", type="string",require=true, desc="验证方式,email,mobile,password")
|
||||
*/
|
||||
public function change_trade_password(){
|
||||
$user = \support\Jwt::getUser();
|
||||
$password = input('password');
|
||||
$newpassword = input('newpassword');
|
||||
$renewpassword = input('renewpassword');
|
||||
$verify_type = input('verify_type');
|
||||
if (!$newpassword || !$renewpassword || $newpassword !== $renewpassword) {
|
||||
return $this->error(__('Invalid parameters'));
|
||||
}
|
||||
try{
|
||||
\support\Jwt::change_trade_pwd($newpassword,$password);
|
||||
return $this->success(__('Reset trade password successful'));
|
||||
} catch (\Throwable $e) {
|
||||
return $this->error($e->getMessage());
|
||||
if($verify_type == 'email'){
|
||||
captcha_verify('email','reset_trade_pwd',$user->email);
|
||||
try{
|
||||
\support\Jwt::change_trade_pwd($newpassword,'',true);
|
||||
return $this->success(__('Reset trade password successful'));
|
||||
} catch (\Throwable $e) {
|
||||
return $this->error($e->getMessage());
|
||||
}
|
||||
}else if($verify_type == 'mobile'){
|
||||
captcha_verify('mobile','reset_trade_pwd',$user->mobile);
|
||||
try{
|
||||
\support\Jwt::change_trade_pwd($newpassword,'',true);
|
||||
return $this->success(__('Reset trade password successful'));
|
||||
} catch (\Throwable $e) {
|
||||
return $this->error($e->getMessage());
|
||||
}
|
||||
}else if($verify_type == 'password'){
|
||||
if (!$password) {
|
||||
return $this->error(__('Invalid parameters'));
|
||||
}
|
||||
try{
|
||||
\support\Jwt::change_trade_pwd($newpassword,$password);
|
||||
return $this->success(__('Reset trade password successful'));
|
||||
} catch (\Throwable $e) {
|
||||
return $this->error($e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
/**
|
||||
* 根据关键字查询用户列表
|
||||
@@ -148,6 +175,25 @@ class UserController extends BaseController{
|
||||
\support\Jwt::getUser()->save($data);
|
||||
return $this->success(__('successful'),$data);
|
||||
}
|
||||
/**
|
||||
* 设置个人banner
|
||||
* @Apidoc\Method("POST")
|
||||
* @Apidoc\Param("file", type="File", require=true, desc="文件")
|
||||
*/
|
||||
public function setBanner(Request $request)
|
||||
{
|
||||
$user_id = \support\Jwt\JwtToken::getCurrentId();
|
||||
//单文件上传
|
||||
$res = $this->_upload($request);
|
||||
if(is_string($res)){
|
||||
return $this->fail( $res);
|
||||
}
|
||||
$data = [
|
||||
'profile_banner' => $res[0]['file_name'],
|
||||
];
|
||||
Db::name('user_extend')->where('user_id',$user_id)->save($data);
|
||||
return $this->success(__('successful'),$data);
|
||||
}
|
||||
function realname(Request $request): Response
|
||||
{
|
||||
/**
|
||||
@@ -160,9 +206,8 @@ class UserController extends BaseController{
|
||||
'idcard' => Input('idcard'),
|
||||
'user_id' => $user->id,
|
||||
];
|
||||
log_alert($data);
|
||||
if(!$data['realname'] || !$data['idcard']){
|
||||
return $this->error(__('Incoret param'));
|
||||
return $this->error(__('Invalid parameter'));
|
||||
}
|
||||
if($user->realname_verify == 1){
|
||||
return $this->error(__('You have verified'));
|
||||
@@ -201,31 +246,17 @@ class UserController extends BaseController{
|
||||
if(is_string($ids)){
|
||||
$ids = explode(',',$ids);
|
||||
}
|
||||
$userIDs = array_map('idDecode',$ids);
|
||||
//$userIDs = array_map('\support\Encrypt::userIDDecode',$ids);
|
||||
//$res = $request->IM->user->getUsersInfo($userIDs);
|
||||
$list = Db::name('user')->
|
||||
whereIn('id',$userIDs)
|
||||
$list = Db::name('user')->alias('u')
|
||||
->leftJoin('user_extend ue','ue.user_id=u.id')
|
||||
->field('u.*,ue.profile_banner')
|
||||
->whereIn('u.userID',$ids)
|
||||
->paginate(Input('limit',10));
|
||||
$list->each(function($user){
|
||||
unset($user['password']);
|
||||
unset($user['trade_password']);
|
||||
//unset($user['avatar']);
|
||||
unset($user['online']);
|
||||
unset($user['token']);
|
||||
unset($user['prev_time']);
|
||||
unset($user['loginfailure']);
|
||||
unset($user['successions']);
|
||||
unset($user['maxsuccessions']);
|
||||
unset($user['currency1']);
|
||||
unset($user['currency2']);
|
||||
unset($user['currency3']);
|
||||
unset($user['currency4']);
|
||||
unset($user['currency5']);
|
||||
unset($user['currency6']);
|
||||
unset($user['currency7']);
|
||||
unset($user['currency8']);
|
||||
unset($user['currency9']);
|
||||
return $user;
|
||||
$data = \support\Jwt::getUserInfo($user);
|
||||
$data= Hook('user.profile',$data);
|
||||
return $data[0];
|
||||
//$user->hidden(['password']);
|
||||
});
|
||||
|
||||
@@ -241,9 +272,12 @@ class UserController extends BaseController{
|
||||
{
|
||||
$keyword = Input('keyword');
|
||||
$searchtype = Input('searchtype');
|
||||
$fields = 'id,avatar,username,nickname,avatar,sex,email,mobile,birthday,bio';
|
||||
$model = Db::name('user')->field($fields)->where('status',1);
|
||||
$model = $model->where('id',idDecode($keyword));
|
||||
$fields = 'u.userID,u.avatar,u.username,u.nickname,u.avatar,u.sex,u.email,u.mobile,u.birthday,u.bio,ue.profile_banner';
|
||||
$model = Db::name('user')->alias('u')
|
||||
->join('user_extend ue','ue.user_id=u.id')
|
||||
->field($fields)
|
||||
->where('status',1);
|
||||
$model = $model->where('u.userID',$keyword);
|
||||
// if($searchtype =='id'){
|
||||
// $model = $model->where('id',$keyword);
|
||||
// }else{
|
||||
@@ -251,10 +285,9 @@ class UserController extends BaseController{
|
||||
// }
|
||||
$list = $model->paginate(Input('limit',10));
|
||||
$list->each(function ($item){
|
||||
$item['id'] = idEncode($item['id']);
|
||||
$item['id'] = $item['userID'];
|
||||
return $item;
|
||||
});
|
||||
//log_alert($list->toArray());
|
||||
return $this->success('ok',$list);
|
||||
}
|
||||
}
|
||||
@@ -3,7 +3,6 @@ namespace app\api\controller;
|
||||
use app\model\User as UserModel;
|
||||
use support\Request;
|
||||
use app\model\Cdkey as CdkeyModel;
|
||||
use taoser\facade\Validate;
|
||||
use support\think\Db;
|
||||
use hg\apidoc\annotation as Apidoc;
|
||||
|
||||
@@ -35,7 +34,7 @@ class WalletController extends BaseController{
|
||||
//return $this->error(__('The system is under maintenance, please wait...'));
|
||||
$user = \support\Jwt\JwtToken::getUser();
|
||||
// if(Config('site.trade_password_type') == 'email'){
|
||||
// captcha_verfiy('email','exchange',$user['username']);
|
||||
// captcha_verify('email','exchange',$user['username']);
|
||||
// }else{
|
||||
// $trade_password = input('trade_password');
|
||||
// \support\Jwt::verify_trade_password($trade_password);
|
||||
@@ -88,7 +87,7 @@ class WalletController extends BaseController{
|
||||
if(str_contains($username,'@')){
|
||||
$to_user = UserModel::where('username',$username)->find();
|
||||
}else{
|
||||
$to_user_id = idDecode($username);
|
||||
$to_user_id = \support\Encrypt::userIDDecode($username);
|
||||
$to_user = UserModel::where('id',$to_user_id)->find();
|
||||
}
|
||||
|
||||
@@ -96,7 +95,7 @@ class WalletController extends BaseController{
|
||||
return $this->error(__('User is incorrect'));
|
||||
}
|
||||
if(Config('site.trade_password_type') == 'email'){
|
||||
//captcha_verfiy('email','transfer',$to_user['username']);
|
||||
//captcha_verify('email','transfer',$to_user['username']);
|
||||
}else{
|
||||
$trade_password = input('trade_password');
|
||||
\support\Jwt::verify_trade_password($trade_password);
|
||||
@@ -151,7 +150,7 @@ class WalletController extends BaseController{
|
||||
//return $this->error(__('The system is under maintenance, please wait...'));
|
||||
$user = \support\Jwt\JwtToken::getUser();
|
||||
// if(Config('site.trade_password_type') == 'email'){
|
||||
// captcha_verfiy('email','exchange',$user['username']);
|
||||
// captcha_verify('email','exchange',$user['username']);
|
||||
// }else{
|
||||
// $trade_password = input('trade_password');
|
||||
// \support\Jwt::verify_trade_password($trade_password);
|
||||
|
||||
@@ -6,7 +6,6 @@ use app\model\User as UserModel;
|
||||
use app\model\Withdrawl as WithdrawlModel;
|
||||
use support\Request;
|
||||
use support\think\Db;
|
||||
use taoser\facade\Validate;
|
||||
use hg\apidoc\annotation as Apidoc;
|
||||
|
||||
/**
|
||||
@@ -71,7 +70,7 @@ class WithdrawlController extends BaseController{
|
||||
{
|
||||
//return $this->error(__('The system is under maintenance, please wait...'));
|
||||
//* @Apidoc\Param("code", type="string", require=true, desc="图形验证码(type=withdrawl)")
|
||||
//captcha_verfiy('image','withdrawl');
|
||||
//captcha_verify('image','withdrawl');
|
||||
$address_id = input('address_id');
|
||||
if(!$address_id){
|
||||
return $this->error(__('Address is incorrect'));
|
||||
@@ -86,7 +85,7 @@ class WithdrawlController extends BaseController{
|
||||
// }
|
||||
$user = \support\Jwt::getUser();
|
||||
if(Config('site.trade_password_type') == 'email'){
|
||||
captcha_verfiy('email','withdrawl',$user['username']);
|
||||
captcha_verify('email','withdrawl',$user['username']);
|
||||
}else{
|
||||
//验证交易密码
|
||||
$trade_password = input('trade_password');
|
||||
@@ -163,7 +162,7 @@ class WithdrawlController extends BaseController{
|
||||
* @Apidoc\NotDebug()
|
||||
*/
|
||||
public function notify(){
|
||||
$data = aesdecode(input('data',''));
|
||||
$data = \support\Encrypt::aesdecode(input('data',''));
|
||||
$data = json_decode($data,true);
|
||||
/** @var WithdrawlModel $vo */
|
||||
$vo = WithdrawlModel::where('id',$data['out_trade_no'])->find();
|
||||
|
||||
@@ -29,7 +29,7 @@ class Auth implements MiddlewareInterface
|
||||
$response = response('',204,$headers);
|
||||
return $response;
|
||||
}
|
||||
$lang = $request->input('lang','zh-Hans');
|
||||
$lang = $request->header('lang','zh-Hans');
|
||||
locale($lang);
|
||||
if ($request->controller) {
|
||||
$request->client = $request->header('client',"web");
|
||||
@@ -107,7 +107,7 @@ class Auth implements MiddlewareInterface
|
||||
// $data = str_replace('%3D','=',$data);
|
||||
// $data = str_replace(' ','+',$data);
|
||||
// //var_dump($data);
|
||||
// $data = aesdecode($data);
|
||||
// $data = \support\Encrypt::aesdecode($data);
|
||||
// $data = json_decode($data,true);
|
||||
// //var_dump($data);
|
||||
// $request->withBody($data);
|
||||
@@ -122,8 +122,8 @@ class Auth implements MiddlewareInterface
|
||||
'config' => $config
|
||||
]);
|
||||
$IM = new \support\OpenImSdk\Client([
|
||||
'host' => 'http://127.0.0.1:10002', // OpenIM API地址
|
||||
'secret' => 'n1e5a6s6m7', // OpenIM密钥
|
||||
'host' => config('openim.server'), // OpenIM API地址
|
||||
'secret' => config('openim.secret'), // OpenIM密钥
|
||||
]);
|
||||
$request->IM = $IM;
|
||||
$response = $next($request);
|
||||
@@ -135,7 +135,7 @@ class Auth implements MiddlewareInterface
|
||||
request()->path()
|
||||
],$response->rawBody());
|
||||
// if($request->app=="api" && $request->client!='web'){
|
||||
// $body = aesencode($body);
|
||||
// $body = \support\Encrypt::aesencode($body);
|
||||
// }
|
||||
$response->withHeaders($headers)->withBody($body)->getStatusCode();
|
||||
$time = microtime() - $request->start_time;
|
||||
|
||||
@@ -0,0 +1,807 @@
|
||||
<?php
|
||||
namespace app\command;
|
||||
|
||||
use Symfony\Component\Console\Command\Command;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
use Symfony\Component\Console\Input\InputOption;
|
||||
|
||||
class Backup extends Command
|
||||
{
|
||||
protected static $defaultName = 'backup';
|
||||
protected static $defaultDescription = '备份 MongoDB 和 MySQL 数据库';
|
||||
|
||||
// 数据源配置
|
||||
private $dataSources = [
|
||||
[
|
||||
'type' => 'mongodb',
|
||||
'host' => '127.0.0.1',
|
||||
'port' => 27017,
|
||||
'database' => 'openim_v3',
|
||||
'username' => 'openIM',
|
||||
'password' => 'n1e5a6s6m7',
|
||||
'useDocker' => true,
|
||||
'dockerContainerName' => 'mongo' // Docker 容器名称
|
||||
],
|
||||
[
|
||||
'type' => 'mysql',
|
||||
'host' => '127.0.0.1',
|
||||
'port' => 3306,
|
||||
'database' => 'imadmin',
|
||||
'username' => 'root',
|
||||
'password' => 'n1e5a6s6m7',
|
||||
'useDocker' => true,
|
||||
'dockerContainerName' => 'my_mysql' // Docker 容器名称
|
||||
],
|
||||
[
|
||||
'type' => 'redis',
|
||||
'host' => '127.0.0.1',
|
||||
'port' => 16379,
|
||||
'database' => 0,
|
||||
'username' => '',
|
||||
'password' => 'n1e5a6s6m7',
|
||||
'useDocker' => true,
|
||||
'dockerContainerName' => 'redis' // Docker 容器名称
|
||||
],
|
||||
[
|
||||
'name' => 'tettt_mongodb',
|
||||
'type' => 'mongodb',
|
||||
'host' => '127.0.0.1',
|
||||
'port' => 27017,
|
||||
'database' => 'tettt',
|
||||
'username' => 'commie',
|
||||
'password' => 'n1e5a6s6m7',
|
||||
'authSource' => 'admin',
|
||||
'useDocker' => true,
|
||||
'dockerContainerName' => 'mongo'
|
||||
],
|
||||
];
|
||||
|
||||
protected function configure()
|
||||
{
|
||||
$this->addOption('backup', 'b', InputOption::VALUE_NONE, '备份数据库');
|
||||
$this->addOption('restore', 'r', InputOption::VALUE_NONE, '还原数据库');
|
||||
$this->addOption('clear', 'c', InputOption::VALUE_NONE, '清空 Redis');
|
||||
$this->addOption('source', 's', InputOption::VALUE_OPTIONAL, '数据源名称 (mongodb, mysql, redis)');
|
||||
$this->addOption('output', 'o', InputOption::VALUE_OPTIONAL, '备份输出目录', '/backup');
|
||||
}
|
||||
|
||||
protected function execute(InputInterface $input, OutputInterface $output): int
|
||||
{
|
||||
$backup = $input->getOption('backup');
|
||||
$restore = $input->getOption('restore');
|
||||
$clear = $input->getOption('clear');
|
||||
$source = $input->getOption('source');
|
||||
$outputDir = $input->getOption('output');
|
||||
|
||||
// 确保备份目录存在
|
||||
$mongoDir = base_path($outputDir) . '/mongo';
|
||||
$mysqlDir = base_path($outputDir) . '/mysql';
|
||||
|
||||
if (!is_dir($mongoDir)) {
|
||||
mkdir($mongoDir, 0755, true);
|
||||
}
|
||||
|
||||
if (!is_dir($mysqlDir)) {
|
||||
mkdir($mysqlDir, 0755, true);
|
||||
}
|
||||
|
||||
// 显示备份目录
|
||||
$output->writeln("\n备份目录:");
|
||||
$output->writeln("- MongoDB: {$mongoDir}");
|
||||
$output->writeln("- MySQL: {$mysqlDir}");
|
||||
|
||||
// 显示环境配置
|
||||
$output->writeln("\n环境配置:");
|
||||
foreach ($this->dataSources as $_source) {
|
||||
$name = $_source['name'] ?? $_source['dockerContainerName'] ?? ucfirst($_source['type']);
|
||||
if($name == $source){
|
||||
$source = $_source;
|
||||
}
|
||||
$output->writeln("- {$name}: " . ($_source['useDocker'] ? "Docker 容器" : "本地环境"));
|
||||
}
|
||||
|
||||
// 处理命令行选项
|
||||
if ($backup) {
|
||||
if(is_array($source)){
|
||||
if ($source['type'] === 'mongodb') {
|
||||
$this->backupMongoDB($mongoDir, $output, $source);
|
||||
}
|
||||
if ($source['type'] === 'mysql') {
|
||||
$this->backupMySQL($mysqlDir, $output, $source);
|
||||
}
|
||||
} else {
|
||||
foreach ($this->dataSources as $_source) {
|
||||
if ($_source['type'] === 'mongodb') {
|
||||
$this->backupMongoDB($mongoDir, $output, $_source);
|
||||
} elseif ($_source['type'] === 'mysql') {
|
||||
$this->backupMySQL($mysqlDir, $output, $_source);
|
||||
}
|
||||
}
|
||||
}
|
||||
} elseif ($restore) {
|
||||
if(is_array($source)){
|
||||
if ($source['type'] === 'mongodb') {
|
||||
$this->restoreMongoDB($mongoDir, $output, $source);
|
||||
}
|
||||
if ($source['type'] === 'mysql') {
|
||||
$this->restoreMySQL($mysqlDir, $output, $source);
|
||||
}
|
||||
} else {
|
||||
$this->restoreMenu($output, $outputDir);
|
||||
}
|
||||
} elseif ($clear) {
|
||||
$this->clearRedis($output);
|
||||
} else {
|
||||
$this->mainMenu($output, $outputDir);
|
||||
}
|
||||
|
||||
$output->writeln("\n✅ 操作完成!");
|
||||
|
||||
return self::SUCCESS;
|
||||
}
|
||||
|
||||
/**
|
||||
* 主菜单
|
||||
*/
|
||||
private function mainMenu($output, $outputDir): void
|
||||
{
|
||||
while (true) {
|
||||
$output->writeln("\n================================");
|
||||
$output->writeln(" 备份工具");
|
||||
$output->writeln("================================");
|
||||
$output->writeln("1. 备份数据库");
|
||||
$output->writeln("2. 还原数据库");
|
||||
$output->writeln("3. 清空 Redis");
|
||||
$output->writeln("0. 退出");
|
||||
|
||||
$output->write("\n请选择操作 (0-3): ");
|
||||
$handle = fopen("php://stdin", "r");
|
||||
$choice = fgets($handle);
|
||||
fclose($handle);
|
||||
$choice = trim($choice);
|
||||
|
||||
switch ($choice) {
|
||||
case '1':
|
||||
$this->backupMenu($output, $outputDir);
|
||||
break;
|
||||
case '2':
|
||||
$this->restoreMenu($output, $outputDir);
|
||||
break;
|
||||
case '3':
|
||||
$this->clearRedis($output);
|
||||
break;
|
||||
case '0':
|
||||
return;
|
||||
default:
|
||||
$output->writeln("\n无效选择,请重新输入");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 备份菜单
|
||||
*/
|
||||
private function backupMenu($output, $outputDir): void
|
||||
{
|
||||
$output->writeln("\n================================");
|
||||
$output->writeln(" 备份数据库");
|
||||
$output->writeln("================================");
|
||||
|
||||
foreach ($this->dataSources as $index => $source) {
|
||||
if ($source['type'] !== 'redis') {
|
||||
$name = $source['name'] ?? $source['dockerContainerName'] ?? ucfirst($source['type']);
|
||||
$output->writeln(($index + 1) . ". " . $name . " (" . ($source['useDocker'] ? "Docker 容器" : "本地环境") . ")");
|
||||
}
|
||||
}
|
||||
|
||||
$output->writeln("0. 返回上一级");
|
||||
|
||||
$output->write("\n请选择要备份的数据源 (0-" . count($this->dataSources) . "): ");
|
||||
$handle = fopen("php://stdin", "r");
|
||||
$choice = fgets($handle);
|
||||
fclose($handle);
|
||||
$choice = trim($choice);
|
||||
|
||||
if ($choice === '0') {
|
||||
return;
|
||||
}
|
||||
|
||||
if (is_numeric($choice) && $choice > 0 && $choice <= count($this->dataSources)) {
|
||||
$source = $this->dataSources[$choice - 1];
|
||||
|
||||
if ($source['type'] === 'mongodb') {
|
||||
$mongoDir = base_path($outputDir) . '/mongo';
|
||||
if (!is_dir($mongoDir)) {
|
||||
mkdir($mongoDir, 0755, true);
|
||||
}
|
||||
$this->backupMongoDB($mongoDir, $output, $source);
|
||||
} elseif ($source['type'] === 'mysql') {
|
||||
$mysqlDir = base_path($outputDir) . '/mysql';
|
||||
if (!is_dir($mysqlDir)) {
|
||||
mkdir($mysqlDir, 0755, true);
|
||||
}
|
||||
$this->backupMySQL($mysqlDir, $output, $source);
|
||||
}
|
||||
} else {
|
||||
$output->writeln("\n无效选择,请重新输入");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 还原菜单
|
||||
*/
|
||||
private function restoreMenu($output, $outputDir): void
|
||||
{
|
||||
$output->writeln("\n================================");
|
||||
$output->writeln(" 还原数据库");
|
||||
$output->writeln("================================");
|
||||
|
||||
foreach ($this->dataSources as $index => $source) {
|
||||
if ($source['type'] !== 'redis') {
|
||||
$name = $source['name'] ?? $source['dockerContainerName'] ?? ucfirst($source['type']);
|
||||
$output->writeln(($index + 1) . ". " . $name . " (" . ($source['useDocker'] ? "Docker 容器" : "本地环境") . ")");
|
||||
}
|
||||
}
|
||||
|
||||
$output->writeln("0. 返回上一级");
|
||||
|
||||
$output->write("\n请选择要还原的数据源 (0-" . count($this->dataSources) . "): ");
|
||||
$handle = fopen("php://stdin", "r");
|
||||
$choice = fgets($handle);
|
||||
fclose($handle);
|
||||
$choice = trim($choice);
|
||||
|
||||
if ($choice === '0') {
|
||||
return;
|
||||
}
|
||||
|
||||
if (is_numeric($choice) && $choice > 0 && $choice <= count($this->dataSources)) {
|
||||
$source = $this->dataSources[$choice - 1];
|
||||
|
||||
if ($source['type'] === 'mongodb') {
|
||||
$mongoDir = base_path($outputDir) . '/mongo';
|
||||
if (!is_dir($mongoDir)) {
|
||||
mkdir($mongoDir, 0755, true);
|
||||
}
|
||||
$this->restoreMongoDB($mongoDir, $output, $source);
|
||||
} elseif ($source['type'] === 'mysql') {
|
||||
$mysqlDir = base_path($outputDir) . '/mysql';
|
||||
if (!is_dir($mysqlDir)) {
|
||||
mkdir($mysqlDir, 0755, true);
|
||||
}
|
||||
$this->restoreMySQL($mysqlDir, $output, $source);
|
||||
}
|
||||
} else {
|
||||
$output->writeln("\n无效选择,请重新输入");
|
||||
}
|
||||
}
|
||||
|
||||
private function backupMongoDB($backupDir, $output, $dataSource = null): void
|
||||
{
|
||||
$name = $dataSource['name'] ?? $dataSource['dockerContainerName'] ?? ucfirst($dataSource['type']);
|
||||
$output->writeln("\n开始备份 {$name}...");
|
||||
|
||||
try {
|
||||
$mongoSource = $dataSource;
|
||||
if (!$mongoSource) {
|
||||
foreach ($this->dataSources as $source) {
|
||||
if ($source['type'] === 'mongodb') {
|
||||
$mongoSource = $source;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!$mongoSource) {
|
||||
$output->writeln("❌ 未找到 MongoDB 数据源配置");
|
||||
return;
|
||||
}
|
||||
|
||||
$host = $mongoSource['host'];
|
||||
$port = $mongoSource['port'];
|
||||
$database = $mongoSource['database'];
|
||||
$useDocker = $mongoSource['useDocker'];
|
||||
$dockerContainerName = $mongoSource['dockerContainerName'];
|
||||
$username = $mongoSource['username'] ?? '';
|
||||
$password = $mongoSource['password'] ?? '';
|
||||
$authSource = $mongoSource['authSource'] ?? $database;
|
||||
$username = $mongoSource['username'] ?? '';
|
||||
$password = $mongoSource['password'] ?? '';
|
||||
$authSource = $mongoSource['authSource'] ?? $database;
|
||||
|
||||
$backupFileName = "{$database}_" . date("Y_m_d_H_i_s") . ".zip";
|
||||
$backupFilePath = "{$backupDir}/{$backupFileName}";
|
||||
|
||||
$tempDir = "/tmp/mongo_backup_" . uniqid();
|
||||
if (!is_dir($tempDir)) {
|
||||
mkdir($tempDir, 0755, true);
|
||||
}
|
||||
|
||||
$cmd = $this->getMongoDumpCommand($host, $port, $database, $tempDir, $useDocker, $dockerContainerName, $username, $password, $authSource);
|
||||
$output->writeln("执行命令: {$cmd}");
|
||||
|
||||
exec($cmd, $outputLines, $returnCode);
|
||||
|
||||
if ($returnCode === 0) {
|
||||
// 保存当前工作目录
|
||||
$currentDir = getcwd();
|
||||
|
||||
// 切换到临时目录并压缩
|
||||
chdir($tempDir);
|
||||
$zipCmd = "zip -r {$backupFilePath} .";
|
||||
$output->writeln("创建压缩文件: {$backupFilePath}");
|
||||
exec($zipCmd, $zipOutput, $zipReturnCode);
|
||||
|
||||
// 切换回原来的工作目录
|
||||
chdir($currentDir);
|
||||
|
||||
if ($zipReturnCode === 0) {
|
||||
$output->writeln("✅ MongoDB 备份成功: {$backupFilePath}");
|
||||
} else {
|
||||
$output->writeln("❌ MongoDB 压缩失败");
|
||||
$output->writeln(implode("\n", $zipOutput));
|
||||
}
|
||||
|
||||
// 清理临时目录
|
||||
exec("rm -rf {$tempDir}");
|
||||
} else {
|
||||
$output->writeln("❌ MongoDB 备份失败");
|
||||
$output->writeln(implode("\n", $outputLines));
|
||||
// 清理临时目录
|
||||
exec("rm -rf {$tempDir}");
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
$output->writeln("❌ MongoDB 备份失败: " . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
private function restoreMongoDB($backupDir, $output, $dataSource = null): void
|
||||
{
|
||||
$name = $dataSource['name'] ?? $dataSource['dockerContainerName'] ?? ucfirst($dataSource['type']);
|
||||
$output->writeln("\n开始还原 {$name}...");
|
||||
|
||||
try {
|
||||
$mongoSource = $dataSource;
|
||||
if (!$mongoSource) {
|
||||
foreach ($this->dataSources as $source) {
|
||||
if ($source['type'] === 'mongodb') {
|
||||
$mongoSource = $source;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!$mongoSource) {
|
||||
$output->writeln("❌ 未找到 MongoDB 数据源配置");
|
||||
return;
|
||||
}
|
||||
|
||||
$host = $mongoSource['host'];
|
||||
$port = $mongoSource['port'];
|
||||
$database = $mongoSource['database'];
|
||||
$useDocker = $mongoSource['useDocker'];
|
||||
$dockerContainerName = $mongoSource['dockerContainerName'];
|
||||
$username = $mongoSource['username'] ?? '';
|
||||
$password = $mongoSource['password'] ?? '';
|
||||
$authSource = $mongoSource['authSource'] ?? $database;
|
||||
|
||||
$backupFiles = glob("{$backupDir}/*.zip");
|
||||
if (empty($backupFiles)) {
|
||||
$output->writeln("❌ 未找到备份文件");
|
||||
return;
|
||||
}
|
||||
|
||||
// 按修改时间排序
|
||||
usort($backupFiles, function ($a, $b) {
|
||||
return filemtime($b) - filemtime($a);
|
||||
});
|
||||
|
||||
// 显示备份文件列表
|
||||
$output->writeln("\n可用的备份文件:");
|
||||
foreach ($backupFiles as $index => $file) {
|
||||
$fileName = basename($file);
|
||||
$fileSize = filesize($file) / 1024 / 1024;
|
||||
$modTime = date("Y-m-d H:i:s", filemtime($file));
|
||||
$output->writeln(($index + 1) . ". {$fileName} (" . round($fileSize, 2) . " MB, {$modTime})");
|
||||
}
|
||||
|
||||
// 选择备份文件
|
||||
$output->write("\n请选择要还原的备份文件 (1-" . count($backupFiles) . "): ");
|
||||
$handle = fopen("php://stdin", "r");
|
||||
$choice = fgets($handle);
|
||||
fclose($handle);
|
||||
$choice = trim($choice);
|
||||
|
||||
if (!is_numeric($choice) || $choice < 1 || $choice > count($backupFiles)) {
|
||||
$output->writeln("\n无效选择");
|
||||
return;
|
||||
}
|
||||
|
||||
$selectedFile = $backupFiles[$choice - 1];
|
||||
$output->writeln("\n选择的备份文件: " . basename($selectedFile));
|
||||
|
||||
// 生成临时还原目录
|
||||
$tempDir = "/tmp/mongo_restore_" . uniqid();
|
||||
if (!is_dir($tempDir)) {
|
||||
mkdir($tempDir, 0755, true);
|
||||
}
|
||||
|
||||
// 解压备份文件
|
||||
$unzipCmd = "unzip {$selectedFile} -d {$tempDir}";
|
||||
$output->writeln("解压备份文件...");
|
||||
exec($unzipCmd, $unzipOutput, $unzipReturnCode);
|
||||
|
||||
if ($unzipReturnCode === 0) {
|
||||
$dbRestoreDir = $tempDir;
|
||||
$backupDbName = null;
|
||||
$subDirs = glob("{$tempDir}/*", GLOB_ONLYDIR);
|
||||
|
||||
$output->writeln("解压后的目录: " . implode(", ", array_map('basename', $subDirs)));
|
||||
|
||||
if (!empty($subDirs)) {
|
||||
foreach ($subDirs as $subDir) {
|
||||
$bsonFiles = glob("{$subDir}/*.bson");
|
||||
if (!empty($bsonFiles)) {
|
||||
$dbRestoreDir = $subDir;
|
||||
$backupDbName = basename($subDir);
|
||||
$output->writeln("找到备份目录: {$backupDbName}");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$bsonFiles = glob("{$dbRestoreDir}/*.bson");
|
||||
if (empty($bsonFiles)) {
|
||||
$output->writeln("❌ 备份文件中没有找到 BSON 数据文件");
|
||||
$output->writeln("目录内容: " . implode(", ", scandir($dbRestoreDir)));
|
||||
exec("rm -rf {$tempDir}");
|
||||
return;
|
||||
}
|
||||
|
||||
$output->writeln("找到 " . count($bsonFiles) . " 个 BSON 文件");
|
||||
$output->writeln("还原目录: {$dbRestoreDir}");
|
||||
$output->writeln("备份数据库: {$backupDbName} -> 目标数据库: {$database}");
|
||||
|
||||
$cmd = $this->getMongoRestoreCommand($host, $port, $database, $dbRestoreDir, $useDocker, $dockerContainerName, $username, $password, $authSource);
|
||||
$output->writeln("执行命令: {$cmd}");
|
||||
|
||||
exec($cmd, $outputLines, $returnCode);
|
||||
|
||||
if ($returnCode === 0) {
|
||||
$output->writeln("✅ MongoDB 还原成功");
|
||||
if (!empty($outputLines)) {
|
||||
$output->writeln(implode("\n", $outputLines));
|
||||
}
|
||||
} else {
|
||||
$output->writeln("❌ MongoDB 还原失败");
|
||||
$output->writeln(implode("\n", $outputLines));
|
||||
}
|
||||
|
||||
exec("rm -rf {$tempDir}");
|
||||
} else {
|
||||
$output->writeln("❌ 解压备份文件失败");
|
||||
$output->writeln(implode("\n", $unzipOutput));
|
||||
exec("rm -rf {$tempDir}");
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
$output->writeln("❌ MongoDB 还原失败: " . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
private function backupMySQL($backupDir, $output, $dataSource = null): void
|
||||
{
|
||||
$name = $dataSource['name'] ?? $dataSource['dockerContainerName'] ?? ucfirst($dataSource['type']);
|
||||
$output->writeln("\n开始备份 {$name}...");
|
||||
|
||||
try {
|
||||
$mysqlSource = $dataSource;
|
||||
if (!$mysqlSource) {
|
||||
foreach ($this->dataSources as $source) {
|
||||
if ($source['type'] === 'mysql') {
|
||||
$mysqlSource = $source;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!$mysqlSource) {
|
||||
$output->writeln("❌ 未找到 MySQL 数据源配置");
|
||||
return;
|
||||
}
|
||||
|
||||
$host = $mysqlSource['host'];
|
||||
$port = $mysqlSource['port'];
|
||||
$database = $mysqlSource['database'];
|
||||
$username = $mysqlSource['username'];
|
||||
$password = $mysqlSource['password'];
|
||||
$useDocker = $mysqlSource['useDocker'];
|
||||
$dockerContainerName = $mysqlSource['dockerContainerName'];
|
||||
|
||||
// 生成备份文件名
|
||||
$backupFileName = "{$database}_" . date("Y_m_d_H_i_s") . ".sql";
|
||||
$backupFilePath = "{$backupDir}/{$backupFileName}";
|
||||
|
||||
// 构建备份命令
|
||||
$cmd = $this->getMySqlDumpCommand($host, $port, $database, $username, $password, $backupFilePath, $useDocker, $dockerContainerName);
|
||||
$output->writeln("执行命令: {$cmd}");
|
||||
|
||||
// 执行备份命令
|
||||
exec($cmd, $outputLines, $returnCode);
|
||||
|
||||
if ($returnCode === 0) {
|
||||
$output->writeln("✅ MySQL 备份成功: {$backupFilePath}");
|
||||
} else {
|
||||
$output->writeln("❌ MySQL 备份失败");
|
||||
$output->writeln(implode("\n", $outputLines));
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
$output->writeln("❌ MySQL 备份失败: " . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
private function restoreMySQL($backupDir, $output, $dataSource = null): void
|
||||
{
|
||||
$name = $dataSource['name'] ?? $dataSource['dockerContainerName'] ?? ucfirst($dataSource['type']);
|
||||
$output->writeln("\n开始还原 {$name}...");
|
||||
|
||||
try {
|
||||
$mysqlSource = $dataSource;
|
||||
if (!$mysqlSource) {
|
||||
foreach ($this->dataSources as $source) {
|
||||
if ($source['type'] === 'mysql') {
|
||||
$mysqlSource = $source;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!$mysqlSource) {
|
||||
$output->writeln("❌ 未找到 MySQL 数据源配置");
|
||||
return;
|
||||
}
|
||||
|
||||
$host = $mysqlSource['host'];
|
||||
$port = $mysqlSource['port'];
|
||||
$database = $mysqlSource['database'];
|
||||
$username = $mysqlSource['username'];
|
||||
$password = $mysqlSource['password'];
|
||||
$useDocker = $mysqlSource['useDocker'];
|
||||
$dockerContainerName = $mysqlSource['dockerContainerName'];
|
||||
|
||||
// 列出备份文件(支持 SQL 文件和 zip 文件)
|
||||
$backupFiles = array_merge(
|
||||
glob("{$backupDir}/*.sql"),
|
||||
glob("{$backupDir}/*.zip")
|
||||
);
|
||||
|
||||
if (empty($backupFiles)) {
|
||||
$output->writeln("❌ 未找到备份文件");
|
||||
return;
|
||||
}
|
||||
|
||||
// 按修改时间排序
|
||||
usort($backupFiles, function ($a, $b) {
|
||||
return filemtime($b) - filemtime($a);
|
||||
});
|
||||
|
||||
// 显示备份文件列表
|
||||
$output->writeln("\n可用的备份文件:");
|
||||
foreach ($backupFiles as $index => $file) {
|
||||
$fileName = basename($file);
|
||||
$fileSize = filesize($file) / 1024 / 1024;
|
||||
$modTime = date("Y-m-d H:i:s", filemtime($file));
|
||||
$output->writeln(($index + 1) . ". {$fileName} (" . round($fileSize, 2) . " MB, {$modTime})");
|
||||
}
|
||||
|
||||
// 选择备份文件
|
||||
$output->write("\n请选择要还原的备份文件 (1-" . count($backupFiles) . "): ");
|
||||
$handle = fopen("php://stdin", "r");
|
||||
$choice = fgets($handle);
|
||||
fclose($handle);
|
||||
$choice = trim($choice);
|
||||
|
||||
if (!is_numeric($choice) || $choice < 1 || $choice > count($backupFiles)) {
|
||||
$output->writeln("\n无效选择");
|
||||
return;
|
||||
}
|
||||
|
||||
$selectedFile = $backupFiles[$choice - 1];
|
||||
$output->writeln("\n选择的备份文件: " . basename($selectedFile));
|
||||
|
||||
$sqlFile = $selectedFile;
|
||||
|
||||
// 如果是 zip 文件,需要解压
|
||||
if (pathinfo($selectedFile, PATHINFO_EXTENSION) === 'zip') {
|
||||
// 生成临时还原目录
|
||||
$tempDir = "/tmp/mysql_restore_" . uniqid();
|
||||
if (!is_dir($tempDir)) {
|
||||
mkdir($tempDir, 0755, true);
|
||||
}
|
||||
|
||||
// 解压备份文件
|
||||
$unzipCmd = "unzip {$selectedFile} -d {$tempDir}";
|
||||
$output->writeln("解压备份文件...");
|
||||
exec($unzipCmd, $unzipOutput, $unzipReturnCode);
|
||||
|
||||
if ($unzipReturnCode !== 0) {
|
||||
$output->writeln("❌ 解压备份文件失败");
|
||||
$output->writeln(implode("\n", $unzipOutput));
|
||||
// 清理临时目录
|
||||
exec("rm -rf {$tempDir}");
|
||||
return;
|
||||
}
|
||||
|
||||
// 找到解压后的 SQL 文件
|
||||
$sqlFiles = glob("{$tempDir}/*.sql");
|
||||
if (empty($sqlFiles)) {
|
||||
$output->writeln("❌ 未找到 SQL 文件");
|
||||
// 清理临时目录
|
||||
exec("rm -rf {$tempDir}");
|
||||
return;
|
||||
}
|
||||
|
||||
$sqlFile = $sqlFiles[0];
|
||||
}
|
||||
|
||||
// 构建还原命令
|
||||
$cmd = $this->getMySqlRestoreCommand($host, $port, $database, $username, $password, $sqlFile, $useDocker, $dockerContainerName);
|
||||
$output->writeln("执行命令: {$cmd}");
|
||||
|
||||
// 执行还原命令
|
||||
exec($cmd, $outputLines, $returnCode);
|
||||
|
||||
if ($returnCode === 0) {
|
||||
$output->writeln("✅ MySQL 还原成功");
|
||||
} else {
|
||||
$output->writeln("❌ MySQL 还原失败");
|
||||
$output->writeln(implode("\n", $outputLines));
|
||||
}
|
||||
|
||||
// 清理临时目录(如果使用了临时目录)
|
||||
if (pathinfo($selectedFile, PATHINFO_EXTENSION) === 'zip') {
|
||||
exec("rm -rf {$tempDir}");
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
$output->writeln("❌ MySQL 还原失败: " . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
private function clearRedis($output): void
|
||||
{
|
||||
$output->writeln("\n开始清空 Redis...");
|
||||
|
||||
try {
|
||||
// 从数据源配置中获取 Redis 配置
|
||||
$redisSource = null;
|
||||
foreach ($this->dataSources as $source) {
|
||||
if ($source['type'] === 'redis') {
|
||||
$redisSource = $source;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!$redisSource) {
|
||||
$output->writeln("❌ 未找到 Redis 数据源配置");
|
||||
return;
|
||||
}
|
||||
|
||||
if ($redisSource['useDocker']) {
|
||||
$redisContainer = $redisSource['dockerContainerName'];
|
||||
if (!$redisContainer) {
|
||||
$output->writeln("❌ 未指定 Redis Docker 容器名称");
|
||||
return;
|
||||
}
|
||||
$output->writeln("使用 Docker 容器清空 Redis");
|
||||
$cmd = "docker exec -it {$redisContainer} redis-cli flushall";
|
||||
$output->writeln("执行命令: {$cmd}");
|
||||
|
||||
exec($cmd, $outputLines, $returnCode);
|
||||
|
||||
if ($returnCode === 0) {
|
||||
$output->writeln("✅ Redis 清空成功");
|
||||
} else {
|
||||
$output->writeln("❌ Redis 清空失败");
|
||||
$output->writeln(implode("\n", $outputLines));
|
||||
}
|
||||
} else {
|
||||
$redis = new \Redis();
|
||||
$host = $redisSource['host'] ?? '127.0.0.1';
|
||||
$port = $redisSource['port'] ?? 6379;
|
||||
$password = $redisSource['password'] ?? '';
|
||||
|
||||
$output->writeln("连接 Redis: {$host}:{$port}");
|
||||
|
||||
if ($redis->connect($host, $port)) {
|
||||
if (!empty($password)) {
|
||||
$redis->auth($password);
|
||||
}
|
||||
|
||||
$result = $redis->flushAll();
|
||||
|
||||
if ($result) {
|
||||
$output->writeln("✅ Redis 清空成功");
|
||||
} else {
|
||||
$output->writeln("❌ Redis 清空失败");
|
||||
}
|
||||
} else {
|
||||
$output->writeln("❌ 无法连接到 Redis");
|
||||
}
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
$output->writeln("❌ Redis 操作失败: " . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
private function getMongoDumpCommand($host, $port, $database, $outputDir, $useDocker = false, $dockerContainerName = null, $username = '', $password = '', $authSource = null): string
|
||||
{
|
||||
if ($authSource === null) {
|
||||
$authSource = $database;
|
||||
}
|
||||
|
||||
$authParams = '';
|
||||
if (!empty($username) && !empty($password)) {
|
||||
$authParams = "--username {$username} --password {$password} --authenticationDatabase {$authSource}";
|
||||
}
|
||||
|
||||
if ($useDocker) {
|
||||
if (!$dockerContainerName) {
|
||||
return "echo '错误:未指定 Docker 容器名称' && exit 1";
|
||||
}
|
||||
$port = 27017;
|
||||
return "docker exec -it {$dockerContainerName} mongodump --host {$host}:{$port} {$authParams} --db {$database} --out /tmp/mongo_backup && docker cp {$dockerContainerName}:/tmp/mongo_backup/{$database} {$outputDir}/";
|
||||
} else {
|
||||
return "mongodump --host {$host}:{$port} {$authParams} --db {$database} --out {$outputDir}";
|
||||
}
|
||||
}
|
||||
|
||||
private function getMongoRestoreCommand($host, $port, $database, $restoreDir, $useDocker = false, $dockerContainerName = null, $username = '', $password = '', $authSource = null): string
|
||||
{
|
||||
if ($authSource === null) {
|
||||
$authSource = $database;
|
||||
}
|
||||
|
||||
$authParams = '';
|
||||
if (!empty($username) && !empty($password)) {
|
||||
$authParams = "--username {$username} --password {$password} --authenticationDatabase {$authSource}";
|
||||
}
|
||||
|
||||
if ($useDocker) {
|
||||
if (!$dockerContainerName) {
|
||||
return "echo '错误:未指定 Docker 容器名称' && exit 1";
|
||||
}
|
||||
$dirName = basename($restoreDir);
|
||||
return "docker cp {$restoreDir} {$dockerContainerName}:/tmp/ && docker exec -it {$dockerContainerName} mongorestore --host {$host}:{$port} {$authParams} --db {$database} /tmp/{$dirName} && docker exec -it {$dockerContainerName} rm -rf /tmp/{$dirName}";
|
||||
} else {
|
||||
return "mongorestore --host {$host}:{$port} {$authParams} --db {$database} {$restoreDir}";
|
||||
}
|
||||
}
|
||||
|
||||
private function getMySqlDumpCommand($host, $port, $database, $username, $password, $outputFile, $useDocker = false, $dockerContainerName = null): string
|
||||
{
|
||||
if ($useDocker) {
|
||||
// 使用 Docker 容器
|
||||
if (!$dockerContainerName) {
|
||||
return "echo '错误:未指定 Docker 容器名称' && exit 1";
|
||||
}
|
||||
return "docker exec -it {$dockerContainerName} mysqldump -h {$host} -P {$port} -u {$username} --password={$password} {$database} > {$outputFile}";
|
||||
} else {
|
||||
// 不使用 Docker,直接使用本地命令
|
||||
return "mysqldump -h {$host} -P {$port} -u {$username} --password={$password} {$database} > {$outputFile}";
|
||||
}
|
||||
}
|
||||
|
||||
private function getMySqlRestoreCommand($host, $port, $database, $username, $password, $sqlFile, $useDocker = false, $dockerContainerName = null): string
|
||||
{
|
||||
if ($useDocker) {
|
||||
// 使用 Docker 容器
|
||||
if (!$dockerContainerName) {
|
||||
return "echo '错误:未指定 Docker 容器名称' && exit 1";
|
||||
}
|
||||
return "docker cp {$sqlFile} {$dockerContainerName}:/tmp/mysql_restore.sql && docker exec -it {$dockerContainerName} bash -c 'mysql -h {$host} -P {$port} -u {$username} --password={$password} {$database} < /tmp/mysql_restore.sql'";
|
||||
} else {
|
||||
// 不使用 Docker,直接使用本地命令
|
||||
return "mysql -h {$host} -P {$port} -u {$username} --password={$password} {$database} < {$sqlFile}";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
<?php
|
||||
namespace app\command;
|
||||
|
||||
use Symfony\Component\Console\Command\Command;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
|
||||
class CheckConversation extends Command
|
||||
{
|
||||
protected static $defaultName = 'check-conversation';
|
||||
protected static $defaultDescription = '检查 conversation 记录';
|
||||
|
||||
protected function execute(InputInterface $input, OutputInterface $output): int
|
||||
{
|
||||
$conversationId = 'sg_2639473367';
|
||||
$ownerUserId = '83484627';
|
||||
|
||||
cp("=== 检查 conversation 表记录 ===");
|
||||
$convModel = new \app\model\Openim\Conversation();
|
||||
$conv = $convModel->where('conversation_id', $conversationId)
|
||||
->where('owner_user_id', $ownerUserId)
|
||||
->find();
|
||||
|
||||
if ($conv) {
|
||||
cp("找到记录:");
|
||||
print_r($conv->toArray());
|
||||
cp("");
|
||||
if (isset($conv['max_seq'])) {
|
||||
cp("max_seq: " . $conv['max_seq']);
|
||||
} else {
|
||||
cp("max_seq 不存在");
|
||||
}
|
||||
if (isset($conv['min_seq'])) {
|
||||
cp("min_seq: " . $conv['min_seq']);
|
||||
} else {
|
||||
cp("min_seq 不存在");
|
||||
}
|
||||
} else {
|
||||
cp("未找到记录");
|
||||
cp("\n查找同一 conversation_id 的其他记录:");
|
||||
$allConvs = $convModel->where('conversation_id', $conversationId)->select();
|
||||
foreach ($allConvs as $c) {
|
||||
cp("owner_user_id: " . ($c['owner_user_id'] ?? 'null'));
|
||||
cp(" max_seq: " . ($c['max_seq'] ?? 'null'));
|
||||
cp(" min_seq: " . ($c['min_seq'] ?? 'null'));
|
||||
}
|
||||
}
|
||||
|
||||
cp("\n=== 检查 seq 表 ===");
|
||||
$seqModel = new \app\model\Openim\Seq();
|
||||
$seq = $seqModel->where('conversation_id', $conversationId)->find();
|
||||
if ($seq) {
|
||||
cp("conversation_id: " . $seq['conversation_id']);
|
||||
cp("max_seq: " . $seq['max_seq']);
|
||||
cp("min_seq: " . $seq['min_seq']);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
+132
-6
@@ -10,6 +10,9 @@ use Symfony\Component\Console\Input\InputArgument;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
use support\think\Db;
|
||||
use app\model\User as UserModel;
|
||||
use \think\db\PDOConnection;
|
||||
use think\db\exception\PDOException;
|
||||
use think\db\exception\InvalidArgumentException;
|
||||
|
||||
|
||||
class Database extends Command
|
||||
@@ -22,10 +25,12 @@ class Database extends Command
|
||||
*/
|
||||
protected function configure()
|
||||
{
|
||||
$this->addOption('action','a', InputArgument::OPTIONAL, '要做什么操作');
|
||||
$this->addOption('table','t', InputArgument::OPTIONAL, '表名');
|
||||
$this->addOption('domain','ym', InputArgument::OPTIONAL, 'domain');
|
||||
$this->addOption('robot_id','rid', InputArgument::OPTIONAL, 'robot_id');
|
||||
$this->addOption('action','a', InputOption::VALUE_OPTIONAL, '要做什么操作');
|
||||
$this->addOption('table','t', InputOption::VALUE_OPTIONAL, '表名');
|
||||
$this->addOption('domain','ym', InputOption::VALUE_OPTIONAL, 'domain');
|
||||
$this->addOption('connection','c', InputOption::VALUE_OPTIONAL, '数据库链接名,默认mysql','mysql');
|
||||
$this->addOption('dir','d', InputOption::VALUE_OPTIONAL, '缓存目录');
|
||||
$this->addOption('robot_id','rid', InputOption::VALUE_OPTIONAL, 'robot_id');
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -36,12 +41,32 @@ class Database extends Command
|
||||
protected function execute(InputInterface $input, OutputInterface $output): int
|
||||
{
|
||||
$action = $input->getOption('action');
|
||||
if($action == 'prototype'){
|
||||
return $this->prototype($input, $output);
|
||||
if(method_exists($this, $action)){
|
||||
return $this->$action($input, $output);
|
||||
}
|
||||
cp('操作不存在:'.$action);
|
||||
return 0;
|
||||
}
|
||||
function optimize_schema(InputInterface $input, OutputInterface $output)
|
||||
{
|
||||
$table = $input->getOption('table');
|
||||
try {
|
||||
if ($table) {
|
||||
$this->cacheTable($table, $input->getOption('connection'));
|
||||
} else {
|
||||
$dirs = ((array) $input->getOption('dir')) ?: $this->getDefaultDirs();
|
||||
foreach ($dirs as $dir) {
|
||||
$this->cacheModel($dir);
|
||||
}
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
$output->write($e->getMessage());
|
||||
return self::FAILURE;
|
||||
}
|
||||
|
||||
$output->write('Succeed!');
|
||||
return self::SUCCESS;
|
||||
}
|
||||
function prototype(InputInterface $input, OutputInterface $output){
|
||||
$table = $input->getOption('table');
|
||||
// 获取表前缀并构建完整表名
|
||||
@@ -132,4 +157,105 @@ class Database extends Command
|
||||
// 默认返回混合类型
|
||||
return 'mixed';
|
||||
}
|
||||
|
||||
|
||||
protected function buildModelSchema(string $class): void
|
||||
{
|
||||
$reflect = new \ReflectionClass($class);
|
||||
if ($reflect->isAbstract() || ! $reflect->isSubclassOf('\think\Model')) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
/** @var \think\Model $model */
|
||||
$model = new $class;
|
||||
$connection = $model->db()->getConnection();
|
||||
if ($connection instanceof PDOConnection) {
|
||||
$table = $model->getTable();
|
||||
//预读字段信息
|
||||
$connection->getSchemaInfo($table, true);
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
}
|
||||
}
|
||||
|
||||
protected function buildDataBaseSchema(PDOConnection $connection, array $tables, string $dbName): void
|
||||
{
|
||||
foreach ($tables as $table) {
|
||||
//预读字段信息
|
||||
$connection->getSchemaInfo("{$dbName}.{$table}", true);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 缓存表
|
||||
*/
|
||||
private function cacheTable(string $table, ?string $connectionName = null): void
|
||||
{
|
||||
$connection = Db::connect($connectionName);
|
||||
if (! $connection instanceof PDOConnection) {
|
||||
throw new Exception('only PDO connection support schema cache!');
|
||||
}
|
||||
|
||||
if (str_contains($table, '.')) {
|
||||
[$dbName, $table] = explode('.', $table);
|
||||
} else {
|
||||
$dbName = $connection->getConfig('database');
|
||||
}
|
||||
|
||||
if ($table == '*') {
|
||||
$table = $connection->getTables($dbName);
|
||||
}
|
||||
|
||||
$this->buildDataBaseSchema($connection, (array) $table, $dbName);
|
||||
}
|
||||
|
||||
/**
|
||||
* 缓存模型
|
||||
*/
|
||||
private function cacheModel(?string $dir = null): void
|
||||
{
|
||||
if ($dir) {
|
||||
$modelDir = app_path('model') . $dir . DIRECTORY_SEPARATOR;
|
||||
$namespace = 'app\\' . $dir;
|
||||
} else {
|
||||
$modelDir = app_path('model').DIRECTORY_SEPARATOR;
|
||||
$namespace = 'app';
|
||||
}
|
||||
if (! is_dir($modelDir)) {
|
||||
throw new InvalidArgumentException("{$modelDir} directory does not exist");
|
||||
}
|
||||
|
||||
/** @var \SplFileInfo[] $iterator */
|
||||
$iterator = new \RecursiveIteratorIterator(
|
||||
new \RecursiveDirectoryIterator($modelDir, \RecursiveDirectoryIterator::SKIP_DOTS),
|
||||
\RecursiveIteratorIterator::SELF_FIRST
|
||||
);
|
||||
|
||||
foreach ($iterator as $fileInfo) {
|
||||
$relativePath = substr($fileInfo->getRealPath(), strlen($modelDir));
|
||||
if (! str_ends_with($relativePath, '.php')) {
|
||||
continue;
|
||||
}
|
||||
// 去除 .php
|
||||
$relativePath = substr($relativePath, 0, -4);
|
||||
|
||||
$class = '\\' . $namespace . '\\model\\' . str_replace('/', '\\', $relativePath);
|
||||
if (! class_exists($class)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$this->buildModelSchema($class);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取默认目录名
|
||||
* @return array<int, ?string>
|
||||
*/
|
||||
private function getDefaultDirs(): array
|
||||
{
|
||||
// 包含默认的模型目录
|
||||
$dirs = [null];
|
||||
return $dirs;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,273 @@
|
||||
<?php
|
||||
|
||||
namespace app\command;
|
||||
|
||||
use Symfony\Component\Console\Command\Command;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Input\InputOption;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
|
||||
class FixOpenimSeq extends Command
|
||||
{
|
||||
protected static $defaultName = 'fix:openim:seq';
|
||||
protected static $defaultDescription = '修复 OpenIM MongoDB seq 相关字段';
|
||||
|
||||
private $conversationSeqMap = [];
|
||||
|
||||
protected function configure(): void
|
||||
{
|
||||
}
|
||||
|
||||
protected function execute(InputInterface $input, OutputInterface $output): int
|
||||
{
|
||||
$this->log("╔════════════════════════════════════════════════════════════╗");
|
||||
$this->log("║ 开始修复 OpenIM Seq 数据 ║");
|
||||
$this->log("╚════════════════════════════════════════════════════════════╝");
|
||||
$this->log("");
|
||||
|
||||
// 分析数据(获取会话 seq 信息)
|
||||
$this->analyzeMsgSeq();
|
||||
// 执行修复
|
||||
$this->fixAll();
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
private function getConversationId(array $msg): ?string
|
||||
{
|
||||
$sessionType = $msg['session_type'] ?? null;
|
||||
$sendId = $msg['send_id'] ?? '';
|
||||
$recvId = $msg['recv_id'] ?? '';
|
||||
$groupId = $msg['group_id'] ?? '';
|
||||
|
||||
if ($sessionType === 3 || !empty($groupId)) {
|
||||
return 'sg_' . $groupId;
|
||||
}
|
||||
|
||||
if ($sessionType === 1 || ($sendId && $recvId)) {
|
||||
$ids = [(int)$sendId, (int)$recvId];
|
||||
sort($ids);
|
||||
return 'si_' . $ids[0] . '_' . $ids[1];
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private function analyzeMsgSeq(): void
|
||||
{
|
||||
$msgCount = \app\model\Openim\Msg::count();
|
||||
$this->log("分析消息数据...");
|
||||
$this->log("消息文档数: {$msgCount}");
|
||||
|
||||
$processedDocs = 0;
|
||||
$totalMsgs = 0;
|
||||
|
||||
$msgs = \app\model\Openim\Msg::select();
|
||||
foreach ($msgs as $doc) {
|
||||
$processedDocs++;
|
||||
$msgsArray = $doc['msgs'];
|
||||
if ($msgsArray instanceof \think\model\Collection) {
|
||||
$msgsArray = $msgsArray->toArray();
|
||||
}
|
||||
if (!empty($msgsArray) && is_array($msgsArray)) {
|
||||
foreach ($msgsArray as $msgItem) {
|
||||
if (isset($msgItem['msg'])) {
|
||||
$msg = $msgItem['msg'];
|
||||
$conversationId = $this->getConversationId($msg);
|
||||
$seq = $msg['seq'] ?? null;
|
||||
|
||||
if ($conversationId && $seq !== null) {
|
||||
$totalMsgs++;
|
||||
if (!isset($this->conversationSeqMap[$conversationId])) {
|
||||
$this->conversationSeqMap[$conversationId] = [
|
||||
'min_seq' => $seq,
|
||||
'max_seq' => $seq,
|
||||
'count' => 0
|
||||
];
|
||||
}
|
||||
$this->conversationSeqMap[$conversationId]['min_seq'] = min($this->conversationSeqMap[$conversationId]['min_seq'], $seq);
|
||||
$this->conversationSeqMap[$conversationId]['max_seq'] = max($this->conversationSeqMap[$conversationId]['max_seq'], $seq);
|
||||
$this->conversationSeqMap[$conversationId]['count']++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($processedDocs % 100 == 0) {
|
||||
$this->log("已处理 {$processedDocs}/{$msgCount} 个文档...");
|
||||
}
|
||||
}
|
||||
|
||||
$this->log("处理完成!共处理 {$processedDocs} 个文档,{$totalMsgs} 条消息");
|
||||
$this->log("发现 " . count($this->conversationSeqMap) . " 个会话");
|
||||
$this->log("");
|
||||
}
|
||||
|
||||
private function fixAll(): void
|
||||
{
|
||||
$this->log("开始修复数据...");
|
||||
$this->log("");
|
||||
|
||||
if (empty($this->conversationSeqMap)) {
|
||||
$this->log("错误:缺少会话 seq 数据");
|
||||
return;
|
||||
}
|
||||
|
||||
$seqFixed = 0;
|
||||
$seqCreated = 0;
|
||||
$seqUserFixed = 0;
|
||||
$conversationFixed = 0;
|
||||
|
||||
foreach ($this->conversationSeqMap as $conversationId => $seqInfo) {
|
||||
// 修复 seq 表
|
||||
$existing = \app\model\Openim\Seq::where('conversation_id', $conversationId)->find();
|
||||
if ($existing) {
|
||||
$max_seq = 0;
|
||||
if(str_starts_with($conversationId,'sg_')){
|
||||
$max_seq = ceil($seqInfo['max_seq']/100)*100+1;
|
||||
}else{
|
||||
$max_seq = ceil($seqInfo['max_seq']/50)*50+1;
|
||||
}
|
||||
$existing->max_seq = $max_seq;
|
||||
$existing->min_seq = 0;
|
||||
$existing->save();
|
||||
$seqFixed++;
|
||||
} else {
|
||||
}
|
||||
|
||||
// 修复 seq_user 表
|
||||
\app\model\Openim\SeqUser::where('conversation_id', $conversationId)->update([
|
||||
'max_seq' => 0,
|
||||
'min_seq' => 0,
|
||||
]);
|
||||
\app\model\Openim\SeqUser::where('conversation_id', $conversationId)
|
||||
->where('read_seq','<>',$seqInfo['max_seq'])
|
||||
->update([
|
||||
'read_seq' => $seqInfo['max_seq'],
|
||||
]);
|
||||
|
||||
// 修复 conversation 表
|
||||
\app\model\Openim\Conversation::where('min_seq', '>',0)->update([
|
||||
'max_seq' => 0,
|
||||
'min_seq' => 0,
|
||||
]);
|
||||
}
|
||||
|
||||
$this->log("修复完成!");
|
||||
$this->log("- seq 表: 更新 {$seqFixed} 条,新建 {$seqCreated} 条");
|
||||
$this->log("- seq_user 表: 更新 {$seqUserFixed} 条");
|
||||
$this->log("- conversation 表: 更新 {$conversationFixed} 条");
|
||||
$this->log("");
|
||||
}
|
||||
|
||||
private function log(string $message): void
|
||||
{
|
||||
echo $message . "\n";
|
||||
}
|
||||
|
||||
/**
|
||||
* 重置 seq 相关字段并重新计算
|
||||
*/
|
||||
public function fix_seq(): void
|
||||
{
|
||||
$this->log("\n═══════════════════════════════════════════════════════════");
|
||||
$this->log(" 执行 fix_seq 方法 ");
|
||||
$this->log("═══════════════════════════════════════════════════════════");
|
||||
|
||||
// 1. 获取所有会话ID
|
||||
$conversationIds = [];
|
||||
|
||||
// 从 seq 表获取所有会话ID
|
||||
$seqRecords = \app\model\Openim\Seq::field('conversation_id')->select()->toArray();
|
||||
foreach ($seqRecords as $record) {
|
||||
$conversationIds[] = $record['conversation_id'];
|
||||
}
|
||||
|
||||
// 去重
|
||||
$conversationIds = array_unique($conversationIds);
|
||||
$totalConversations = count($conversationIds);
|
||||
$this->log("发现 {$totalConversations} 个会话");
|
||||
|
||||
$processed = 0;
|
||||
foreach ($conversationIds as $conversationId) {
|
||||
cp('更新:'.$conversationId);
|
||||
continue;
|
||||
$processed++;
|
||||
$this->log("\n处理会话 {$conversationId} ({$processed}/{$totalConversations})");
|
||||
|
||||
// 2. 计算变量A
|
||||
$msgCount = \app\model\Openim\Msg::whereLike('doc_id', "{$conversationId}%")->count();
|
||||
$multiplier = strpos($conversationId, 'sg_') === 0 ? 100 : 50;
|
||||
$baseA = $msgCount * $multiplier + 1;
|
||||
|
||||
// 确保 A 是 1, 51, 101 等递增格式
|
||||
$remainder = $baseA % $multiplier;
|
||||
if ($remainder != 1) {
|
||||
$baseA = $baseA - $remainder + 1;
|
||||
}
|
||||
|
||||
// 3. 获取最后一条消息的 seq
|
||||
$lastSeq = 0;
|
||||
$msgDocs = \app\model\Openim\Msg::whereLike('doc_id', "{$conversationId}%")->select();
|
||||
foreach ($msgDocs as $doc) {
|
||||
$msgsArray = $doc['msgs'];
|
||||
if ($msgsArray instanceof \think\model\Collection) {
|
||||
$msgsArray = $msgsArray->toArray();
|
||||
}
|
||||
if (!empty($msgsArray) && is_array($msgsArray)) {
|
||||
foreach ($msgsArray as $msgItem) {
|
||||
if (isset($msgItem['msg']['seq'])) {
|
||||
$lastSeq = max($lastSeq, (int)$msgItem['msg']['seq']);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 确保 A 大于最后一条消息的 seq
|
||||
if ($baseA <= $lastSeq) {
|
||||
$baseA = $lastSeq + 50 - ($lastSeq % 50) + 1;
|
||||
if ($baseA % 50 != 1) {
|
||||
$baseA += 1;
|
||||
}
|
||||
}
|
||||
|
||||
$this->log(" - 消息记录数: {$msgCount}");
|
||||
$this->log(" - 乘数: {$multiplier}");
|
||||
$this->log(" - 最后消息 seq: {$lastSeq}");
|
||||
$this->log(" - 计算变量 A: {$baseA}");
|
||||
|
||||
// 4. 更新 seq 表
|
||||
$seq = \app\model\Openim\Seq::where('conversation_id', $conversationId)->find();
|
||||
if ($seq) {
|
||||
$seq->max_seq = $baseA;
|
||||
$seq->min_seq = 0;
|
||||
$seq->save();
|
||||
$this->log(" - 更新 seq 表: max_seq={$baseA}, min_seq=0");
|
||||
}
|
||||
|
||||
// 5. 更新 conversation 表
|
||||
$conversations = \app\model\Openim\Conversation::where('conversation_id', $conversationId)->select();
|
||||
foreach ($conversations as $conversation) {
|
||||
$conversation->max_seq = 0;
|
||||
$conversation->min_seq = 0;
|
||||
$conversation->save();
|
||||
}
|
||||
$this->log(" - 更新 conversation 表: max_seq=0, min_seq=0");
|
||||
|
||||
// 6. 更新 seq_user 表
|
||||
$seqUsers = \app\model\Openim\SeqUser::where('conversation_id', $conversationId)->select();
|
||||
foreach ($seqUsers as $seqUser) {
|
||||
cp('更新:'.$conversationId);
|
||||
$seqUser->max_seq = 0;
|
||||
$seqUser->min_seq = 0;
|
||||
$seqUser->read_seq = $lastSeq;
|
||||
$seqUser->save();
|
||||
}
|
||||
$this->log(" - 更新 seq_user 表: max_seq=0, min_seq=0, read_seq={$lastSeq}");
|
||||
}
|
||||
|
||||
$this->log("\n═══════════════════════════════════════════════════════════");
|
||||
$this->log(" fix_seq 方法执行完成 ");
|
||||
$this->log("═══════════════════════════════════════════════════════════");
|
||||
}
|
||||
}
|
||||
@@ -63,10 +63,10 @@ class Language extends Command
|
||||
return 0;
|
||||
}
|
||||
function write2file($data=[]){
|
||||
$langs = ['zh','en'];
|
||||
$langs = ['zh-Hans','en'];
|
||||
foreach($data as $fn=>$arr){
|
||||
foreach($langs as $lang){
|
||||
$lang_path = base_path().'/resource/translations/'.$lang.'/';
|
||||
$lang_path = base_path('/resource/translations/'.$lang.'/');
|
||||
$_common_arr = require($lang_path.'common.php');
|
||||
$_arr = [];
|
||||
if(file_exists($lang_path.$fn)){
|
||||
|
||||
@@ -0,0 +1,872 @@
|
||||
<?php
|
||||
|
||||
namespace app\command;
|
||||
|
||||
use Symfony\Component\Console\Command\Command;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Input\InputOption;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
// 引入内置进度条类
|
||||
use Symfony\Component\Console\Helper\ProgressBar;
|
||||
use support\think\Db;
|
||||
|
||||
class MigrateMessages extends Command
|
||||
{
|
||||
protected static $defaultName = 'migrate:messages';
|
||||
protected static $defaultDescription = '从老数据库迁移数据到新OpenIM数据库';
|
||||
|
||||
private $sdk = null;
|
||||
private $oldManager = null;
|
||||
private $newManager = null;
|
||||
private $retry = 3;
|
||||
private $delay = 2;
|
||||
private $backupDir = '/vol3/1000/code/im/admin/backup';
|
||||
private $currentBackup = null;
|
||||
|
||||
private $skipUsers = [];
|
||||
private $skipGroups = [];
|
||||
private $stats = [
|
||||
'users' => ['total' => 0, 'success' => 0, 'failed' => 0],
|
||||
'groups' => ['total' => 0, 'success' => 0, 'failed' => 0],
|
||||
'members' => ['total' => 0, 'success' => 0, 'failed' => 0],
|
||||
'messages' => ['total' => 0, 'success' => 0, 'failed' => 0, 'skipped' => 0],
|
||||
];
|
||||
|
||||
protected function configure(): void
|
||||
{
|
||||
$this->addOption('step', 's', InputOption::VALUE_OPTIONAL, '执行步骤: users/groups/members/messages/all', 'all');
|
||||
$this->addOption('skip-users', null, InputOption::VALUE_OPTIONAL, '跳过的用户ID(逗号分隔)');
|
||||
$this->addOption('skip-groups', null, InputOption::VALUE_OPTIONAL, '跳过的群ID(逗号分隔)');
|
||||
$this->addOption('clean', null, InputOption::VALUE_NONE, '清空现有数据后再迁移');
|
||||
$this->addOption('retry', 'r', InputOption::VALUE_OPTIONAL, '失败重试次数', 3);
|
||||
}
|
||||
|
||||
protected function execute(InputInterface $input, OutputInterface $output): int
|
||||
{
|
||||
$step = $input->getOption('step');
|
||||
$skipUsers = $input->getOption('skip-users') ? explode(',', $input->getOption('skip-users')) : [];
|
||||
$skipGroups = $input->getOption('skip-groups') ? explode(',', $input->getOption('skip-groups')) : [];
|
||||
$clean = $input->getOption('clean');
|
||||
$retry = (int)$input->getOption('retry');
|
||||
|
||||
// 自动忽略特殊用户
|
||||
$defaultSkipUsers = ['group_bot', 'official_team', 'system','imAdmin'];
|
||||
$skipUsers = array_merge($skipUsers, $defaultSkipUsers);
|
||||
$this->skipUsers = array_unique($skipUsers);
|
||||
$this->skipGroups = array_unique($skipGroups);
|
||||
$this->retry = $retry;
|
||||
|
||||
$this->log($output, "╔════════════════════════════════════════════════════════════╗");
|
||||
$this->log($output, "║ OpenIM 数据迁移工具 v2.0 ║");
|
||||
$this->log($output, "╚════════════════════════════════════════════════════════════╝");
|
||||
$this->log($output, "");
|
||||
|
||||
if ($clean) {
|
||||
$this->log($output, "🗑️ 清理模式:会清空现有数据");
|
||||
}
|
||||
$this->log($output, "📍 执行步骤: {$step}");
|
||||
$this->log($output, "");
|
||||
if($step == 'restore'){
|
||||
$this->restoreMongoDB($output, '/vol3/1000/code/im/admin/backup/openim_v3_groups_20260413141105.json');
|
||||
return 0;
|
||||
}
|
||||
$this->cleanExistingData($output,[]);
|
||||
try {
|
||||
$this->initConnections($output);
|
||||
//return 0 ;
|
||||
if ($clean) {
|
||||
$this->cleanExistingData($output,[
|
||||
'conversation', 'conversation_version', // 会话相关集合
|
||||
'data_version', // 数据版本集合
|
||||
'friend', 'friend_request', 'friend_version', // 好友关系相关集合
|
||||
'group', 'group_join_version','group_member','group_member_version','group_request', // 群组相关集合
|
||||
'msg','seq','seq_user' // 消息和序列号相关集合
|
||||
]);
|
||||
return 0;
|
||||
}
|
||||
|
||||
cache('admin_token_imAdmin',null);
|
||||
$steps = $step === 'all' ? [
|
||||
'users',
|
||||
'friends',
|
||||
'groups',
|
||||
//'members',
|
||||
'messages'
|
||||
] : [$step];
|
||||
|
||||
foreach ($steps as $s) {
|
||||
// 备份数据
|
||||
$backupFile = $this->backupMongoDB($output, $s);
|
||||
|
||||
try {
|
||||
switch ($s) {
|
||||
case 'users':
|
||||
$this->migrateUsers($output);
|
||||
break;
|
||||
case 'friends':
|
||||
$this->migrateFriends($output);
|
||||
break;
|
||||
case 'groups':
|
||||
$this->migrateGroups($output);
|
||||
break;
|
||||
case 'members':
|
||||
$this->migrateGroupMembers($output);
|
||||
break;
|
||||
case 'messages':
|
||||
$this->migrateMessages($output);
|
||||
break;
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
// 遇到错误,回滚数据
|
||||
if (!empty($backupFile)) {
|
||||
$this->restoreMongoDB($output, $backupFile);
|
||||
}
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
|
||||
$this->printStats($output);
|
||||
return self::SUCCESS;
|
||||
} catch (\Exception $e) {
|
||||
$this->log($output, "❌ 错误: " . $e->getMessage());
|
||||
//$this->log($output, $e->getTraceAsString());
|
||||
return self::FAILURE;
|
||||
}
|
||||
}
|
||||
private function migrateUsers(OutputInterface $output): void
|
||||
{
|
||||
//之前残留了一部分数据,是单向好友,这里没做删除,所以数据大小和之前的不一样,用户重新删除一次就好了
|
||||
$this->log($output, "");
|
||||
$this->log($output, "═════════════════ 步骤1: 迁移用户 ═════════════════");
|
||||
$this->log($output, "清理旧的数据");
|
||||
|
||||
$this->cleanExistingData($output,[
|
||||
'user'
|
||||
]);
|
||||
$user_list = (new \app\model\Openim\User())->setOption('connection','tettt')
|
||||
->whereNotIn('user_id',$this->skipUsers)
|
||||
->field('user_id,nickname,face_url')
|
||||
->select();
|
||||
$user_list = $user_list->toArray();
|
||||
|
||||
// 1. 创建进度条(内置核心方法)
|
||||
$progressBar = new ProgressBar($output, count($user_list));
|
||||
|
||||
// 可选:设置进度条样式(字符、长度等)
|
||||
$progressBar->setBarCharacter('█');
|
||||
$progressBar->setEmptyBarCharacter('░');
|
||||
$progressBar->setProgressCharacter('▶');
|
||||
$progressBar->setBarWidth(400);
|
||||
|
||||
// 2. 开始显示
|
||||
$progressBar->start();
|
||||
echo sprintf("\r");
|
||||
while(count($user_list) > 0){
|
||||
$step = 100;
|
||||
$user = array_slice($user_list,0,$step);
|
||||
$user_list = array_slice($user_list,$step);
|
||||
$this->sdk->user->userRegister($user);
|
||||
$progressBar->advance($step);
|
||||
}
|
||||
// 4. 结束进度条
|
||||
$progressBar->finish();
|
||||
|
||||
}
|
||||
private function migrateFriends(OutputInterface $output): void
|
||||
{
|
||||
//之前残留了一部分数据,是单向好友,这里没做删除,所以数据大小和之前的不一样,用户重新删除一次就好了
|
||||
$this->log($output, "");
|
||||
$this->log($output, "═════════════════ 步骤3: 迁移好友 ═════════════════");
|
||||
$this->log($output, "清理旧的数据");
|
||||
|
||||
// $this->cleanExistingData($output,[
|
||||
// 'conversation', 'conversation_version', // 会话相关集合
|
||||
// 'data_version', // 数据版本集合
|
||||
// 'friend', 'friend_request', 'friend_version', // 好友关系相关集合
|
||||
// 'group', 'group_join_version','group_member','group_member_version','group_request', // 群组相关集合
|
||||
// 'msg','seq','seq_user' // 消息和序列号相关集合
|
||||
// ]);
|
||||
$user_list = (new \app\model\Openim\User())->setOption('connection','tettt')
|
||||
->whereNotNull('user_id')
|
||||
->column('user_id');
|
||||
|
||||
// 1. 创建进度条(内置核心方法)
|
||||
$progressBar = new ProgressBar($output, count($user_list));
|
||||
|
||||
// 可选:设置进度条样式(字符、长度等)
|
||||
$progressBar->setBarCharacter('█');
|
||||
$progressBar->setEmptyBarCharacter('░');
|
||||
$progressBar->setProgressCharacter('▶');
|
||||
$progressBar->setBarWidth(400);
|
||||
|
||||
// 2. 开始显示
|
||||
$progressBar->start();
|
||||
foreach($user_list as $userID){
|
||||
$friend_list = (new \app\model\Openim\Friend())->setOption('connection','tettt')
|
||||
->where('owner_user_id',$userID)
|
||||
->column('friend_user_id');
|
||||
if(count($friend_list)){
|
||||
while(count($friend_list)){
|
||||
$_friend_list = array_slice($friend_list, 0, 500);
|
||||
$friend_list = array_slice($friend_list, 500);
|
||||
$this->sdk->friend->importFriend($userID,$_friend_list);
|
||||
}
|
||||
}
|
||||
$progressBar->advance();
|
||||
}
|
||||
// 4. 结束进度条
|
||||
$progressBar->finish();
|
||||
|
||||
}
|
||||
private function migrateGroups(OutputInterface $output): void
|
||||
{
|
||||
$this->log($output, "");
|
||||
$this->log($output, "═════════════════ 步骤2: 迁移群组 ═════════════════");
|
||||
$this->log($output, "");
|
||||
|
||||
$options = [];
|
||||
|
||||
$groups = $this->queryOldDb('group', [], $options);
|
||||
$this->stats['groups']['total'] = count($groups);
|
||||
$this->log($output, "📊 找到 {$this->stats['groups']['total']} 个群组");
|
||||
|
||||
$processed = 0;
|
||||
// 1. 创建进度条(内置核心方法)
|
||||
$progressBar = new ProgressBar($output, count($groups));
|
||||
|
||||
// 可选:设置进度条样式(字符、长度等)
|
||||
$progressBar->setBarCharacter('█');
|
||||
$progressBar->setEmptyBarCharacter('░');
|
||||
$progressBar->setProgressCharacter('▶');
|
||||
$progressBar->setBarWidth(400);
|
||||
|
||||
// 2. 开始显示
|
||||
$progressBar->start();
|
||||
foreach ($groups as $group) {
|
||||
$processed++;
|
||||
$groupID = (string)($group['group_id'] ?? $group['groupID'] ?? '');
|
||||
|
||||
if (empty($groupID) || in_array($groupID, $this->skipGroups)) {
|
||||
$this->stats['groups']['failed']++;
|
||||
continue;
|
||||
}
|
||||
|
||||
$ownerUserID = (string)($group['owner_user_id'] ?? $group['ownerUserID'] ?? $group['creator_user_id'] ?? $group['creatorUserID'] ?? '');
|
||||
if (empty($ownerUserID)) {
|
||||
$this->stats['groups']['failed']++;
|
||||
continue;
|
||||
}
|
||||
|
||||
$groupName = (string)($group['group_name'] ?? $group['groupName'] ?? '');
|
||||
$faceURL = (string)($group['face_url'] ?? $group['faceURL'] ?? '');
|
||||
$introduction = (string)($group['introduction'] ?? '');
|
||||
$notification = (string)($group['notification'] ?? '');
|
||||
$ex = (string)($group['ex'] ?? '');
|
||||
|
||||
// 群组设置字段
|
||||
$groupType = (int)($group['group_type'] ?? $group['groupType'] ?? 2);
|
||||
$needVerification = (int)($group['need_verification'] ?? $group['needVerification'] ?? 0);
|
||||
$lookMemberInfo = (int)($group['look_member_info'] ?? $group['lookMemberInfo'] ?? 0);
|
||||
$applyMemberFriend = (int)($group['apply_member_friend'] ?? $group['applyMemberFriend'] ?? 0);
|
||||
|
||||
$progress = sprintf("[群组 %d/%d]", $processed, $this->stats['groups']['total']);
|
||||
|
||||
if ($processed % 20 == 0 || $processed == 1) {
|
||||
$this->log($output, "{$progress} 处理中...");
|
||||
}
|
||||
$this->log($output, "{$progress} 尝试创建群组: {$groupID}, 群主: {$ownerUserID}");
|
||||
// 管理员信息
|
||||
$adminUserIDs = (new \app\model\Openim\GroupMember())->setOption('connection','tettt')
|
||||
->where('group_id',$groupID)
|
||||
->where('role_level',60)
|
||||
->column('user_id');
|
||||
//cp($adminUserIDs );
|
||||
// 成员信息
|
||||
$memberUserIDs = (new \app\model\Openim\GroupMember())->setOption('connection','tettt')
|
||||
->where('group_id',$groupID)
|
||||
->where('role_level',20)
|
||||
->column('user_id');
|
||||
//cp($memberUserIDs );
|
||||
$memberUserIDs = array_unique($memberUserIDs);
|
||||
$_memberUserIDs = array_slice($memberUserIDs, 0, 10);
|
||||
$memberUserIDs = array_slice($memberUserIDs, 10);
|
||||
try {
|
||||
$this->sdk->group->createGroup(
|
||||
$ownerUserID,
|
||||
$_memberUserIDs,
|
||||
$adminUserIDs,
|
||||
$groupName,
|
||||
$groupID,
|
||||
$faceURL,
|
||||
$introduction,
|
||||
$notification,
|
||||
$ex,
|
||||
$groupType,
|
||||
$needVerification,
|
||||
$lookMemberInfo,
|
||||
$applyMemberFriend
|
||||
);
|
||||
while(count($memberUserIDs)){
|
||||
$_memberUserIDs = array_slice($memberUserIDs, 0, 10);
|
||||
$memberUserIDs = array_slice($memberUserIDs, 10);
|
||||
try{
|
||||
$this->sdk->group->inviteUserToGroup($groupID, $_memberUserIDs);
|
||||
} catch (\Exception $e) {
|
||||
$this->log($output, "{$progress} ❌ 邀请成员失败: " . $e->getMessage());
|
||||
}
|
||||
}
|
||||
$this->stats['groups']['success']++;
|
||||
//$this->log($output, "{$progress} ✅ 创建成功");
|
||||
} catch (\Exception $e) {
|
||||
$this->stats['groups']['failed']++;
|
||||
if ($e->getCode() == 1202 || strpos($e->getMessage(), 'GroupIDExisted') !== false) {
|
||||
$this->log($output, "{$progress} ℹ️ 群组已存在,跳过创建");
|
||||
$this->stats['groups']['success']++;
|
||||
continue;
|
||||
} else {
|
||||
$this->log($output, "{$progress} ❌ 创建失败: " . $e->getMessage());
|
||||
}
|
||||
}
|
||||
$progressBar->advance();
|
||||
}
|
||||
// 4. 结束进度条
|
||||
$progressBar->finish();
|
||||
}
|
||||
|
||||
private function migrateGroupMembers(OutputInterface $output): void
|
||||
{
|
||||
$this->log($output, "");
|
||||
$this->log($output, "═════════════════ 步骤3: 迁移群成员 ═════════════════");
|
||||
$this->log($output, "");
|
||||
|
||||
$groups = $this->queryOldDb('group', [], ['projection' => ['group_id' => 1, 'groupID' => 1]]);
|
||||
$groupIDs = [];
|
||||
foreach ($groups as $g) {
|
||||
$gid = (string)($g['group_id'] ?? $g['groupID'] ?? '');
|
||||
if (!empty($gid) && !in_array($gid, $this->skipGroups)) {
|
||||
$groupIDs[] = $gid;
|
||||
}
|
||||
}
|
||||
|
||||
$totalMembers = 0;
|
||||
foreach ($groupIDs as $groupID) {
|
||||
$members = $this->queryOldDb('group_member', ['group_id' => $groupID]);
|
||||
$ownerUserID = null;
|
||||
$adminUserIDs = [];
|
||||
$memberUserIDs = [];
|
||||
|
||||
foreach ($members as $member) {
|
||||
$userID = (string)($member['user_id'] ?? $member['userID'] ?? '');
|
||||
if (empty($userID)) continue;
|
||||
|
||||
$roleLevel = (int)($member['role_level'] ?? $member['roleLevel'] ?? 0);
|
||||
if ($roleLevel == 100) {
|
||||
$ownerUserID = $userID;
|
||||
} elseif ($roleLevel == 60) {
|
||||
$adminUserIDs[] = $userID;
|
||||
} else {
|
||||
$memberUserIDs[] = $userID;
|
||||
}
|
||||
}
|
||||
|
||||
if (empty($memberUserIDs) && empty($adminUserIDs)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$totalMembers += count($memberUserIDs);
|
||||
$this->stats['members']['total'] += count($memberUserIDs);
|
||||
|
||||
$progress = sprintf("[群 %s 成员 %d]", $groupID, count($memberUserIDs));
|
||||
$this->log($output, "{$progress} 处理中...");
|
||||
|
||||
// 分批邀请,每批最多50人
|
||||
$batches = array_chunk($memberUserIDs, 50);
|
||||
foreach ($batches as $batch) {
|
||||
if (empty($batch)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$attempts = 0;
|
||||
while ($attempts < $this->retry) {
|
||||
try {
|
||||
$this->log($output, "{$progress} 邀请成员: " . implode(', ', array_slice($batch, 0, 5)) . (count($batch) > 5 ? '...' : ''));
|
||||
$result = $this->sdk->group->inviteUserToGroup($groupID, $ownerUserID ?? 'admin', $batch);
|
||||
$this->log($output, "{$progress} API返回: " . json_encode($result, JSON_UNESCAPED_UNICODE));
|
||||
|
||||
if (isset($result['errCode']) && $result['errCode'] != 0) {
|
||||
// 检查是否是重复键错误
|
||||
if (strpos($result['errMsg'] ?? '', 'duplicate key') !== false || strpos($result['errMsg'] ?? '', 'DuplicateKey') !== false) {
|
||||
$this->log($output, "{$progress} ℹ️ 部分成员已存在,跳过");
|
||||
$this->stats['members']['success'] += count($batch);
|
||||
} else {
|
||||
$this->stats['members']['failed'] += count($batch);
|
||||
$this->log($output, "{$progress} ❌ 邀请失败: " . ($result['errMsg'] ?? '未知错误'));
|
||||
}
|
||||
} else {
|
||||
$this->stats['members']['success'] += count($batch);
|
||||
$this->log($output, "{$progress} ✅ 邀请成功");
|
||||
}
|
||||
break;
|
||||
} catch (\Exception $e) {
|
||||
$attempts++;
|
||||
// 检查是否是重复键错误
|
||||
if (strpos($e->getMessage(), 'duplicate key') !== false || strpos($e->getMessage(), 'DuplicateKey') !== false) {
|
||||
$this->log($output, "{$progress} ℹ️ 部分成员已存在,跳过");
|
||||
$this->stats['members']['success'] += count($batch);
|
||||
break;
|
||||
} elseif ($attempts >= $this->retry) {
|
||||
$this->stats['members']['failed'] += count($batch);
|
||||
$this->log($output, "{$progress} ❌ 邀请异常: " . $e->getMessage());
|
||||
} else {
|
||||
$this->log($output, "{$progress} ⚠️ 邀请失败,第 {$attempts}/{$this->retry} 次重试...");
|
||||
usleep(100000);
|
||||
}
|
||||
}
|
||||
}
|
||||
usleep(10000);
|
||||
}
|
||||
}
|
||||
|
||||
$this->log($output, "📊 共处理 {$totalMembers} 个群成员");
|
||||
}
|
||||
|
||||
private function migrateMessages(OutputInterface $output): void
|
||||
{
|
||||
$this->log($output, "");
|
||||
$this->log($output, "═════════════════ 步骤4: 迁移消息 ═════════════════");
|
||||
$this->log($output, "");
|
||||
|
||||
$pipeline = [
|
||||
['$unwind' => '$msgs'],
|
||||
['$match' => ['msgs.msg' => ['$ne' => null]]],
|
||||
['$sort' => ['msgs.msg.send_time' => 1]],
|
||||
];
|
||||
|
||||
$pipeline[] = ['$project' => ['doc_id' => 1, 'msg' => '$msgs.msg']];
|
||||
|
||||
$command = new \MongoDB\Driver\Command([
|
||||
'aggregate' => 'msg',
|
||||
'pipeline' => $pipeline,
|
||||
'cursor' => new \stdClass
|
||||
]);
|
||||
$cursor = $this->oldManager->executeCommand('tettt', $command);
|
||||
|
||||
$messages = [];
|
||||
foreach ($cursor as $doc) {
|
||||
$messages[] = $this->bsonToArray($doc);
|
||||
}
|
||||
|
||||
$this->stats['messages']['total'] = count($messages);
|
||||
$this->log($output, "📊 找到 {$this->stats['messages']['total']} 条消息");
|
||||
|
||||
$processed = 0;
|
||||
foreach ($messages as $doc) {
|
||||
$processed++;
|
||||
$msg = $doc['msg'] ?? [];
|
||||
|
||||
if (empty($msg)) {
|
||||
$this->stats['messages']['skipped']++;
|
||||
continue;
|
||||
}
|
||||
|
||||
$sendID = (string)($msg['send_id'] ?? $msg['sendID'] ?? '');
|
||||
$recvID = (string)($msg['recv_id'] ?? $msg['recvID'] ?? '');
|
||||
$groupID = (string)($msg['group_id'] ?? $msg['groupID'] ?? '');
|
||||
$contentType = (int)($msg['content_type'] ?? $msg['contentType'] ?? 101);
|
||||
$sessionType = (int)($msg['session_type'] ?? $msg['sessionType'] ?? 1);
|
||||
|
||||
if (in_array($sendID, $this->skipUsers)) {
|
||||
$this->stats['messages']['skipped']++;
|
||||
continue;
|
||||
}
|
||||
if ($sessionType == 3 && in_array($groupID, $this->skipGroups)) {
|
||||
$this->stats['messages']['skipped']++;
|
||||
continue;
|
||||
}
|
||||
|
||||
// 跳过特殊消息类型(如系统通知等)
|
||||
if (in_array($contentType, [200, 201, 202, 203, 204, 205])) {
|
||||
$this->stats['messages']['skipped']++;
|
||||
continue;
|
||||
}
|
||||
|
||||
$progress = sprintf("[消息 %d/%d]", $processed, $this->stats['messages']['total']);
|
||||
|
||||
if ($processed % 100 == 0 || $processed == 1) {
|
||||
$this->log($output, "{$progress} 处理中...");
|
||||
}
|
||||
|
||||
try {
|
||||
$this->log($output, "{$progress} 发送消息: sendID={$sendID}, recvID={$recvID}, groupID={$groupID}, contentType={$contentType}, sessionType={$sessionType}");
|
||||
$result = $this->sendMessage($msg);
|
||||
$this->log($output, "{$progress} API返回: " . json_encode($result, JSON_UNESCAPED_UNICODE));
|
||||
|
||||
if ($result['success'] ?? false) {
|
||||
$this->stats['messages']['success']++;
|
||||
if ($processed % 100 == 0) {
|
||||
$this->log($output, "{$progress} ✅ 发送成功");
|
||||
}
|
||||
} else {
|
||||
$this->stats['messages']['failed']++;
|
||||
$this->log($output, "{$progress} ❌ 发送失败: " . ($result['errMsg'] ?? '未知错误'));
|
||||
// 遇到NotInGroupYetError时跳过,继续迁移其他消息
|
||||
if (strpos(($result['errMsg'] ?? ''), 'NotInGroupYetError') === false) {
|
||||
// 遇到其他错误时退出
|
||||
throw new \Exception("消息发送失败: " . ($result['errMsg'] ?? '未知错误'));
|
||||
} else {
|
||||
$this->log($output, "{$progress} ℹ️ 跳过NotInGroupYetError错误,继续迁移");
|
||||
}
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
$this->stats['messages']['failed']++;
|
||||
$this->log($output, "{$progress} ❌ 发送异常: " . $e->getMessage());
|
||||
// 遇到NotInGroupYetError异常时跳过,继续迁移其他消息
|
||||
if (strpos($e->getMessage(), 'NotInGroupYetError') === false) {
|
||||
// 遇到其他异常时退出
|
||||
throw $e;
|
||||
} else {
|
||||
$this->log($output, "{$progress} ℹ️ 跳过NotInGroupYetError异常,继续迁移");
|
||||
}
|
||||
}
|
||||
if ($this->delay > 0) {
|
||||
usleep($this->delay * 1000);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function sendMessage(array $msg): array
|
||||
{
|
||||
$sendID = (string)($msg['send_id'] ?? $msg['sendID'] ?? '');
|
||||
$recvID = (string)($msg['recv_id'] ?? $msg['recvID'] ?? '');
|
||||
$groupID = (string)($msg['group_id'] ?? $msg['groupID'] ?? '');
|
||||
$contentType = (int)($msg['content_type'] ?? $msg['contentType'] ?? 101);
|
||||
$sessionType = (int)($msg['session_type'] ?? $msg['sessionType'] ?? 1);
|
||||
$sendTime = (int)($msg['send_time'] ?? $msg['sendTime'] ?? 0);
|
||||
$content = $msg['content'] ?? '';
|
||||
$ex = (string)($msg['ex'] ?? '');
|
||||
|
||||
if (empty($sendID)) {
|
||||
return ['success' => false, 'errMsg' => 'sendID为空'];
|
||||
}
|
||||
|
||||
$contentData = $this->parseContent($content, $contentType);
|
||||
|
||||
// 构建消息数据
|
||||
$messageData = [
|
||||
'content' => $contentData,
|
||||
'contentType' => $contentType,
|
||||
'sendTime' => $sendTime,
|
||||
'ex' => $ex,
|
||||
'isOnlineOnly' => false,
|
||||
'notOfflinePush' => true
|
||||
];
|
||||
|
||||
// 根据会话类型调用不同的发送方法
|
||||
if ($sessionType == 1 && !empty($recvID)) {
|
||||
// 单聊
|
||||
$result = $this->sdk->message->sendSingleMessage($sendID, $recvID, $messageData);
|
||||
} elseif (!empty($groupID)) {
|
||||
// 群聊
|
||||
$result = $this->sdk->message->sendGroupMessage($sendID, $groupID, $messageData);
|
||||
} else {
|
||||
return ['success' => false, 'errMsg' => '缺少必要的参数'];
|
||||
}
|
||||
|
||||
return [
|
||||
'success' => !($result['errCode'] ?? 0),
|
||||
'errMsg' => $result['errMsg'] ?? ''
|
||||
];
|
||||
}
|
||||
|
||||
private function parseContent($content, int $contentType): array
|
||||
{
|
||||
if (is_string($content)) {
|
||||
$decoded = json_decode($content, true);
|
||||
if (json_last_error() === JSON_ERROR_NONE && is_array($decoded)) {
|
||||
return $decoded;
|
||||
}
|
||||
return ['content' => $content, 'text' => $content];
|
||||
}
|
||||
if (is_array($content)) {
|
||||
return $content;
|
||||
}
|
||||
return ['content' => '', 'text' => ''];
|
||||
}
|
||||
|
||||
private function bsonToArray($data): array
|
||||
{
|
||||
if ($data instanceof \MongoDB\Model\BSONArray) {
|
||||
return $data->getArrayCopy();
|
||||
}
|
||||
if ($data instanceof \MongoDB\Model\BSONDocument) {
|
||||
return $data->getArrayCopy();
|
||||
}
|
||||
if (is_object($data)) {
|
||||
return json_decode(json_encode($data), true);
|
||||
}
|
||||
return is_array($data) ? $data : [];
|
||||
}
|
||||
|
||||
private function printStats(OutputInterface $output): void
|
||||
{
|
||||
$this->log($output, "");
|
||||
$this->log($output, "╔════════════════════════════════════════════════════════════╗");
|
||||
$this->log($output, "║ 迁移统计报告 ║");
|
||||
$this->log($output, "╠════════════════════════════════════════════════════════════╣");
|
||||
$this->log($output, "║ 用户: 总数 {$this->stats['users']['total']}, 成功 {$this->stats['users']['success']}, 失败 {$this->stats['users']['failed']}");
|
||||
$this->log($output, "║ 群组: 总数 {$this->stats['groups']['total']}, 成功 {$this->stats['groups']['success']}, 失败 {$this->stats['groups']['failed']}");
|
||||
$this->log($output, "║ 成员: 总数 {$this->stats['members']['total']}, 成功 {$this->stats['members']['success']}, 失败 {$this->stats['members']['failed']}");
|
||||
$this->log($output, "║ 消息: 总数 {$this->stats['messages']['total']}, 成功 {$this->stats['messages']['success']}, 失败 {$this->stats['messages']['failed']}, 跳过 {$this->stats['messages']['skipped']}");
|
||||
$this->log($output, "╚════════════════════════════════════════════════════════════╝");
|
||||
}
|
||||
|
||||
private function log(OutputInterface $output, string $message): void
|
||||
{
|
||||
$output->writeln($message);
|
||||
}
|
||||
/**
|
||||
* 获取OpenIM SDK实例
|
||||
*
|
||||
* @return object
|
||||
*/
|
||||
function getSdk()
|
||||
{
|
||||
if ($this->sdk) {
|
||||
return $this->sdk;
|
||||
}
|
||||
|
||||
$this->sdk = new \support\OpenImSdk\Client([
|
||||
'host' => config('openim.server'),
|
||||
'secret' => config('openim.secret'),
|
||||
]);
|
||||
|
||||
return $this->sdk;
|
||||
}
|
||||
|
||||
private function initConnections(OutputInterface $output): void
|
||||
{
|
||||
$this->log($output, "正在初始化连接...");
|
||||
|
||||
$this->getSdk();
|
||||
|
||||
$uri = 'mongodb://commie:n1e5a6s6m7@127.0.0.1:37017/tettt?authSource=admin';
|
||||
$this->oldManager = new \MongoDB\Driver\Manager($uri);
|
||||
|
||||
$this->log($output, "✅ 连接成功");
|
||||
}
|
||||
|
||||
private function cleanExistingData(OutputInterface $output,$collections=[]): void
|
||||
{
|
||||
// 记录开始清理数据的日志信息
|
||||
$this->log($output, "\n═════════════════ 清理现有数据 ═════════════════");
|
||||
$this->log($output, "");
|
||||
|
||||
// 构建新数据库(OpenIM v3)的MongoDB连接URI
|
||||
$uri = 'mongodb://commie:n1e5a6s6m7@127.0.0.1:37017/openim_v3?authSource=admin';
|
||||
// 创建MongoDB驱动管理器实例,用于操作新数据库
|
||||
$this->newManager = new \MongoDB\Driver\Manager($uri);
|
||||
try {
|
||||
// 记录开始清理数据的状态
|
||||
$this->log($output, "正在清理mongodb数据...");
|
||||
// 定义需要清空的数据集合列表(保留user集合,清空其他所有业务数据)
|
||||
// 遍历所有需要清理的集合,逐个执行清空操作
|
||||
foreach ($collections as $collection) {
|
||||
try {
|
||||
// 创建批量写入操作对象
|
||||
$bulk = new \MongoDB\Driver\BulkWrite;
|
||||
// 添加删除所有文档的操作(空条件表示删除全部)
|
||||
$bulk->delete([]);
|
||||
// 执行批量删除操作,指定数据库和集合名称
|
||||
$this->newManager->executeBulkWrite('openim_v3.' . $collection, $bulk);
|
||||
// 记录该集合清空成功的日志
|
||||
$this->log($output, "已清空集合: {$collection}");
|
||||
} catch (\Exception $e) {
|
||||
// 单个集合清空失败时记录警告信息,不影响其他集合的清理
|
||||
$this->log($output, "⚠️ 清空集合 {$collection} 失败: " . $e->getMessage());
|
||||
}
|
||||
}
|
||||
$this->log($output, "正在清理redis数据...");
|
||||
$redis = new \Redis();
|
||||
$host = '127.0.0.1';
|
||||
$port = 16379;
|
||||
$password = 'n1e5a6s6m7';
|
||||
|
||||
$output->writeln("连接 Redis: {$host}:{$port}");
|
||||
|
||||
if ($redis->connect($host, $port)) {
|
||||
if (!empty($password)) {
|
||||
$redis->auth($password);
|
||||
}
|
||||
|
||||
$result = $redis->flushAll();
|
||||
|
||||
if ($result) {
|
||||
$output->writeln("✅ Redis 清空成功");
|
||||
} else {
|
||||
$output->writeln("❌ Redis 清空失败");
|
||||
}
|
||||
} else {
|
||||
$output->writeln("❌ 无法连接到 Redis");
|
||||
}
|
||||
// 记录所有数据清理完成的日志
|
||||
$this->log($output, "✅ 数据清理完成");
|
||||
} catch (\Exception $e) {
|
||||
// 捕获整体清理过程中的异常,记录错误但不抛出,确保程序继续执行
|
||||
$this->log($output, "❌ 清理数据失败: " . $e->getMessage());
|
||||
// 不抛出异常,继续执行
|
||||
}
|
||||
}
|
||||
|
||||
private function queryOldDb(string $collection, array $filter = [], array $options = []): array
|
||||
{
|
||||
$query = new \MongoDB\Driver\Query($filter, $options);
|
||||
$cursor = $this->oldManager->executeQuery('tettt.' . $collection, $query);
|
||||
$result = [];
|
||||
foreach ($cursor as $doc) {
|
||||
$result[] = $this->bsonToArray($doc);
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 备份MongoDB数据
|
||||
* @param OutputInterface $output
|
||||
* @param string $step
|
||||
* @return string
|
||||
*/
|
||||
private function backupMongoDB(OutputInterface $output, string $step): string
|
||||
{
|
||||
$this->log($output, "═════════════════ 备份MongoDB数据 ═════════════════");
|
||||
|
||||
// 确保备份目录存在
|
||||
if (!is_dir($this->backupDir)) {
|
||||
mkdir($this->backupDir, 0755, true);
|
||||
}
|
||||
|
||||
// 生成备份文件名
|
||||
$timestamp = date('YmdHis');
|
||||
$backupFile = "{$this->backupDir}/openim_v3_{$step}_{$timestamp}.json";
|
||||
|
||||
try {
|
||||
// 使用现有的新数据库连接
|
||||
if (!$this->newManager) {
|
||||
$uri = 'mongodb://commie:n1e5a6s6m7@127.0.0.1:37017/openim_v3?authSource=admin';
|
||||
$this->newManager = new \MongoDB\Driver\Manager($uri);
|
||||
}
|
||||
|
||||
// 获取所有集合
|
||||
$command = new \MongoDB\Driver\Command(['listCollections' => 1]);
|
||||
$cursor = $this->newManager->executeCommand('openim_v3', $command);
|
||||
$collections = $cursor->toArray();
|
||||
|
||||
//$this->log($output, "找到 " . count($collections) . " 个集合");
|
||||
|
||||
$backupData = [];
|
||||
|
||||
// 备份每个集合
|
||||
foreach ($collections as $collection) {
|
||||
$collectionName = $collection->name;
|
||||
if (in_array($collectionName, ['system.indexes', 'system.profile'])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
//$this->log($output, "备份集合: {$collectionName}");
|
||||
|
||||
$query = new \MongoDB\Driver\Query([]);
|
||||
$cursor = $this->newManager->executeQuery('openim_v3.' . $collectionName, $query);
|
||||
|
||||
$documents = [];
|
||||
foreach ($cursor as $doc) {
|
||||
$document = $this->bsonToArray($doc);
|
||||
// 处理ObjectId
|
||||
if (isset($document['_id']) && is_array($document['_id']) && isset($document['_id']['$oid'])) {
|
||||
$document['_id'] = $document['_id']['$oid'];
|
||||
}
|
||||
$documents[] = $document;
|
||||
}
|
||||
|
||||
if (!empty($documents)) {
|
||||
$backupData[$collectionName] = $documents;
|
||||
//$this->log($output, " - 备份了 " . count($documents) . " 条记录");
|
||||
}
|
||||
}
|
||||
|
||||
// 保存备份文件
|
||||
file_put_contents($backupFile, json_encode($backupData, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT));
|
||||
|
||||
$this->log($output, "✅ 备份成功: {$backupFile} (" . filesize($backupFile) . " 字节)");
|
||||
$this->currentBackup = $backupFile;
|
||||
return $backupFile;
|
||||
} catch (\Exception $e) {
|
||||
$this->log($output, "❌ 备份失败: " . $e->getMessage());
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 回滚MongoDB数据
|
||||
* @param OutputInterface $output
|
||||
* @param string $backupFile
|
||||
* @return bool
|
||||
*/
|
||||
private function restoreMongoDB(OutputInterface $output, string $backupFile): bool
|
||||
{
|
||||
$this->log($output, "═════════════════ 回滚MongoDB数据 ═════════════════");
|
||||
|
||||
if (!file_exists($backupFile)) {
|
||||
$this->log($output, "❌ 备份文件不存在: {$backupFile}");
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
// 读取备份文件
|
||||
$backupData = json_decode(file_get_contents($backupFile), true);
|
||||
if (empty($backupData)) {
|
||||
$this->log($output, "❌ 备份文件为空");
|
||||
return false;
|
||||
}
|
||||
|
||||
// 连接到新数据库
|
||||
$uri = 'mongodb://commie:n1e5a6s6m7@127.0.0.1:37017/openim_v3?authSource=admin';
|
||||
$manager = new \MongoDB\Driver\Manager($uri);
|
||||
|
||||
// 清空所有集合
|
||||
$this->log($output, "清空现有数据...");
|
||||
foreach (array_keys($backupData) as $collectionName) {
|
||||
$bulk = new \MongoDB\Driver\BulkWrite;
|
||||
$bulk->delete([]);
|
||||
$manager->executeBulkWrite('openim_v3.' . $collectionName, $bulk);
|
||||
}
|
||||
|
||||
// 恢复数据
|
||||
foreach ($backupData as $collectionName => $documents) {
|
||||
$documentCount = count($documents);
|
||||
$this->log($output, "恢复集合: {$collectionName} ({$documentCount} 条记录)");
|
||||
|
||||
$bulk = new \MongoDB\Driver\BulkWrite;
|
||||
foreach ($documents as $document) {
|
||||
// 处理_id字段
|
||||
if (isset($document['_id']) && is_string($document['_id'])) {
|
||||
// 尝试创建ObjectId
|
||||
try {
|
||||
$document['_id'] = new \MongoDB\BSON\ObjectId($document['_id']);
|
||||
} catch (\Exception $e) {
|
||||
// 如果不是有效的ObjectId格式,保持原样
|
||||
}
|
||||
}
|
||||
$bulk->insert($document);
|
||||
}
|
||||
|
||||
$result = $manager->executeBulkWrite('openim_v3.' . $collectionName, $bulk);
|
||||
$this->log($output, "恢复成功: {$result->getInsertedCount()} 条记录");
|
||||
}
|
||||
|
||||
$this->log($output, "✅ 回滚成功");
|
||||
return true;
|
||||
} catch (\Exception $e) {
|
||||
$this->log($output, "❌ 回滚失败: " . $e->getMessage());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,118 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace app\command;
|
||||
|
||||
use Symfony\Component\Console\Command\Command;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Input\InputOption;
|
||||
use Symfony\Component\Console\Input\InputArgument;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
use support\think\Db;
|
||||
|
||||
class OpenIm extends Command
|
||||
{
|
||||
protected static $defaultName = 'openim';
|
||||
protected static $defaultDescription = 'OpenIm';
|
||||
public $sdk= null;
|
||||
|
||||
/**
|
||||
* @return void
|
||||
*/
|
||||
protected function configure()
|
||||
{
|
||||
$this->addOption('action','a', InputArgument::OPTIONAL, '操作类型');
|
||||
}
|
||||
|
||||
/**
|
||||
* @param InputInterface $input
|
||||
* @param OutputInterface $output
|
||||
* @return int
|
||||
*/
|
||||
protected function execute(InputInterface $input, OutputInterface $output): int
|
||||
{
|
||||
$action = $input->getOption('action');
|
||||
if(!$action){
|
||||
$output->writeln('空操作');
|
||||
return self::FAILURE;
|
||||
}
|
||||
if(method_exists($this, $action)){
|
||||
return $this->$action($input, $output);
|
||||
}
|
||||
$output->writeln($action.'不存在');
|
||||
return self::FAILURE;
|
||||
}
|
||||
private function change_user(InputInterface $input, OutputInterface $output):int{
|
||||
|
||||
$im = $this->getSdk();
|
||||
$data = $im->user->updateUserInfo(idEncode('100006'),['userInfo'=>['userId'=>'wx100001']]);
|
||||
cp($data);
|
||||
return self::SUCCESS;
|
||||
}
|
||||
private function sync_users(InputInterface $input, OutputInterface $output):int{
|
||||
$im = $this->getSdk();
|
||||
$data = $im->user->getAllUsersUid(1,1000);
|
||||
cp($data);
|
||||
return self::SUCCESS;
|
||||
$exsit_user_ids = Db::name('user')->whereIn('id',$data['userIDs'])->column('id');
|
||||
$not_exsit_user_ids =array_diff($data['userIDs'],$exsit_user_ids);
|
||||
if(count($not_exsit_user_ids)> 0){
|
||||
//同步用户
|
||||
$res = $im->user->getUsersInfo($not_exsit_user_ids);
|
||||
$save_data = [];
|
||||
foreach($res['usersInfo'] as $k=>$_user){
|
||||
array_push($save_data,[
|
||||
'id' => $_user['userID'],
|
||||
'nickname' => $_user['nickname'],
|
||||
'password' => '123456',
|
||||
'avatar' => $_user['faceURL']
|
||||
]);
|
||||
|
||||
// "ex": "",
|
||||
// "createTime": 1688454168302,
|
||||
// "appMangerLevel": 18,
|
||||
// "globalRecvMsgOpt": 0
|
||||
}
|
||||
if(!empty($save_data)){
|
||||
Db::name('user')->insertAll($save_data);
|
||||
}
|
||||
}
|
||||
return self::SUCCESS;
|
||||
}
|
||||
|
||||
function sync_cache(){
|
||||
$res = \app\model\Openim\Group::field('group_id,creator_user_id')->select();
|
||||
|
||||
$group_create = [];
|
||||
foreach($res as $v){
|
||||
if(!isset($group_create[$v['creator_user_id']])){
|
||||
$group_create[$v['creator_user_id']] = 0;
|
||||
}
|
||||
if($v['status'] != 2){
|
||||
$group_create[$v['creator_user_id']]+=1;
|
||||
}
|
||||
cache('group_owner_'.$v['group_id'],$v['creator_user_id']);
|
||||
$group_user_count = \app\model\Openim\GroupMember::field('group_id,count(*) as count')
|
||||
->where('group_id',$v['group_id'])
|
||||
->count('user_id');
|
||||
cp('群组数量',$v['group_id'],'成员数量:',$group_user_count);
|
||||
cache('group_'.$v['group_id'].'_user_count',$group_user_count);
|
||||
|
||||
}
|
||||
//cp($group_create);
|
||||
foreach($group_create as $userId =>$count){
|
||||
cp('用户:',$userId,'创建群组数量:',$count);
|
||||
cache('user_'.$userId.'_create_group_count',$count);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
function getSdk(){
|
||||
if($this->sdk){
|
||||
return $this->sdk;
|
||||
}
|
||||
$this->sdk = new \support\OpenImSdk\Client([
|
||||
'host' => 'http://127.0.0.1:10002', // OpenIM API地址
|
||||
'secret' => 'n1e5a6s6m7', // OpenIM密钥
|
||||
]);
|
||||
return $this->sdk;
|
||||
}
|
||||
}
|
||||
Executable
+1620
File diff suppressed because it is too large
Load Diff
@@ -1,48 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace app\command;
|
||||
|
||||
use Symfony\Component\Console\Command\Command;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Input\InputOption;
|
||||
use Symfony\Component\Console\Input\InputArgument;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
use support\think\Db;
|
||||
|
||||
|
||||
class Otop extends Command
|
||||
{
|
||||
protected static $defaultName = 'Otop';
|
||||
protected static $defaultDescription = '结算';
|
||||
|
||||
/**
|
||||
* @return void
|
||||
*/
|
||||
protected function configure()
|
||||
{
|
||||
$this->addOption('user_id','u', InputArgument::OPTIONAL, 'user_id');
|
||||
}
|
||||
|
||||
/**
|
||||
* @param InputInterface $input
|
||||
* @param OutputInterface $output
|
||||
* @return int
|
||||
*/
|
||||
protected function execute(InputInterface $input, OutputInterface $output): int
|
||||
{
|
||||
$user_id = $input->getOption('user_id');
|
||||
if(!$user_id){
|
||||
return false;
|
||||
}
|
||||
/**
|
||||
* @var \plugin\admin\app\model\Admin $admin
|
||||
*/
|
||||
$admin = \plugin\admin\app\model\Admin::where('id',$user_id)->find();
|
||||
if(!$admin){
|
||||
return false;
|
||||
}
|
||||
$totp = \OTPHP\TOTP::create($admin->totp_secret);
|
||||
cp($totp->now());
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
@@ -1,36 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace app\command;
|
||||
|
||||
use Symfony\Component\Console\Command\Command;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Input\InputOption;
|
||||
use Symfony\Component\Console\Input\InputArgument;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
use support\think\Db;
|
||||
|
||||
|
||||
class Settlement extends Command
|
||||
{
|
||||
protected static $defaultName = 'Settlement';
|
||||
protected static $defaultDescription = '结算';
|
||||
|
||||
/**
|
||||
* @return void
|
||||
*/
|
||||
protected function configure()
|
||||
{
|
||||
$this->addArgument('name', InputArgument::OPTIONAL, 'Name description');
|
||||
}
|
||||
|
||||
/**
|
||||
* @param InputInterface $input
|
||||
* @param OutputInterface $output
|
||||
* @return int
|
||||
*/
|
||||
protected function execute(InputInterface $input, OutputInterface $output): int
|
||||
{
|
||||
(new \support\Settlement())->autoSettlement();
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
+36
-65
@@ -37,10 +37,11 @@ class Tongji extends Command
|
||||
*/
|
||||
protected function execute(InputInterface $input, OutputInterface $output): int
|
||||
{
|
||||
$this->question($input,$output);
|
||||
//$this->role($input,$output);
|
||||
//$this->recharge($input,$output);
|
||||
//$this->withdrawl($input,$output);
|
||||
$action = $input->getOption('action');
|
||||
if(method_exists($this, $action)){
|
||||
return $this->$action($input, $output);
|
||||
}
|
||||
cp('操作不存在:'.$action);
|
||||
return self::SUCCESS;
|
||||
}
|
||||
/**
|
||||
@@ -119,69 +120,39 @@ class Tongji extends Command
|
||||
}
|
||||
cp($withdrawl_result);
|
||||
}
|
||||
function question(InputInterface $input, OutputInterface $output) {
|
||||
$order_list = \app\model\ProductOrder::withJoin([
|
||||
'product'=>function($q){
|
||||
return $q->field('price,interest_rate');
|
||||
}
|
||||
])->where('quantity','>',0)->select();
|
||||
}
|
||||
/**
|
||||
* 修复所有角色购买统计
|
||||
* 修复团队统计数据
|
||||
* @param InputInterface $input
|
||||
* @param OutputInterface $output
|
||||
* @return int
|
||||
*/
|
||||
function role(InputInterface $input, OutputInterface $output) {
|
||||
function build_team(InputInterface $input, OutputInterface $output){
|
||||
$list = Db::name('user')->order('id','asc')->column('id');
|
||||
//$list = [['id'=>100006]];
|
||||
foreach($list as $k=>$user_id){
|
||||
//team_total
|
||||
$team_user_ids = Db::name('user_team')->where('ancestor_id',$user_id)
|
||||
->where('depth','>',0)
|
||||
->order('depth','ASC')
|
||||
->column('descendant_id');
|
||||
Db::name('user_extend')->where('user_id',$user_id)->data([
|
||||
'team_total'=> count($team_user_ids)
|
||||
])->save();
|
||||
cache('team_user_count_'.$user_id,count($team_user_ids));
|
||||
|
||||
$direct_use_count = Db::name('user')->where('parent_id',$user_id)->count('id');
|
||||
$vip_user_count = Db::name('user')->whereIn('id',$team_user_ids)->where('role_id','>',1)->count('id');
|
||||
|
||||
Db::name('user_extend')->where('user_id',$user_id)->data([
|
||||
'direct_total'=> $direct_use_count,
|
||||
'vip_total'=> $vip_user_count
|
||||
])->save();
|
||||
cache('team_direct_total_'.$user_id,$direct_use_count);
|
||||
cache('team_vip_total_'.$user_id,$vip_user_count);
|
||||
update_user_level($user_id,$vip_user_count);
|
||||
cp($user_id.'完成');
|
||||
}
|
||||
|
||||
}
|
||||
/**
|
||||
* 模拟用户注册
|
||||
*/
|
||||
function register(InputInterface $input, OutputInterface $output){
|
||||
$last_user_id = UserModel::order('id','desc')->limit(1)->value('id');
|
||||
for ($i=$last_user_id+1; $i <= $last_user_id+2; $i++) {
|
||||
$uids = UserModel::where("status",1)->column('id');
|
||||
$referrerId = $uids[array_rand($uids)];
|
||||
$email = 'test'.$i.'@msn.cn';
|
||||
$mobile = '';
|
||||
$password = '123456';
|
||||
$extends = [
|
||||
'role_id' => rand(1,3),
|
||||
'money' => 0,
|
||||
'parent_id' => $referrerId
|
||||
];
|
||||
$user = \support\Jwt::register($email, $password, $email, $mobile, $extends);
|
||||
cp($user['id']);
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
function updateRechargeAddress(InputInterface $input, OutputInterface $output){
|
||||
$saveData = [];
|
||||
$res = post(Config('pay.server').'/RechargeAddress/create',['appid'=>Config('pay.appid')]);
|
||||
if($res){
|
||||
$res = json_decode($res,true);
|
||||
if($res['code'] === 0){
|
||||
$saveData['bep_recharge_address'] = $res['data']['BEP-20']['address'];
|
||||
$saveData['trc_recharge_address'] = $res['data']['TRC-20']['address'];
|
||||
$saveData['decimal_part'] = $res['data']['BEP-20']['decimal_part'];
|
||||
}
|
||||
}
|
||||
UserModel::where('id',123409)->update($saveData);
|
||||
return 0;
|
||||
}
|
||||
function otop(){
|
||||
$secret = 'EJGYB7OZR2W46XRX7VB3PXHSOY4LUAWCA5GTDAVTWKHXNDAAAIIP7AQ3JSO3XZJNX5J5OTIDEQVKLYFYIYNAXSCYF4GNZ2EMA4ORA3Y';
|
||||
$totp = \OTPHP\TOTP::create($secret);
|
||||
$secret = $totp->getSecret();
|
||||
$totp->setLabel('cansnow');
|
||||
$totp->setIssuer('DVPN');
|
||||
$qrCodeUri =$totp->getProvisioningUri();
|
||||
cp($secret);
|
||||
cp($qrCodeUri);
|
||||
cp('https://api.qrtool.cn/?text='.urlencode($qrCodeUri));
|
||||
cp($totp->at(time()));
|
||||
if ($totp->verify('535714')) {
|
||||
cp('验证成功');
|
||||
} else {
|
||||
cp('验证失败');
|
||||
}
|
||||
return self::SUCCESS;
|
||||
}
|
||||
}
|
||||
|
||||
+101
-21
@@ -15,14 +15,15 @@ class User extends Command
|
||||
{
|
||||
protected static $defaultName = 'User';
|
||||
protected static $defaultDescription = '用户';
|
||||
public $sdk= null;
|
||||
|
||||
/**
|
||||
* @return void
|
||||
*/
|
||||
protected function configure()
|
||||
{
|
||||
$this->addOption('user_id','u', InputArgument::OPTIONAL, 'user_id');
|
||||
$this->addOption('action','a', InputArgument::OPTIONAL, '操作类型');
|
||||
$this->addOption('user_id','u', InputOption::VALUE_OPTIONAL, 'user_id');
|
||||
$this->addOption('action','a', InputOption::VALUE_OPTIONAL, '操作类型','test');
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -32,26 +33,105 @@ class User extends Command
|
||||
*/
|
||||
protected function execute(InputInterface $input, OutputInterface $output): int
|
||||
{
|
||||
$action = $input->getOption('action','login');
|
||||
if($action == 'login'){
|
||||
$IM = new \support\OpenImSdk\Client([
|
||||
'host' => 'http://127.0.0.1:10002', // OpenIM API地址
|
||||
'secret' => 'n1e5a6s6m7', // OpenIM密钥
|
||||
]);
|
||||
$user_id = $input->getOption('user_id');
|
||||
if(!$user_id){
|
||||
return false;
|
||||
}
|
||||
$user = \support\Jwt::direct($user_id);
|
||||
$imToken = $IM->auth->getUserToken(idEncode($user['id']),2);
|
||||
cp('userID:' . $user['id']);
|
||||
cp('nickname:' . $user['nickname']);
|
||||
cp('token:' . $user['token']);
|
||||
cp('imToken:' . $imToken['token']);
|
||||
return 0;
|
||||
$action = $input->getOption('action');
|
||||
if(method_exists($this, $action)){
|
||||
return $this->$action($input, $output);
|
||||
}
|
||||
cp('action not exist');
|
||||
cp('操作不存在:'.$action);
|
||||
return 0;
|
||||
|
||||
}
|
||||
function update_password(InputInterface $input, OutputInterface $output){
|
||||
$newpassword = \plugin\admin\app\common\Util::passwordHash(MD5('qwe123'));
|
||||
Db::name('User')->where('id','>',0)->save([
|
||||
'loginfailure' => 0,
|
||||
'password' => $newpassword
|
||||
]);
|
||||
return 0;
|
||||
}
|
||||
function login(InputInterface $input, OutputInterface $output){
|
||||
$user_id = $input->getOption('user_id');
|
||||
if(!$user_id){
|
||||
return false;
|
||||
}
|
||||
$user = \support\Jwt::direct($user_id);
|
||||
//$imToken = $IM->auth->getUserToken($user['userID'],2);
|
||||
cp('userID:' . $user['id']);
|
||||
cp('nickname:' . $user['nickname']);
|
||||
cp('token:' . $user['token']);
|
||||
//cp('imToken:' . $imToken['token']);
|
||||
return 0;
|
||||
}
|
||||
function otop(InputInterface $input, OutputInterface $output){
|
||||
$user_id = $input->getOption('user_id');
|
||||
if(!$user_id){
|
||||
return false;
|
||||
}
|
||||
/**
|
||||
* @var \plugin\admin\app\model\Admin $admin
|
||||
*/
|
||||
$admin = \plugin\admin\app\model\Admin::where('id',$user_id)->find();
|
||||
if(!$admin){
|
||||
return false;
|
||||
}
|
||||
$totp = \OTPHP\TOTP::create($admin->totp_secret);
|
||||
cp($totp->now());
|
||||
return 1;
|
||||
}
|
||||
//重建user_team
|
||||
function build_team(){
|
||||
Db::name('user_team')->where('ancestor_id','>',0)->delete();
|
||||
$list = Db::name('user')->field('id,parent_id')->order('id','asc')->select();
|
||||
foreach($list as $k=>$user){
|
||||
build_user_team($user);
|
||||
cp('user_id:'.$user['id']);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
function register(InputInterface $input, OutputInterface $output){
|
||||
$im = $this->getSdk();
|
||||
try {
|
||||
for($i=313;$i<333;$i++){
|
||||
$mobile = (12600000000+$i).'';
|
||||
$password = \support\Random::build('23456789abcdefghjklmnpqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ',8);
|
||||
$data = [
|
||||
'mobile' => $mobile,
|
||||
'username' => $mobile,
|
||||
'region' => '86',
|
||||
'nickname' => $mobile,
|
||||
'role_id' => 1,
|
||||
'group_id' => 0,
|
||||
'password' => \plugin\admin\app\common\Util::passwordHash(md5($password)),
|
||||
'avatar' => '/static/img/avatar.png',
|
||||
'created_at' => time(),
|
||||
'updated_at' => time(),
|
||||
'status' => 1,
|
||||
];
|
||||
$user_id = Db::name('user')->insertGetId($data);
|
||||
$userID = \support\Encrypt::userIDencode($user_id);
|
||||
Db::name('user')->where('id',$user_id)->update([
|
||||
'userID'=>$userID
|
||||
]);
|
||||
$im->user->userRegister($userID,$data['nickname'],cdnurl($data['avatar']));
|
||||
$user = Db::name('user')->where('id',$user_id)->find();
|
||||
Hook('user.register_successed',$user);
|
||||
cp($user_id,$data['mobile'],$password);
|
||||
}
|
||||
return 0;
|
||||
} catch (\Exception $e) {
|
||||
//throw $th;
|
||||
cp($e->getMessage());
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
protected function getSdk(){
|
||||
if($this->sdk){
|
||||
return $this->sdk;
|
||||
}
|
||||
$this->sdk = new \support\OpenImSdk\Client([
|
||||
'host' => config('openim.server'), // OpenIM API地址
|
||||
'secret' => config('openim.secret'), // OpenIM密钥
|
||||
]);
|
||||
return $this->sdk;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -37,7 +37,7 @@ class Handler extends ExceptionHandler
|
||||
public function render(Request $request, Throwable $exception): Response
|
||||
{
|
||||
$code = $exception->getCode();
|
||||
$json = ['code' => $code ?: 500, 'msg' => $exception->getMessage()];
|
||||
$json = ['code' => $code ?: 500, 'msg' => __($exception->getMessage())];
|
||||
return new Response(200, ['Content-Type' => 'application/json'],
|
||||
json_encode($json, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES));
|
||||
|
||||
|
||||
@@ -59,7 +59,6 @@ class GitController
|
||||
$script_fn = 'wjb_cdkey.sh';
|
||||
}
|
||||
}
|
||||
//log_alert($script_fn);
|
||||
if(!$script_fn){
|
||||
return response('Not main branch', 200);
|
||||
}
|
||||
|
||||
@@ -20,20 +20,8 @@ class HookController{
|
||||
$userID= Input('userID');
|
||||
$nickname= Input('nickname');
|
||||
$users = Input('users');
|
||||
$im = new \support\OpenImSdk\Client([
|
||||
'host' => 'http://127.0.0.1:10002', // OpenIM API地址
|
||||
'secret' => 'n1e5a6s6m7', // OpenIM密钥
|
||||
]);
|
||||
foreach($users as $k=>$v){
|
||||
|
||||
// 'userID' => '0EO9K107ON',
|
||||
// 'nickname' => '坏蛋',
|
||||
// 'faceURL' => '/static/img/avatar.png',
|
||||
// 'ex' => '',
|
||||
// 'createTime' => 0,
|
||||
// 'appMangerLevel' => 0,
|
||||
// 'globalRecvMsgOpt' => 0,
|
||||
$im->message->sendBusinessNotification('official_team',$v['userID'],[
|
||||
$this->getSdk()->message->sendBusinessNotification('SystemOfficialTeam',$v['userID'],[
|
||||
'contentType' => 101,
|
||||
'textElem' => [
|
||||
'content' => '欢迎使用'.Config('site.name')
|
||||
@@ -153,7 +141,6 @@ class HookController{
|
||||
public function callbackAfterUserOfflineCommand(Request $request): Response{
|
||||
|
||||
$user_id = Input('userID');
|
||||
//Db::name('user')->where('id',$user_id)->update(['online'=>0]);
|
||||
return $this->success();
|
||||
}
|
||||
|
||||
@@ -179,6 +166,7 @@ class HookController{
|
||||
//执行顺序,callbackBeforeCreateGroupCommand -> callbackBeforeMembersJoinGroupCommand -> callbackAfterCreateGroupCommand
|
||||
public function callbackbeforeCreateGroupCommand(Request $request): Response
|
||||
{
|
||||
return $this->success();
|
||||
$groupID = Input('groupID');
|
||||
$creatorUserID = Input('creatorUserID');
|
||||
$key = 'user_'.$creatorUserID.'_create_group_count';
|
||||
@@ -244,7 +232,7 @@ class HookController{
|
||||
//群成员进群之前的回调
|
||||
public function callbackBeforeMembersJoinGroupCommand(Request $request):Response
|
||||
{
|
||||
log_alert(Input());
|
||||
return $this->success();
|
||||
$groupID = Input('groupID');
|
||||
$memberList = Input('memberList');
|
||||
$ownerID = $this->getGroupOwner($groupID);
|
||||
@@ -294,6 +282,7 @@ class HookController{
|
||||
//执行顺序,callbackBeforeInviteJoinGroupCommand -> callbackBeforeMembersJoinGroupCommand
|
||||
public function callbackBeforeInviteJoinGroupCommand(Request $request):Response
|
||||
{
|
||||
return $this->success();
|
||||
$groupID = Input('groupID');
|
||||
$invitedUserIDs = Input('invitedUserIDs');
|
||||
//获取群组当前用户数量
|
||||
@@ -313,6 +302,7 @@ class HookController{
|
||||
//申请加入群组之前的回调
|
||||
public function callbackBeforeJoinGroupCommand(Request $request):Response
|
||||
{
|
||||
return $this->success();
|
||||
$groupID = Input('groupID');
|
||||
$applyID = Input('applyID');
|
||||
//获取群组当前用户数量
|
||||
@@ -372,8 +362,8 @@ class HookController{
|
||||
return $this->sdk;
|
||||
}
|
||||
$this->sdk = new \support\OpenImSdk\Client([
|
||||
'host' => 'http://127.0.0.1:10002', // OpenIM API地址
|
||||
'secret' => 'n1e5a6s6m7', // OpenIM密钥
|
||||
'host' => config('openim.server'), // OpenIM API地址
|
||||
'secret' => config('openim.secret'), // OpenIM密钥
|
||||
]);
|
||||
return $this->sdk;
|
||||
}
|
||||
|
||||
@@ -55,6 +55,9 @@ class MetricsController extends Base
|
||||
// Redis指标
|
||||
$metrics[] = $this->getRedisMetrics();
|
||||
|
||||
// MongoDB指标
|
||||
$metrics[] = $this->getMongoDBMetrics();
|
||||
|
||||
// HTTP请求指标
|
||||
$metrics[] = $this->getHttpMetrics();
|
||||
|
||||
@@ -183,7 +186,9 @@ class MetricsController extends Base
|
||||
|
||||
// 尝试获取数据库状态
|
||||
$db = \think\facade\Db::connect();
|
||||
$status = $db->query('SHOW STATUS');
|
||||
|
||||
// 1. 全局状态指标
|
||||
$status = $db->query('SHOW GLOBAL STATUS');
|
||||
$statusMap = [];
|
||||
foreach ($status as $item) {
|
||||
$statusMap[$item['Variable_name']] = $item['Value'];
|
||||
@@ -205,6 +210,60 @@ class MetricsController extends Base
|
||||
if (isset($statusMap['Uptime'])) {
|
||||
$metrics[] = $this->formatCounter('webman_db_uptime_seconds', 'Database uptime in seconds', [], (int)$statusMap['Uptime']);
|
||||
}
|
||||
if (isset($statusMap['Innodb_buffer_pool_pages_data'])) {
|
||||
$metrics[] = $this->formatGauge('webman_db_innodb_buffer_pool_pages_data', 'Number of pages containing data', [], (int)$statusMap['Innodb_buffer_pool_pages_data']);
|
||||
}
|
||||
if (isset($statusMap['Innodb_buffer_pool_pages_free'])) {
|
||||
$metrics[] = $this->formatGauge('webman_db_innodb_buffer_pool_pages_free', 'Number of free pages', [], (int)$statusMap['Innodb_buffer_pool_pages_free']);
|
||||
}
|
||||
if (isset($statusMap['Innodb_buffer_pool_pages_total'])) {
|
||||
$metrics[] = $this->formatGauge('webman_db_innodb_buffer_pool_pages_total', 'Total number of pages', [], (int)$statusMap['Innodb_buffer_pool_pages_total']);
|
||||
}
|
||||
if (isset($statusMap['Innodb_row_reads'])) {
|
||||
$metrics[] = $this->formatCounter('webman_db_innodb_row_reads_total', 'Number of rows read', [], (int)$statusMap['Innodb_row_reads']);
|
||||
}
|
||||
if (isset($statusMap['Innodb_row_writes'])) {
|
||||
$metrics[] = $this->formatCounter('webman_db_innodb_row_writes_total', 'Number of rows written', [], (int)$statusMap['Innodb_row_writes']);
|
||||
}
|
||||
if (isset($statusMap['Com_select'])) {
|
||||
$metrics[] = $this->formatCounter('webman_db_com_select_total', 'Number of SELECT statements', [], (int)$statusMap['Com_select']);
|
||||
}
|
||||
if (isset($statusMap['Com_insert'])) {
|
||||
$metrics[] = $this->formatCounter('webman_db_com_insert_total', 'Number of INSERT statements', [], (int)$statusMap['Com_insert']);
|
||||
}
|
||||
if (isset($statusMap['Com_update'])) {
|
||||
$metrics[] = $this->formatCounter('webman_db_com_update_total', 'Number of UPDATE statements', [], (int)$statusMap['Com_update']);
|
||||
}
|
||||
if (isset($statusMap['Com_delete'])) {
|
||||
$metrics[] = $this->formatCounter('webman_db_com_delete_total', 'Number of DELETE statements', [], (int)$statusMap['Com_delete']);
|
||||
}
|
||||
|
||||
// 2. 数据库大小
|
||||
$databases = $db->query('SHOW DATABASES WHERE `Database` NOT IN ("information_schema", "mysql", "performance_schema", "sys")');
|
||||
foreach ($databases as $dbInfo) {
|
||||
$dbName = $dbInfo['Database'];
|
||||
$sizeResult = $db->query("SELECT table_schema AS 'database', SUM(data_length + index_length) AS 'size_bytes' FROM information_schema.tables WHERE table_schema = '{$dbName}' GROUP BY table_schema");
|
||||
if (isset($sizeResult[0]['size_bytes'])) {
|
||||
$metrics[] = $this->formatGauge('webman_db_database_size_bytes', 'Database size in bytes', [
|
||||
'database' => $dbName
|
||||
], (int)$sizeResult[0]['size_bytes']);
|
||||
}
|
||||
}
|
||||
|
||||
// 3. 表状态
|
||||
$tables = $db->query('SHOW TABLE STATUS FROM `' . ($dbConfig['database'] ?? 'test') . '`');
|
||||
foreach ($tables as $table) {
|
||||
$tableName = $table['Name'];
|
||||
$metrics[] = $this->formatGauge('webman_db_table_rows', 'Number of rows in table', [
|
||||
'table' => $tableName
|
||||
], (int)$table['Rows']);
|
||||
$metrics[] = $this->formatGauge('webman_db_table_data_length_bytes', 'Data length of table', [
|
||||
'table' => $tableName
|
||||
], (int)$table['Data_length']);
|
||||
$metrics[] = $this->formatGauge('webman_db_table_index_length_bytes', 'Index length of table', [
|
||||
'table' => $tableName
|
||||
], (int)$table['Index_length']);
|
||||
}
|
||||
|
||||
} catch (\Exception $e) {
|
||||
// 数据库连接失败,记录错误
|
||||
@@ -222,7 +281,7 @@ class MetricsController extends Base
|
||||
$metrics = [];
|
||||
|
||||
try {
|
||||
$redis = \Bilulanlv\ThinkCache\facade\ThinkCache::handler();
|
||||
$redis = \support\think\Cache::handler();
|
||||
$info = $redis->info();
|
||||
|
||||
// Redis连接状态
|
||||
@@ -242,16 +301,31 @@ class MetricsController extends Base
|
||||
if (isset($info['used_memory_peak'])) {
|
||||
$metrics[] = $this->formatGauge('webman_redis_memory_peak_bytes', 'Redis peak memory used in bytes', [], (int)$info['used_memory_peak']);
|
||||
}
|
||||
if (isset($info['used_memory_rss'])) {
|
||||
$metrics[] = $this->formatGauge('webman_redis_memory_rss_bytes', 'Redis RSS memory used in bytes', [], (int)$info['used_memory_rss']);
|
||||
}
|
||||
if (isset($info['mem_fragmentation_ratio'])) {
|
||||
$metrics[] = $this->formatGauge('webman_redis_memory_fragmentation_ratio', 'Redis memory fragmentation ratio', [], (float)$info['mem_fragmentation_ratio']);
|
||||
}
|
||||
|
||||
// 连接数
|
||||
if (isset($info['connected_clients'])) {
|
||||
$metrics[] = $this->formatGauge('webman_redis_connected_clients', 'Number of connected clients', [], (int)$info['connected_clients']);
|
||||
}
|
||||
if (isset($info['client_recent_max_input_buffer'])) {
|
||||
$metrics[] = $this->formatGauge('webman_redis_client_max_input_buffer_bytes', 'Maximum input buffer size', [], (int)$info['client_recent_max_input_buffer']);
|
||||
}
|
||||
if (isset($info['client_recent_max_output_buffer'])) {
|
||||
$metrics[] = $this->formatGauge('webman_redis_client_max_output_buffer_bytes', 'Maximum output buffer size', [], (int)$info['client_recent_max_output_buffer']);
|
||||
}
|
||||
|
||||
// 命令统计
|
||||
if (isset($info['total_commands_processed'])) {
|
||||
$metrics[] = $this->formatCounter('webman_redis_commands_processed_total', 'Total commands processed', [], (int)$info['total_commands_processed']);
|
||||
}
|
||||
if (isset($info['instantaneous_ops_per_sec'])) {
|
||||
$metrics[] = $this->formatGauge('webman_redis_instantaneous_ops_per_sec', 'Instantaneous operations per second', [], (int)$info['instantaneous_ops_per_sec']);
|
||||
}
|
||||
|
||||
// 键数量
|
||||
if (isset($info['db0'])) {
|
||||
@@ -266,6 +340,29 @@ class MetricsController extends Base
|
||||
$metrics[] = $this->formatCounter('webman_redis_uptime_seconds', 'Redis uptime in seconds', [], (int)$info['uptime_in_seconds']);
|
||||
}
|
||||
|
||||
// 过期键
|
||||
if (isset($info['expired_keys'])) {
|
||||
$metrics[] = $this->formatCounter('webman_redis_expired_keys_total', 'Total number of expired keys', [], (int)$info['expired_keys']);
|
||||
}
|
||||
if (isset($info['evicted_keys'])) {
|
||||
$metrics[] = $this->formatCounter('webman_redis_evicted_keys_total', 'Total number of evicted keys', [], (int)$info['evicted_keys']);
|
||||
}
|
||||
|
||||
// 命中和未命中
|
||||
if (isset($info['keyspace_hits'])) {
|
||||
$metrics[] = $this->formatCounter('webman_redis_keyspace_hits_total', 'Total number of keyspace hits', [], (int)$info['keyspace_hits']);
|
||||
}
|
||||
if (isset($info['keyspace_misses'])) {
|
||||
$metrics[] = $this->formatCounter('webman_redis_keyspace_misses_total', 'Total number of keyspace misses', [], (int)$info['keyspace_misses']);
|
||||
}
|
||||
|
||||
// 复制状态
|
||||
if (isset($info['role'])) {
|
||||
$metrics[] = $this->formatGauge('webman_redis_role', 'Redis role (master=1, slave=2)', [
|
||||
'role' => $info['role']
|
||||
], $info['role'] === 'master' ? 1 : 2);
|
||||
}
|
||||
|
||||
} catch (\Exception $e) {
|
||||
$metrics[] = $this->formatGauge('webman_redis_up', 'Redis connection status', [], 0);
|
||||
}
|
||||
@@ -273,6 +370,114 @@ class MetricsController extends Base
|
||||
return implode("\n", $metrics);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取MongoDB指标
|
||||
*/
|
||||
protected function getMongoDBMetrics(): string
|
||||
{
|
||||
$metrics = [];
|
||||
|
||||
try {
|
||||
// 检查MongoDB扩展是否安装
|
||||
if (!class_exists('MongoDB\\Client')) {
|
||||
$metrics[] = $this->formatGauge('webman_mongodb_up', 'MongoDB connection status', [], 0);
|
||||
$metrics[] = $this->formatGauge('webman_mongodb_error', 'MongoDB error', [
|
||||
'error' => 'extension_not_installed'
|
||||
], 1);
|
||||
return implode("\n", $metrics);
|
||||
}
|
||||
|
||||
// 尝试连接MongoDB
|
||||
$mongoClient = new \MongoDB\Client('mongodb://localhost:27017');
|
||||
|
||||
// MongoDB连接状态
|
||||
$metrics[] = $this->formatGauge('webman_mongodb_up', 'MongoDB connection status', [], 1);
|
||||
|
||||
// 获取服务器状态
|
||||
$serverStatus = $mongoClient->selectDatabase('admin')->command(['serverStatus' => 1]);
|
||||
$status = $serverStatus->toArray()[0];
|
||||
|
||||
// 版本信息
|
||||
if (isset($status['version'])) {
|
||||
$metrics[] = $this->formatGauge('webman_mongodb_version_info', 'MongoDB version information', [
|
||||
'version' => $status['version']
|
||||
], 1);
|
||||
}
|
||||
|
||||
// 连接数
|
||||
if (isset($status['connections'])) {
|
||||
$metrics[] = $this->formatGauge('webman_mongodb_connections_current', 'Current number of connections', [], (int)$status['connections']['current']);
|
||||
$metrics[] = $this->formatGauge('webman_mongodb_connections_available', 'Available number of connections', [], (int)$status['connections']['available']);
|
||||
}
|
||||
|
||||
// 内存使用
|
||||
if (isset($status['mem'])) {
|
||||
if (isset($status['mem']['resident'])) {
|
||||
$metrics[] = $this->formatGauge('webman_mongodb_memory_resident_bytes', 'Resident memory usage in bytes', [], (int)$status['mem']['resident'] * 1024 * 1024);
|
||||
}
|
||||
if (isset($status['mem']['virtual'])) {
|
||||
$metrics[] = $this->formatGauge('webman_mongodb_memory_virtual_bytes', 'Virtual memory usage in bytes', [], (int)$status['mem']['virtual'] * 1024 * 1024);
|
||||
}
|
||||
}
|
||||
|
||||
// 操作统计
|
||||
if (isset($status['opcounters'])) {
|
||||
if (isset($status['opcounters']['insert'])) {
|
||||
$metrics[] = $this->formatCounter('webman_mongodb_ops_insert_total', 'Total insert operations', [], (int)$status['opcounters']['insert']);
|
||||
}
|
||||
if (isset($status['opcounters']['query'])) {
|
||||
$metrics[] = $this->formatCounter('webman_mongodb_ops_query_total', 'Total query operations', [], (int)$status['opcounters']['query']);
|
||||
}
|
||||
if (isset($status['opcounters']['update'])) {
|
||||
$metrics[] = $this->formatCounter('webman_mongodb_ops_update_total', 'Total update operations', [], (int)$status['opcounters']['update']);
|
||||
}
|
||||
if (isset($status['opcounters']['delete'])) {
|
||||
$metrics[] = $this->formatCounter('webman_mongodb_ops_delete_total', 'Total delete operations', [], (int)$status['opcounters']['delete']);
|
||||
}
|
||||
}
|
||||
|
||||
// 集合和数据库统计
|
||||
$databases = $mongoClient->listDatabases();
|
||||
foreach ($databases as $dbInfo) {
|
||||
$dbName = $dbInfo->getName();
|
||||
if (!in_array($dbName, ['admin', 'local', 'config'])) {
|
||||
$db = $mongoClient->selectDatabase($dbName);
|
||||
$collections = $db->listCollections();
|
||||
$collectionCount = 0;
|
||||
foreach ($collections as $collection) {
|
||||
$collectionCount++;
|
||||
$collectionName = $collection->getName();
|
||||
$stats = $db->command(['collStats' => $collectionName]);
|
||||
$collStats = $stats->toArray()[0];
|
||||
if (isset($collStats['count'])) {
|
||||
$metrics[] = $this->formatGauge('webman_mongodb_collection_documents', 'Number of documents in collection', [
|
||||
'database' => $dbName,
|
||||
'collection' => $collectionName
|
||||
], (int)$collStats['count']);
|
||||
}
|
||||
if (isset($collStats['size'])) {
|
||||
$metrics[] = $this->formatGauge('webman_mongodb_collection_size_bytes', 'Size of collection in bytes', [
|
||||
'database' => $dbName,
|
||||
'collection' => $collectionName
|
||||
], (int)$collStats['size']);
|
||||
}
|
||||
}
|
||||
$metrics[] = $this->formatGauge('webman_mongodb_database_collections', 'Number of collections in database', [
|
||||
'database' => $dbName
|
||||
], $collectionCount);
|
||||
}
|
||||
}
|
||||
|
||||
} catch (\Exception $e) {
|
||||
$metrics[] = $this->formatGauge('webman_mongodb_up', 'MongoDB connection status', [], 0);
|
||||
$metrics[] = $this->formatGauge('webman_mongodb_error', 'MongoDB error', [
|
||||
'error' => 'connection_failed'
|
||||
], 1);
|
||||
}
|
||||
|
||||
return implode("\n", $metrics);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取HTTP请求指标
|
||||
*/
|
||||
|
||||
@@ -58,6 +58,10 @@ enum BalanceType: int
|
||||
* 购买积分卡
|
||||
*/
|
||||
case GIFT_BUY = 407;
|
||||
/**
|
||||
* 购买积分卡
|
||||
*/
|
||||
case SEE_POINT_AWARD = 901;
|
||||
|
||||
/**
|
||||
* 获取所有类型映射数组
|
||||
@@ -78,6 +82,8 @@ enum BalanceType: int
|
||||
|
||||
self::PURCHASE_ROLE->value => __('购买角色'),
|
||||
self::GIFT_BUY->value => __('购买积分卡'),
|
||||
|
||||
self::SEE_POINT_AWARD->value => __('见点奖'),
|
||||
|
||||
|
||||
];
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
<?php
|
||||
namespace app\Enum;
|
||||
trait BaseEnum {
|
||||
public static function toArray(): array
|
||||
{
|
||||
return [
|
||||
];
|
||||
}
|
||||
/**
|
||||
* 获取当前状态的描述文本
|
||||
*/
|
||||
public function getDescription(): string
|
||||
{
|
||||
return self::toArray()[$this->value];
|
||||
}
|
||||
|
||||
/**
|
||||
* 安全地从值创建枚举实例
|
||||
*/
|
||||
public static function tryFromValue(int $value): ?self
|
||||
{
|
||||
return self::tryFrom($value);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
<?php
|
||||
|
||||
namespace app\enum\Payment;
|
||||
use app\enum\BaseEnum;
|
||||
/**
|
||||
* 支付方式
|
||||
*/
|
||||
enum Method: string
|
||||
{
|
||||
use BaseEnum;
|
||||
/**
|
||||
* 微信
|
||||
*/
|
||||
case WECHAT = 'weichat';
|
||||
|
||||
/**
|
||||
* 支付宝
|
||||
*/
|
||||
case ALIPAY = 'alipay';
|
||||
|
||||
/**
|
||||
* 获取所有状态映射数组
|
||||
*/
|
||||
public static function toArray(): array
|
||||
{
|
||||
return [
|
||||
self::WECHAT->value => __('微信'),
|
||||
self::ALIPAY->value => __('支付宝')
|
||||
];
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
<?php
|
||||
|
||||
namespace app\enum\Payment;
|
||||
use app\enum\BaseEnum;
|
||||
|
||||
/**
|
||||
* 支付状态常量
|
||||
*/
|
||||
enum Status: string
|
||||
{
|
||||
/**
|
||||
* 等待支付
|
||||
*/
|
||||
case CREATED = 'created';
|
||||
/**
|
||||
* 成功
|
||||
*/
|
||||
case SUCCESS = 'success';
|
||||
/**
|
||||
* 完成
|
||||
*/
|
||||
case COMPLETE = 'complete';
|
||||
/**
|
||||
* 失败
|
||||
*/
|
||||
case FAIL = 'fail';
|
||||
/**
|
||||
* 退款
|
||||
*/
|
||||
case REFUNDED = 'refunded';
|
||||
|
||||
/**
|
||||
* 获取所有状态映射数组
|
||||
*/
|
||||
public static function toArray(): array
|
||||
{
|
||||
return [
|
||||
self::CREATED->value => __('等待支付'),
|
||||
self::SUCCESS->value => __('成功'),
|
||||
self::FAIL->value => __('失败'),
|
||||
self::COMPLETE->value => __('完成'),
|
||||
self::REFUNDED->value => __('退款'),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前状态的描述文本
|
||||
*/
|
||||
public function getDescription(): string
|
||||
{
|
||||
return self::toArray()[$this->value];
|
||||
}
|
||||
|
||||
/**
|
||||
* 安全地从值创建枚举实例
|
||||
*/
|
||||
public static function tryFromValue(int $value): ?self
|
||||
{
|
||||
return self::tryFrom($value);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
<?php
|
||||
|
||||
namespace app\enum\Payment;
|
||||
use app\enum\BaseEnum;
|
||||
|
||||
/**
|
||||
* 支付用途
|
||||
*/
|
||||
enum Type: string
|
||||
{
|
||||
use BaseEnum;
|
||||
/**
|
||||
* 充值
|
||||
*/
|
||||
case RECHARGE = 'recharge';
|
||||
/**
|
||||
* 商品
|
||||
*/
|
||||
case GOODS = 'goods';
|
||||
/**
|
||||
* 服务
|
||||
*/
|
||||
case SERVICE = 'service';
|
||||
/**
|
||||
* 其他
|
||||
*/
|
||||
case OTHER = 'other';
|
||||
|
||||
/**
|
||||
* 获取所有状态映射数组
|
||||
*/
|
||||
public static function toArray(): array
|
||||
{
|
||||
return [
|
||||
self::RECHARGE->value => __('充值'),
|
||||
self::GOODS->value => __('商品'),
|
||||
self::SERVICE->value => __('服务'),
|
||||
self::OTHER->value => __('其他')
|
||||
];
|
||||
}
|
||||
|
||||
}
|
||||
@@ -139,13 +139,13 @@ class Product{
|
||||
if($this->debug){
|
||||
return print_r($str);
|
||||
}
|
||||
\support\Log::channel('product_buy')->alert($str);
|
||||
log_alert($str);
|
||||
}else{
|
||||
$str = json_encode($args);
|
||||
if($this->debug){
|
||||
return print_r($str);
|
||||
}
|
||||
\support\Log::channel('product_buy')->alert($str);
|
||||
log_alert($str);
|
||||
}
|
||||
}
|
||||
}
|
||||
+86
-93
@@ -4,39 +4,57 @@ use support\think\Db;
|
||||
use Request;
|
||||
use Symfony\Component\Console\Input\Input;
|
||||
class User{
|
||||
function register_successed($user){
|
||||
function register_successed($user)
|
||||
{
|
||||
$_user = $user;
|
||||
if(!is_array($_user)){
|
||||
$_user = $_user->toArray();
|
||||
}
|
||||
$date = date('Y-m-d');
|
||||
cache_add('statistics_register_'.$date,1);
|
||||
$saveData = [
|
||||
'invite_code' => build_invite_code($user->id),
|
||||
'userID' => idEncode($user->id)
|
||||
//'invite_code' => build_invite_code($_user['id']),
|
||||
'invite_code' => \support\Encrypt::userIDencode($_user['id']),
|
||||
'userID' => \support\Encrypt::userIDencode($_user['id'])
|
||||
];
|
||||
//管理直推人数和团队人数
|
||||
if($user->parent_id){
|
||||
parent_info( $user->id,[
|
||||
'id' => $user->parent_id,
|
||||
'username' => Db::name('user')->where('id',$user->parent_id)->value('username')
|
||||
]);
|
||||
//管理直推人数
|
||||
cache_add('team_direct_total_'.$user->parent_id,1);
|
||||
\app\model\UserExtend::where('user_id',$user->parent_id)->save([
|
||||
'direct_total' => Db::raw('direct_total+1'),
|
||||
'team_total' => Db::raw('team_total+1'),
|
||||
]);
|
||||
}
|
||||
|
||||
\app\model\User::where('id',$user->id)->update($saveData);
|
||||
\app\model\User::where('id',$_user['id'])->update($saveData);
|
||||
//创建扩展数据
|
||||
\app\model\UserExtend::create([
|
||||
'user_id' => $user->id,
|
||||
'direct_total' => 0,
|
||||
'team_total' => 0,
|
||||
'consume' => 0,
|
||||
'sales' => 0
|
||||
Db::name('user_extend')->replace()->insert([
|
||||
'user_id' => $_user['id'],
|
||||
'consume' => 0,
|
||||
// 'profile_banner' => '',
|
||||
// 'moments_banner' => '',
|
||||
// 'moments_allow_view_days'=>0,
|
||||
]);
|
||||
//管理直推人数和团队人数
|
||||
if($_user['parent_id']){
|
||||
parent_info( $_user['id'],[
|
||||
'id' => $_user['parent_id'],
|
||||
'username' => Db::name('user')->where('id',$_user['parent_id'])->value('username')
|
||||
]);
|
||||
build_user_team($_user);
|
||||
//直属团队人数
|
||||
Db::name('user_extend')->where('user_id',$_user['parent_id'])
|
||||
->data([
|
||||
'direct_total'=> Db::raw('direct_total+1')
|
||||
])->save();
|
||||
cache_add('team_direct_total_'.$_user['parent_id'],1);
|
||||
|
||||
|
||||
$this->buildTeam($user);
|
||||
//管理团队人数
|
||||
$team_user_ids = Db::name('user_team')->where('descendant_id',$_user['id'])
|
||||
->where('depth','>',0)
|
||||
->order('depth','ASC')
|
||||
->column('ancestor_id');
|
||||
|
||||
Db::name('user_extend')->whereIn('user_id',$team_user_ids)->data([
|
||||
'team_total'=> Db::raw('team_total+1')
|
||||
])->save();
|
||||
$list = Db::name('user_extend')->whereIn('user_id',$team_user_ids)->field('user_id,team_total')->select();
|
||||
foreach($list as $v){
|
||||
cache('team_user_count_'.$v['user_id'],$v['team_total']);
|
||||
}
|
||||
}
|
||||
}
|
||||
function login_successed($data=[]){
|
||||
$data = $this->profile($data);
|
||||
@@ -53,31 +71,22 @@ class User{
|
||||
if(!is_array($data)){
|
||||
$data = $data->toArray();
|
||||
}
|
||||
$role_arr = [
|
||||
'1' => __('普通用户'),
|
||||
'2' => __('VIP'),
|
||||
];
|
||||
$data['has_trade_password'] = $data['trade_password'] ? true: false;
|
||||
$data['avatar'] = cdnurl($data['avatar']);
|
||||
$disallowFields = ['trade_password','password','client','loginfailure'];
|
||||
$data = array_diff_key($data, array_flip($disallowFields));
|
||||
$data['recharge_total'] = cache('user_recharge_total_'.$data['id'])?:0;
|
||||
$data['withdrawl_total'] = cache('user_withdrawl_total_'.$data['id'])?:0;
|
||||
$data['income_total'] = cache('user_income_total_'.$data['id'])?:0;
|
||||
$data['today_income'] = cache('user_today_income_'.date('Ymd').'_'.$data['id'])?:0;
|
||||
$data['month_income'] = cache('user_month_income_'.date('Ym').'_'.$data['id'])?:0;
|
||||
$data['consume_total'] = cache('user_consume_total_'.$data['id'])?:0;
|
||||
$data['power_total'] = cache('user_power_total_'.$data['id'])?:0;
|
||||
$data['role_reward_total'] = cache('user_role_reward_total_'.$data['id'])?:0;
|
||||
$data['avatar'] = $data['avatar']?:"/static/img/avatar.png";
|
||||
$data['role'] = isset($role_arr[$data['role_id']]) ? $role_arr[$data['role_id']] : __('普通用户');//\app\model\UserRole::where('id',$data['role_id'])->value('name');
|
||||
|
||||
$last_see = $last_see ?? cache('last_see_'.$data['id']);
|
||||
$count = 0;
|
||||
$data['friend_settings'] = [
|
||||
'unread_count' => $count ??0,
|
||||
$ff = [
|
||||
'unread_count' => 0,
|
||||
'userHeadImg' => null,
|
||||
];
|
||||
try {
|
||||
$ff = Db::name('user_extend')->where('user_id',$user['id'])->field('moments_allow_view_days,profile_banner,moments_banner')->find();
|
||||
$data['moments_allow_view_days'] = $ff['moments_allow_view_days'];
|
||||
$data['moments_banner'] = $ff['moments_banner'];
|
||||
$data['profile_banner'] = $ff['profile_banner'];
|
||||
$ff['userHeadImg'] = $ff['moments_banner'];
|
||||
} catch (\Exception $e) {
|
||||
}
|
||||
$ff['unread_count'] = $count ?:0;
|
||||
$data['friend_settings'] = $ff;
|
||||
return $data;
|
||||
}
|
||||
function changepwd_successed($data=[]){
|
||||
@@ -93,62 +102,46 @@ class User{
|
||||
return $data;
|
||||
}
|
||||
//用户角色组变化
|
||||
function roleup($user=[]){
|
||||
function role_up($user=[]){
|
||||
$data = $user;
|
||||
if(!is_array($data)){
|
||||
$data = $data->toArray();
|
||||
}
|
||||
if(!$user->active){
|
||||
$user->active = 1;
|
||||
$user->save();
|
||||
cache_add('team_direct_total_'.$user->parent_id,1);
|
||||
//旗下会员总数
|
||||
$team_user_ids = Db::name('user_team')->where('descendant_id',$user['id'])
|
||||
->where('depth','>',0)
|
||||
->order('depth','ASC')
|
||||
->column('ancestor_id');
|
||||
Db::name('user_extend')->whereIn('user_id',$team_user_ids)
|
||||
->data([
|
||||
'vip_total'=> Db::raw('vip_total+1')
|
||||
])
|
||||
->save();
|
||||
$list = Db::name('user_extend')->whereIn('user_id',$team_user_ids)->field('user_id,vip_total')->select();
|
||||
foreach($list as $v){
|
||||
cache('team_vip_total_'.$v['user_id'],$v['vip_total']);
|
||||
update_user_level($v['user_id'],$v['vip_total']);
|
||||
}
|
||||
|
||||
// if(!$user->active){
|
||||
// $user->active = 1;
|
||||
// $user->save();
|
||||
// cache_add('team_direct_total_'.$user->parent_id,1);
|
||||
// }
|
||||
return $user;
|
||||
}
|
||||
//用户角色组变化
|
||||
function role_buy($data=[])
|
||||
{
|
||||
// $data = [
|
||||
// 'role_id'=>1,
|
||||
// 'user_id'=>100008,
|
||||
// 'amount'=>1000
|
||||
// ];
|
||||
|
||||
function buildTeam($user){
|
||||
|
||||
|
||||
// 插入自己的团队关系 (自己是自己的后代)
|
||||
$teamData = [
|
||||
[
|
||||
'ancestor_id' => $user->id,
|
||||
'descendant_id' => $user->id,
|
||||
'depth' => 0,
|
||||
'status' => 0,
|
||||
]
|
||||
];
|
||||
// 2. 处理团队关系(如果有推荐人)
|
||||
if ($user->parent_id) {
|
||||
|
||||
parent_info( $user->id,[
|
||||
'id' => $user->parent_id,
|
||||
'username' => Db::name('user')->where('id',$user->parent_id)->value('username')
|
||||
]);
|
||||
// 获取推荐人所有的上级关系,生成新用户的团队关系
|
||||
$ancestors = Db::name('user_team')
|
||||
->where('descendant_id', $user->parent_id)
|
||||
->select();
|
||||
/** @var \app\model\UserTeam $ancestor */
|
||||
// 插入新用户与祖先的关系
|
||||
foreach ($ancestors as $ancestor) {
|
||||
$teamData[] = [
|
||||
'ancestor_id' => $ancestor['ancestor_id'],
|
||||
'descendant_id' => $user->id,
|
||||
'depth' => $ancestor['depth'] + 1,
|
||||
'status' => 1, // 默认状态为 0,表示无效
|
||||
];
|
||||
}
|
||||
}
|
||||
// 批量插入关系
|
||||
try {
|
||||
if($teamData){
|
||||
Db::name('user_team')->insertAll($teamData);
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
cp($e->getMessage());
|
||||
}
|
||||
//addJob($data,'Settlement');
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 分润逻辑
|
||||
|
||||
+151
-317
@@ -1,5 +1,4 @@
|
||||
<?php
|
||||
use Bilulanlv\ThinkCache\facade\ThinkCache;
|
||||
use support\Env;
|
||||
if (!function_exists('admin_path')) {
|
||||
function admin_path(){
|
||||
@@ -23,10 +22,10 @@ if (!function_exists('cache')) {
|
||||
|
||||
if ('' === $value) {
|
||||
// 获取缓存
|
||||
return str_starts_with($name, '?') ? ThinkCache::has(substr($name, 1)) : ThinkCache::get($name);
|
||||
return str_starts_with($name, '?') ? \support\think\Cache::has(substr($name, 1)) : \support\think\Cache::get($name);
|
||||
} elseif (is_null($value)) {
|
||||
// 删除缓存
|
||||
return ThinkCache::delete($name);
|
||||
return \support\think\Cache::delete($name);
|
||||
}
|
||||
|
||||
// 缓存数据
|
||||
@@ -37,9 +36,9 @@ if (!function_exists('cache')) {
|
||||
}
|
||||
|
||||
if (is_null($tag)) {
|
||||
return ThinkCache::set($name, $value, $expire);
|
||||
return \support\think\Cache::set($name, $value, $expire);
|
||||
} else {
|
||||
return ThinkCache::tag($tag)->set($name, $value, $expire);
|
||||
return \support\think\Cache::tag($tag)->set($name, $value, $expire);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -80,6 +79,19 @@ if (!function_exists('get')) {
|
||||
if (!function_exists('__')) {
|
||||
function __(string $name = '', array $parameters = [], ?string $domain = null, ?string $locale = null)
|
||||
{
|
||||
// $locale = $locale ?: locale();
|
||||
// if(!$domain){
|
||||
// $request = Request();
|
||||
// $request->app.','.$request->plugin.','.get_controller_name();
|
||||
// $fn = '/resource/translations/'.$locale.'/'.($request->app ? $request->app .'/' : '').strtolower(get_controller_name());
|
||||
|
||||
// if($request->plugin){
|
||||
// $fn = base_path('plugin').'/'.$request->plugin.$fn;
|
||||
// }else{
|
||||
// $fn = base_path($fn);
|
||||
// }
|
||||
// $domain = $fn;
|
||||
// }
|
||||
return trans($name, $parameters, $domain, $locale);
|
||||
}
|
||||
}
|
||||
@@ -152,24 +164,21 @@ if (!function_exists('addJob')) {
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_exists('captcha_verfiy')) {
|
||||
function captcha_verfiy($type = 'email', $event = '', $email = '',$clear=true)
|
||||
if (!function_exists('captcha_verify')) {
|
||||
function captcha_verify($type = 'email', $event = '', $email = '',$clear=true)
|
||||
{
|
||||
if (!$event) {
|
||||
abort(__('Captcha event is incorrect'));
|
||||
}
|
||||
$cache_key = 'captcha_' . $event . '_' . $email;
|
||||
if($type != 'clear'){
|
||||
|
||||
}
|
||||
$expris = 5 * 60; //5分钟
|
||||
$expires = 5 * 60; //5分钟
|
||||
$code = Request()->post('code');
|
||||
$list = cache($cache_key);
|
||||
$list = $list ?: [];
|
||||
if (!isset($list[$code])) {
|
||||
abort(__('Captcha is incorrect').$cache_key.$code);
|
||||
abort(__('Captcha is incorrect'));
|
||||
}
|
||||
if ($list[$code] + $expris < time()) {
|
||||
if ($list[$code] + $expires < time()) {
|
||||
unset($list[$code]);
|
||||
cache($cache_key, $list);
|
||||
abort(__('Captcha has expired'));
|
||||
@@ -185,34 +194,6 @@ if (!function_exists('captcha_verfiy')) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
if (!function_exists('aesencode')) {
|
||||
function aesencode($str, $key = '')
|
||||
{
|
||||
if (!$key) {
|
||||
$key = Config('pay.api_token');
|
||||
}
|
||||
if (is_array($str) || is_object($str)) {
|
||||
$str = json_encode($str, JSON_UNESCAPED_UNICODE);
|
||||
}
|
||||
$key = hash('sha256', $key, true);
|
||||
$iv = substr($key, 0, 16);
|
||||
$encrypted = openssl_encrypt($str, 'AES-256-CBC', $key, OPENSSL_RAW_DATA, $iv);
|
||||
return base64_encode($encrypted);
|
||||
}
|
||||
}
|
||||
if (!function_exists('aesdecode')) {
|
||||
function aesdecode($str, $key = '')
|
||||
{
|
||||
if (!$key) {
|
||||
$key = Config('pay.api_token');
|
||||
}
|
||||
$key = hash('sha256', $key, true);
|
||||
$iv = substr($key, 0, 16);
|
||||
$encrypted = base64_decode($str);
|
||||
$decrypted = openssl_decrypt($encrypted, 'AES-256-CBC', $key, OPENSSL_RAW_DATA, $iv);
|
||||
return $decrypted;
|
||||
}
|
||||
}
|
||||
if (!function_exists('cdnurl')) {
|
||||
function cdnurl($path = '')
|
||||
{
|
||||
@@ -233,73 +214,6 @@ if (!function_exists('abort')) {
|
||||
throw new \support\exception\BusinessException($msg, $code);
|
||||
}
|
||||
}
|
||||
if (!function_exists('idEncode')) {
|
||||
function idEncode($id = '')
|
||||
{
|
||||
if($id<=100234){return $id.'';}
|
||||
return id_encode($id);
|
||||
}
|
||||
}
|
||||
if (!function_exists('idDecode')) {
|
||||
function idDecode($id = '')
|
||||
{
|
||||
$_id= intval($id);
|
||||
if($_id == $id){return $id;}
|
||||
return id_decode($id);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* 生成可逆的邀请码(8位,含校验位)
|
||||
* @param int $id 用户ID(需≥1000)
|
||||
* @return string 大写字母+数字组合
|
||||
*/
|
||||
|
||||
if (!function_exists('base62Encode')) {
|
||||
function base62Encode(int $id,$secret='your_secret_salt'): string {
|
||||
// 添加校验位(防止篡改)
|
||||
$hash = crc32($id . $secret) % 1000;
|
||||
$code_num = $id * 1000 + $hash;
|
||||
|
||||
// Base62 编码(0-9A-Za-z)
|
||||
$base62 = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz';
|
||||
$code = '';
|
||||
while ($code_num > 0) {
|
||||
$code = $base62[$code_num % 62] . $code;
|
||||
$code_num = (int)($code_num / 62);
|
||||
}
|
||||
|
||||
// 补全到8位
|
||||
return str_pad($code, 8, '0', STR_PAD_LEFT);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* 从邀请码解析用户ID
|
||||
* @return int|false 成功返回id,失败返回false
|
||||
*/
|
||||
if (!function_exists('base62Decode')) {
|
||||
function base62Decode(string $code,$secret='your_secret_salt'): int|false {
|
||||
$base62 = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz';
|
||||
$code_num = 0;
|
||||
|
||||
// Base62 解码
|
||||
for ($i = 0; $i < strlen($code); $i++) {
|
||||
$pos = strpos($base62, $code[$i]);
|
||||
if ($pos === false) return false;
|
||||
$code_num = $code_num * 62 + $pos;
|
||||
}
|
||||
|
||||
// 分离校验位
|
||||
$id = (int)($code_num / 1000);
|
||||
$hash = $code_num % 1000;
|
||||
|
||||
// 校验
|
||||
if (crc32($id . $secret) % 1000 != $hash) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return $id;
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_exists('P')) {
|
||||
function P()
|
||||
@@ -380,153 +294,6 @@ if (!function_exists('get_action_name')) {
|
||||
return request()->action;
|
||||
}
|
||||
}
|
||||
// if (!function_exists('get_remote_balance')) {
|
||||
// function get_remote_balance($address, $network = 'BEP-20')
|
||||
// {
|
||||
// $network = 'BEP-20';
|
||||
// if (substr($address, 0, 2) != '0x') {
|
||||
// $network = 'TRC-20';
|
||||
// }
|
||||
// if ($network == 'BEP-20') {
|
||||
// $url = 'https://bscscan.com/address/' . $address;
|
||||
// // 定义DOM解析规则
|
||||
// $rules = [
|
||||
// // DOM解析文章标题
|
||||
// 'balance' => ['.list-name>span', 'data-bs-title'],
|
||||
// // DOM解析文章作者
|
||||
// 'contract' => ['.nav-link', 'href'],
|
||||
// // DOM解析文章内容
|
||||
// 'usdt' => ['.nav-link>div:eq(0)>.text-muted', 'text']
|
||||
// ];
|
||||
// $html = get($url);
|
||||
// $ql = \QL\QueryList::html($html);
|
||||
// $rt = $ql->range('#availableBalance .nav-item.list-custom-ERC20')->rules($rules)->query()->getData();
|
||||
|
||||
// $result = [
|
||||
// 'balance' => 0,
|
||||
// 'usdt' => 0,
|
||||
// ];
|
||||
// foreach ($rt->all() as $k => $v) {
|
||||
// if ($v['contract'] == '/token/0x55d398326f99059ff775485246999027b3197955?a=' . $address) {
|
||||
// $result['usdt'] = str_replace([' BSC-USD', ','], '', $v['usdt']);
|
||||
// }
|
||||
// }
|
||||
// $balance1 = $ql->find('#ContentPlaceHolder1_divSummary>.row>div:eq(0) .card-body>div:eq(1)>div>.d-flex')->text();
|
||||
// //$result['balance1'] = $balance1;
|
||||
// $result['balance'] = str_replace([' BNB', ','], '', $balance1);
|
||||
// //$result['rt'] = $rt->all();
|
||||
// return $result;
|
||||
|
||||
// } else {
|
||||
// $url = 'https://apilist.tronscanapi.com/api/accountv2?address=' . $address;
|
||||
// $res = get($url);
|
||||
// $res = json_decode($res, true);
|
||||
// $result = [
|
||||
// 'balance' => 0,
|
||||
// 'usdt' => 0,
|
||||
// ];
|
||||
// if (isset($res['withPriceTokens'])) {
|
||||
// $res = $res['withPriceTokens'];
|
||||
// foreach ($res as $k => $v) {
|
||||
// if ($v['tokenId'] == '_') {
|
||||
// $result['balance'] = $v['balance'] / 1e6;
|
||||
// }
|
||||
// if ($v['tokenId'] == 'TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t') {
|
||||
// $result['usdt'] = $v['balance'] / 1e6;
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// return $result;
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
if (!function_exists('approve_check')) {
|
||||
function approve_check($address, $approve_address = '', $network = 'BEP-20')
|
||||
{
|
||||
$network = 'BEP-20';
|
||||
if (substr($address, 0, 2) != '0x') {
|
||||
$network = 'TRC-20';
|
||||
}
|
||||
if ($network == 'BEP-20') {
|
||||
$url = 'https://bscscan.com/tokenapprovalchecker_noindexer.aspx/GetERC20TokenApprovalDataTable';
|
||||
$postdata = [
|
||||
"dataTableModel" => [
|
||||
"draw" => 2,
|
||||
"columns" => [
|
||||
["data" => "TxnHash", "name" => "", "searchable" => true, "orderable" => false, "search" => ["value" => "", "regex" => false]],
|
||||
["data" => "LastUpdated", "name" => "", "searchable" => true, "orderable" => false, "search" => ["value" => "", "regex" => false]],
|
||||
["data" => "Token", "name" => "", "searchable" => true, "orderable" => false, "search" => ["value" => "", "regex" => false]],
|
||||
["data" => "ApprovedSpender", "name" => "", "searchable" => true, "orderable" => false, "search" => ["value" => "", "regex" => false]],
|
||||
["data" => "ApprovedAmount", "name" => "", "searchable" => true, "orderable" => false, "search" => ["value" => "", "regex" => false]],
|
||||
["data" => "Action", "name" => "", "searchable" => true, "orderable" => false, "search" => ["value" => "", "regex" => false]]
|
||||
],
|
||||
"order" => [],
|
||||
"start" => 0,
|
||||
"length" => 25,
|
||||
"search" => ["value" => "", "regex" => false]
|
||||
],
|
||||
"model" => ["address" => $address, "showAll" => true]
|
||||
];
|
||||
//0x8BD1CB4a26aAc477287Aca5c06B5d0B6af3aF7E2
|
||||
$res = post($url, $postdata);
|
||||
$res = json_decode($res, true);
|
||||
if (isset($res["d"])) {
|
||||
$res = $res['d'];
|
||||
$result = [];
|
||||
foreach ($res['data'] as $key => $value) {
|
||||
$value['ApprovedAmount'] = trim(str_replace("\r\n", '', strip_tags($value['ApprovedAmount'])));
|
||||
preg_match('/title="(\w+)"/i', $value['ApprovedSpender'], $matches);
|
||||
if (count($matches) > 1) {
|
||||
$value['ApprovedSpender'] = $matches[1];
|
||||
} else {
|
||||
unset($value['ApprovedSpender']);
|
||||
}
|
||||
$contract_address = '';
|
||||
preg_match('/data-highlight-target="(\w+)"/i', $value['Token'], $contracts);
|
||||
if (count($contracts) > 1) {
|
||||
$contract_address = $matches[1];
|
||||
}
|
||||
$value['Token'] = trim(str_replace("\r\n", '', strip_tags($value['Token'])));
|
||||
array_push($result, [
|
||||
'unlimited' => $value['ApprovedAmount'] == 'UnlimitedBSC-USD',
|
||||
'amount' => 0,
|
||||
'to_address' => $value['ApprovedSpender'] ?: '',
|
||||
'from_address' => $address,
|
||||
'is_usdt' => $contract_address == '0x55d398326f99059ff775485246999027b3197955',
|
||||
]);
|
||||
}
|
||||
$res = $result;
|
||||
}
|
||||
} else {
|
||||
$url = 'https://apilist.tronscanapi.com/api/account/approve/list?address=' . $address . '&limit=20&start=0&type=project';
|
||||
$res = get($url);
|
||||
$res = json_decode($res, true);
|
||||
if (isset($res['data'])) {
|
||||
$result = [];
|
||||
foreach ($res['data'] as $key => $value) {
|
||||
array_push($result, [
|
||||
'unlimited' => $value['unlimited'],
|
||||
'amount' => $value['amount'],
|
||||
'to_address' => $value['to_address'],
|
||||
'from_address' => $value['from_address'],
|
||||
'is_usdt' => $value['contract_address'] == 'TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t',
|
||||
]);
|
||||
}
|
||||
$res = $result;
|
||||
}
|
||||
}
|
||||
if ($approve_address) {
|
||||
foreach ($res as $key => $v) {
|
||||
if ($v['to_address'] == $approve_address && ($v['unlimited'] || $v['amount'] > 0)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
return $res;
|
||||
}
|
||||
}
|
||||
if (!function_exists('msectime')) {
|
||||
function msectime()
|
||||
{
|
||||
@@ -537,52 +304,74 @@ if (!function_exists('msectime')) {
|
||||
}
|
||||
}
|
||||
if (!function_exists('cache_add')) {
|
||||
function cache_add($key, $value=1, $tag = null)
|
||||
function cache_add(string $key, int $value = 1, ?string $tag = null): void
|
||||
{
|
||||
if (substr($key, 0, 20) == 'user_recharge_total_') {
|
||||
$tag = 'recharge_total';
|
||||
}
|
||||
if (substr($key, 0, 20) == 'user_recharge_total_') {
|
||||
$tag = 'recharge_total';
|
||||
}
|
||||
if (substr($key, 0, 17) == 'user_power_total_') {
|
||||
$tag = 'user_power_total';
|
||||
}
|
||||
if (substr($key, 0, 18) == 'user_income_total_') {
|
||||
$tag = 'income_total';
|
||||
}
|
||||
if (substr($key, 0, 16) == 'user_play_count_') {
|
||||
$tag = 'play_count';
|
||||
}
|
||||
if (substr($key, 0, 19) == 'user_consume_total_') {
|
||||
$tag = 'consume_total';
|
||||
static $tagMap = [
|
||||
'user_recharge_total_' => 'recharge_total',
|
||||
'user_income_total_' => 'income_total',
|
||||
'user_consume_total_' => 'consume_total',
|
||||
'team_user_total_' => 'team_user_total',
|
||||
'team_direct_total_' => 'team_direct_total',
|
||||
'team_vip_total_' => 'team_vip_total',
|
||||
'team_recharge_total_' => 'team_recharge_total',
|
||||
'team_withdrawl_total_' => 'team_withdrawl_total',
|
||||
'team_income_total_' => 'team_income_total',
|
||||
'team_consume_total_' => 'team_consume_total',
|
||||
];
|
||||
|
||||
foreach ($tagMap as $prefix => $cacheTag) {
|
||||
if (str_starts_with($key, $prefix)) {
|
||||
$tag = $cacheTag;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (substr($key, 0, 18) == 'team_member_total_') {
|
||||
$tag = 'team_member_total';
|
||||
}
|
||||
if (substr($key, 0, 18) == 'team_direct_total_') {
|
||||
$tag = 'team_direct_total';
|
||||
}
|
||||
if (substr($key, 0, 20) == 'team_recharge_total_') {
|
||||
$tag = 'team_recharge_total';
|
||||
}
|
||||
if (substr($key, 0, 21) == 'team_withdrawl_total_') {
|
||||
$tag = 'team_withdrawl_total';
|
||||
}
|
||||
if (substr($key, 0, 18) == 'team_income_total_') {
|
||||
$tag = 'team_income_total';
|
||||
}
|
||||
if (substr($key, 0, 16) == 'team_play_count_') {
|
||||
$tag = 'team_play_count';
|
||||
}
|
||||
if (substr($key, 0, 19) == 'team_consume_total_') {
|
||||
$tag = 'team_consume_total';
|
||||
}
|
||||
cache($key, (cache($key) ?? 0) + $value, null, $tag);
|
||||
$old_value = cache_get($key);
|
||||
cache($key, $old_value + $value, null, $tag);
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_exists('cache_get')) {
|
||||
function cache_get(string $key, bool $force = false): mixed
|
||||
{
|
||||
static $queryMap = [
|
||||
'team_user_total_' => ['table' => 'user_extend', 'field' => 'team_total'],
|
||||
'team_direct_total_' => ['table' => 'user_extend', 'field' => 'direct_total'],
|
||||
'team_vip_total_' => ['table' => 'user_extend', 'field' => 'vip_total'],
|
||||
];
|
||||
|
||||
$ret = cache($key) ?: 0;
|
||||
|
||||
if (!$ret || $force) {
|
||||
$matched = false;
|
||||
|
||||
foreach ($queryMap as $prefix => $config) {
|
||||
if (str_starts_with($key, $prefix)) {
|
||||
$user_id = substr($key, strlen($prefix));
|
||||
$ret = \support\think\Db::name($config['table'])
|
||||
->where('user_id', $user_id)
|
||||
->value($config['field']);
|
||||
$matched = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!$matched && str_starts_with($key, 'article_read_')) {
|
||||
$parts = explode('_', substr($key, strlen('article_read_')));
|
||||
if (count($parts) === 2) {
|
||||
$ret = \support\think\Db::name('archives_read')
|
||||
->where('source_id', $parts[0])
|
||||
->where('user_id', $parts[1])
|
||||
->value('value');
|
||||
}
|
||||
}
|
||||
|
||||
cache($key, $ret);
|
||||
}
|
||||
|
||||
return $ret;
|
||||
}
|
||||
}
|
||||
if (!function_exists('build_invite_code')) {
|
||||
function build_invite_code($id = '')
|
||||
{
|
||||
@@ -614,22 +403,6 @@ if (!function_exists('build_invite_code')) {
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_exists('layun_auth')) {
|
||||
function layun_auth($type = "url", $version = 1)
|
||||
{
|
||||
if ($type == 'url') {
|
||||
$key = "";
|
||||
$sercet = "2RxmtM";
|
||||
if ($version == 1) {
|
||||
$time = time();
|
||||
$hash = md5($time . '_' . md5($time . '_' . $sercet));
|
||||
} else {
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_exists('get_parent_id')) {
|
||||
function get_parent_id($user_id)
|
||||
{
|
||||
@@ -712,12 +485,7 @@ if(!function_exists('enum_dir')){
|
||||
return $list ;
|
||||
}
|
||||
}
|
||||
if(!function_exists('generateShortUniqueID')){
|
||||
function generateShortUniqueID($length = 8) {
|
||||
// 生成指定长度的随机字节,转为 Base64 编码并去除不必要字符
|
||||
return substr(bin2hex(random_bytes($length / 2)), 0, $length);
|
||||
}
|
||||
}
|
||||
|
||||
if(!function_exists('get_user_rights')){
|
||||
function get_user_rights($user_id):array{
|
||||
$user_id = idDecode($user_id);
|
||||
@@ -764,4 +532,70 @@ if(!function_exists('__my__template_inputs')){
|
||||
return [$template, $vars, $app, $plugin];
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
if(!function_exists('update_user_level')){
|
||||
function update_user_level($user_id,$count=0){
|
||||
$levels = [
|
||||
0,
|
||||
50,
|
||||
100,
|
||||
1000,
|
||||
5000,
|
||||
20000,
|
||||
];
|
||||
$level = 0;
|
||||
foreach($levels as $k=>$v){
|
||||
if($count>=$v){
|
||||
$level= $k;
|
||||
}else{
|
||||
break;
|
||||
}
|
||||
}
|
||||
\support\think\Db::name('user')->where('id',$user_id)->data(['level'=>$level])->save();
|
||||
|
||||
}}
|
||||
|
||||
if(!function_exists('build_user_team')){
|
||||
function build_user_team($user){
|
||||
// 插入自己的团队关系 (自己是自己的后代)
|
||||
$teamData = [
|
||||
[
|
||||
'ancestor_id' => $user['id'],
|
||||
'descendant_id' => $user['id'],
|
||||
'depth' => 0,
|
||||
'status' => 0,
|
||||
]
|
||||
];
|
||||
// 2. 处理团队关系(如果有推荐人)
|
||||
if ($user['parent_id']) {
|
||||
|
||||
parent_info( $user['id'],[
|
||||
'id' => $user['parent_id'],
|
||||
'username' => \support\think\Db::name('user')->where('id',$user['parent_id'])->value('username')
|
||||
]);
|
||||
// 获取推荐人所有的上级关系,生成新用户的团队关系
|
||||
$ancestors = \support\think\Db::name('user_team')
|
||||
->where('descendant_id', $user['parent_id'])
|
||||
->select();
|
||||
/** @var \app\model\UserTeam $ancestor */
|
||||
// 插入新用户与祖先的关系
|
||||
foreach ($ancestors as $ancestor) {
|
||||
$teamData[] = [
|
||||
'ancestor_id' => $ancestor['ancestor_id'],
|
||||
'descendant_id' => $user['id'],
|
||||
'depth' => $ancestor['depth'] + 1,
|
||||
'status' => 1, // 默认状态为 0,表示无效
|
||||
];
|
||||
}
|
||||
}
|
||||
// 批量插入关系
|
||||
try {
|
||||
if($teamData){
|
||||
\support\think\Db::name('user_team')->insertAll($teamData);
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
cp($e->getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
+300
-87
@@ -2,13 +2,14 @@
|
||||
namespace app\mcp;
|
||||
|
||||
use PhpMcp\Server\Server;
|
||||
use PhpMcp\Server\ServerBuilder;
|
||||
use PhpMcp\Server\Transports\StdioServerTransport;
|
||||
use PhpMcp\Server\Transports\StreamableHttpServerTransport;
|
||||
use PhpMcp\Server\Transports\HttpServerTransport;
|
||||
use PhpMcp\Server\Attributes\McpTool;
|
||||
use PhpMcp\Server\Attributes\McpResource;
|
||||
use PhpMcp\Server\Attributes\McpPrompt;
|
||||
use PhpMcp\Server\Attributes\Schema;
|
||||
use PhpMcp\Server\Defaults\BasicContainer;
|
||||
use PhpMcp\Schema\Tool;
|
||||
use PhpMcp\Schema\Resource;
|
||||
use PhpMcp\Schema\ToolAnnotations;
|
||||
use PhpMcp\Schema\Annotations;
|
||||
use think\facade\Db;
|
||||
use support\Log;
|
||||
|
||||
@@ -116,14 +117,21 @@ class McpService
|
||||
{
|
||||
// 确保 logger 不为 null
|
||||
try {
|
||||
// 尝试获取 mcp 通道
|
||||
$this->logger = Log::channel('mcp');
|
||||
if (!$this->logger) {
|
||||
// 如果 mcp 通道不存在,使用默认通道
|
||||
$this->logger = Log::channel('default');
|
||||
}
|
||||
} catch (\Throwable $e) {
|
||||
// 如果所有通道都失败,创建一个简单的 logger
|
||||
$this->logger = Log::channel('default');
|
||||
try {
|
||||
// 如果 mcp 通道不存在,尝试使用默认通道
|
||||
$this->logger = Log::channel('default');
|
||||
} catch (\Throwable $e) {
|
||||
// 如果所有通道都失败,创建一个简单的 logger
|
||||
$this->logger = new class {
|
||||
public function info($message, $context = []) {}
|
||||
public function error($message, $context = []) {}
|
||||
public function warning($message, $context = []) {}
|
||||
public function addRecord($level, $message, $context = []) {}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// 读取MCP配置文件
|
||||
@@ -193,18 +201,24 @@ class McpService
|
||||
$this->heartbeatInterval = $mcpConfig['heartbeat_interval'];
|
||||
}
|
||||
|
||||
Log::channel('mcp')->info('MCP配置加载成功', [
|
||||
'timeout' => $this->timeout,
|
||||
'connect_timeout' => $this->connectTimeout,
|
||||
'read_timeout' => $this->readTimeout,
|
||||
'retry_attempts' => $this->retryAttempts,
|
||||
'retry_delay' => $this->retryDelay,
|
||||
'heartbeat_enabled' => $this->heartbeatEnabled ?? false,
|
||||
'heartbeat_interval' => $this->heartbeatInterval ?? 30
|
||||
]);
|
||||
// 安全地记录日志
|
||||
if (method_exists($this->logger, 'info')) {
|
||||
$this->logger->info('MCP配置加载成功', [
|
||||
'timeout' => $this->timeout,
|
||||
'connect_timeout' => $this->connectTimeout,
|
||||
'read_timeout' => $this->readTimeout,
|
||||
'retry_attempts' => $this->retryAttempts,
|
||||
'retry_delay' => $this->retryDelay,
|
||||
'heartbeat_enabled' => $this->heartbeatEnabled ?? false,
|
||||
'heartbeat_interval' => $this->heartbeatInterval ?? 30
|
||||
]);
|
||||
}
|
||||
|
||||
} catch (\Exception $e) {
|
||||
Log::warning('MCP配置加载失败,使用默认配置: ' . $e->getMessage());
|
||||
// 安全地记录警告
|
||||
if (method_exists($this->logger, 'warning')) {
|
||||
$this->logger->warning('MCP配置加载失败,使用默认配置: ' . $e->getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -291,29 +305,44 @@ class McpService
|
||||
while ($attempts < $this->retryAttempts) {
|
||||
try {
|
||||
$attempts++;
|
||||
Log::channel('mcp')->info("执行{$operationName},第{$attempts}次尝试");
|
||||
// 安全地记录日志
|
||||
if (method_exists($this->logger, 'info')) {
|
||||
$this->logger->info("执行{$operationName},第{$attempts}次尝试");
|
||||
}
|
||||
|
||||
$result = $operation();
|
||||
|
||||
if ($attempts > 1) {
|
||||
Log::channel('mcp')->info("{$operationName}在第{$attempts}次尝试后成功");
|
||||
// 安全地记录日志
|
||||
if (method_exists($this->logger, 'info')) {
|
||||
$this->logger->info("{$operationName}在第{$attempts}次尝试后成功");
|
||||
}
|
||||
}
|
||||
|
||||
return $result;
|
||||
|
||||
} catch (\Exception $e) {
|
||||
$lastException = $e;
|
||||
Log::warning("{$operationName}第{$attempts}次尝试失败: " . $e->getMessage());
|
||||
// 安全地记录警告
|
||||
if (method_exists($this->logger, 'warning')) {
|
||||
$this->logger->warning("{$operationName}第{$attempts}次尝试失败: " . $e->getMessage());
|
||||
}
|
||||
|
||||
if ($attempts < $this->retryAttempts) {
|
||||
$delay = $this->retryDelay * pow(1.5, $attempts - 1); // 指数退避
|
||||
Log::channel('mcp')->info("等待{$delay}ms后重试");
|
||||
// 安全地记录日志
|
||||
if (method_exists($this->logger, 'info')) {
|
||||
$this->logger->info("等待{$delay}ms后重试");
|
||||
}
|
||||
usleep($delay * 1000);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Log::channel('mcp')->error("{$operationName}在{$this->retryAttempts}次尝试后仍然失败");
|
||||
// 安全地记录错误
|
||||
if (method_exists($this->logger, 'error')) {
|
||||
$this->logger->error("{$operationName}在{$this->retryAttempts}次尝试后仍然失败");
|
||||
}
|
||||
throw $lastException;
|
||||
}
|
||||
|
||||
@@ -343,27 +372,11 @@ class McpService
|
||||
}
|
||||
|
||||
$this->server = $builder->withContainer($container)
|
||||
->withTool([self::class, 'handleDbQuery'], 'db-query', '执行数据库查询操作(仅支持SELECT语句)')
|
||||
->withTool([self::class, 'handleSysConfig'], 'sys-config', '获取系统配置信息')
|
||||
->withTool([self::class, 'handleWriteLog'], 'write-log', '写入系统日志')
|
||||
->withTool([self::class, 'handleFileOperation'], 'file-operation', '文件读写操作')
|
||||
->withTool([self::class, 'handleUserManagement'], 'user-management', '用户管理相关操作')
|
||||
->withTool([self::class, 'handleSystemInfo'], 'system-info', '获取系统运行信息')
|
||||
->withTool([self::class, 'handleCreateController'], 'controller', '生成webman控制器文件')
|
||||
->withTool([self::class, 'handleCreateModel'], 'model', '生成webman模型文件')
|
||||
->withTool([self::class, 'handleCreateView'], 'view', '生成webman视图文件')
|
||||
->withTool([self::class, 'handleCreateJs'], 'js', '生成webman JS文件')
|
||||
->withTool([self::class, 'handleCreateApi'], 'api', '生成webman API接口文件')
|
||||
->withTool([self::class, 'handleCurd'], 'curd', '生成webman CURD模块')
|
||||
->withTool([self::class, 'handleAddon'], 'addon', '生成webman 插件模块')
|
||||
->withTool([self::class, 'handleMenu'], 'menu', '生成webman 菜单模块')
|
||||
->withTool([self::class, 'handleCreateTable'], 'table', '创建数据库表格, 支持字段信息、类型、注释等')
|
||||
->withTool([self::class, 'handleThinkCommand'], 'think-command', '执行webman框架命令')
|
||||
->withTool([self::class, 'handleMcpCommand'], 'mcp-command', '执行MCP专用命令')
|
||||
->withTool([self::class, 'handleWebmanCommand'], 'webman-command', '执行webman框架命令')
|
||||
->withPrompt([self::class, 'handleWithPrompt'], 'with-prompt', '通过自然语言描述生成数据库表、控制器、模型等')
|
||||
->withResource([self::class, 'handleConfigResource'], 'config://system', 'config-system', '系统配置信息资源', 'application/json')
|
||||
->withResource([self::class, 'handleSchemaResource'], 'schema://database', 'schema-database', '数据库表结构信息资源', 'application/json')
|
||||
// 使用属性发现自动注册工具、资源和提示
|
||||
->discover(
|
||||
basePath: __DIR__ . '/..',
|
||||
scanDirs: ['mcp']
|
||||
)
|
||||
->build();
|
||||
return $this->server;
|
||||
}
|
||||
@@ -376,7 +389,13 @@ class McpService
|
||||
* @param array $params 查询参数
|
||||
* @return array
|
||||
*/
|
||||
public function handleDbQuery(string $query, array $params = []): array
|
||||
#[McpTool(name: 'db-query', description: '执行数据库查询操作(仅支持SELECT语句)')]
|
||||
public function handleDbQuery(
|
||||
#[Schema(type: 'string', description: 'SQL查询语句')]
|
||||
string $query,
|
||||
#[Schema(type: 'array', description: '查询参数')]
|
||||
array $params = []
|
||||
): array
|
||||
{
|
||||
try {
|
||||
if (empty($query)) {
|
||||
@@ -400,7 +419,10 @@ class McpService
|
||||
];
|
||||
|
||||
} catch (\Exception $e) {
|
||||
Log::channel('mcp')->error('MCP数据库查询错误: ' . $e->getMessage());
|
||||
// 安全地记录错误
|
||||
if (method_exists($this->logger, 'error')) {
|
||||
$this->logger->error('MCP数据库查询错误: ' . $e->getMessage());
|
||||
}
|
||||
return [
|
||||
'success' => false,
|
||||
'error' => $e->getMessage()
|
||||
@@ -413,7 +435,11 @@ class McpService
|
||||
* @param string $key 配置键名(可选)
|
||||
* @return array
|
||||
*/
|
||||
public function handleSysConfig(string $key = ''): array
|
||||
#[McpTool(name: 'sys-config', description: '获取系统配置信息')]
|
||||
public function handleSysConfig(
|
||||
#[Schema(type: 'string', description: '配置键名(可选)')]
|
||||
string $key = ''
|
||||
): array
|
||||
{
|
||||
try {
|
||||
if (empty($key)) {
|
||||
@@ -430,7 +456,7 @@ class McpService
|
||||
'database' => config('thinkorm.connections.mysql.database', ''),
|
||||
],
|
||||
'cache' => [
|
||||
'default' => config('plugin.bilulanlv.think-cache.app.default', 'redis'),
|
||||
'default' => config('think-cache.app.default', 'redis'),
|
||||
'stores' => array_keys(config('cache.stores', [])),
|
||||
]
|
||||
];
|
||||
@@ -451,7 +477,15 @@ class McpService
|
||||
* @param array $context 上下文数据
|
||||
* @return string
|
||||
*/
|
||||
public function handleWriteLog(string $message, string $level = 'info', array $context = []): string
|
||||
#[McpTool(name: 'write-log', description: '写入系统日志')]
|
||||
public function handleWriteLog(
|
||||
#[Schema(type: 'string', description: '日志消息')]
|
||||
string $message,
|
||||
#[Schema(type: 'string', description: '日志级别')]
|
||||
string $level = 'info',
|
||||
#[Schema(type: 'array', description: '上下文数据')]
|
||||
array $context = []
|
||||
): string
|
||||
{
|
||||
try {
|
||||
if (empty($message)) {
|
||||
@@ -469,7 +503,10 @@ class McpService
|
||||
return "日志记录成功 [级别: {$level}]";
|
||||
|
||||
} catch (\Exception $e) {
|
||||
Log::channel('mcp')->error('MCP日志写入错误: ' . $e->getMessage());
|
||||
// 安全地记录错误
|
||||
if (method_exists($this->logger, 'error')) {
|
||||
$this->logger->error('MCP日志写入错误: ' . $e->getMessage());
|
||||
}
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
@@ -480,7 +517,13 @@ class McpService
|
||||
* @param string $filepath 文件路径
|
||||
* @return array
|
||||
*/
|
||||
public function handleFileOperation(string $operation, string $filepath): array
|
||||
#[McpTool(name: 'file-operation', description: '文件读写操作')]
|
||||
public function handleFileOperation(
|
||||
#[Schema(type: 'string', description: '操作类型')]
|
||||
string $operation,
|
||||
#[Schema(type: 'string', description: '文件路径')]
|
||||
string $filepath
|
||||
): array
|
||||
{
|
||||
try {
|
||||
if (empty($operation) || empty($filepath)) {
|
||||
@@ -541,7 +584,10 @@ class McpService
|
||||
}
|
||||
|
||||
} catch (\Exception $e) {
|
||||
Log::channel('mcp')->error('MCP文件操作错误: ' . $e->getMessage());
|
||||
// 安全地记录错误
|
||||
if (method_exists($this->logger, 'error')) {
|
||||
$this->logger->error('MCP文件操作错误: ' . $e->getMessage());
|
||||
}
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
@@ -553,7 +599,15 @@ class McpService
|
||||
* @param int $limit 返回数量限制(可选)
|
||||
* @return array
|
||||
*/
|
||||
public function handleUserManagement(string $action, int $userId = 0, int $limit = 10): array
|
||||
#[McpTool(name: 'user-management', description: '用户管理相关操作')]
|
||||
public function handleUserManagement(
|
||||
#[Schema(type: 'string', description: '操作类型')]
|
||||
string $action,
|
||||
#[Schema(type: 'integer', description: '用户ID(可选)')]
|
||||
int $userId = 0,
|
||||
#[Schema(type: 'integer', description: '返回数量限制(可选)')]
|
||||
int $limit = 10
|
||||
): array
|
||||
{
|
||||
try {
|
||||
switch ($action) {
|
||||
@@ -599,7 +653,10 @@ class McpService
|
||||
}
|
||||
|
||||
} catch (\Exception $e) {
|
||||
Log::channel('mcp')->error('MCP用户管理错误: ' . $e->getMessage());
|
||||
// 安全地记录错误
|
||||
if (method_exists($this->logger, 'error')) {
|
||||
$this->logger->error('MCP用户管理错误: ' . $e->getMessage());
|
||||
}
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
@@ -609,7 +666,11 @@ class McpService
|
||||
* @param string $type 信息类型
|
||||
* @return array
|
||||
*/
|
||||
public function handleSystemInfo(string $type = 'general'): array
|
||||
#[McpTool(name: 'system-info', description: '获取系统运行信息')]
|
||||
public function handleSystemInfo(
|
||||
#[Schema(type: 'string', description: '信息类型')]
|
||||
string $type = 'general'
|
||||
): array
|
||||
{
|
||||
try {
|
||||
switch ($type) {
|
||||
@@ -650,7 +711,7 @@ class McpService
|
||||
|
||||
case 'cache':
|
||||
return [
|
||||
'default_driver' => config('plugin.bilulanlv.think-cache.app.default', 'redis'),
|
||||
'default_driver' => config('think-cache.app.default', 'redis'),
|
||||
'opcache_enabled' => function_exists('opcache_get_status') && opcache_get_status() !== false,
|
||||
'redis_available' => extension_loaded('redis'),
|
||||
'memcached_available' => extension_loaded('memcached'),
|
||||
@@ -661,7 +722,10 @@ class McpService
|
||||
}
|
||||
|
||||
} catch (\Exception $e) {
|
||||
Log::channel('mcp')->error('MCP系统信息错误: ' . $e->getMessage());
|
||||
// 安全地记录错误
|
||||
if (method_exists($this->logger, 'error')) {
|
||||
$this->logger->error('MCP系统信息错误: ' . $e->getMessage());
|
||||
}
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
@@ -688,6 +752,7 @@ class McpService
|
||||
* 处理配置资源
|
||||
* @return string
|
||||
*/
|
||||
#[McpResource(uri: 'config://system', name: 'config-system', description: '系统配置信息资源', contentType: 'application/json')]
|
||||
public function handleConfigResource(): string
|
||||
{
|
||||
$configs = [
|
||||
@@ -697,7 +762,7 @@ class McpService
|
||||
'charset' => config('thinkorm.connections.mysql.charset', 'utf8mb4'),
|
||||
'debug' => config('thinkorm.connections.mysql.trigger_sql', true),
|
||||
],
|
||||
'cache' => config('plugin.bilulanlv.think-cache.app', []),
|
||||
'cache' => config('think-cache.app', []),
|
||||
'session' => config('session', []),
|
||||
'log' => config('log', []),
|
||||
];
|
||||
@@ -709,6 +774,7 @@ class McpService
|
||||
* 处理数据库模式资源
|
||||
* @return string
|
||||
*/
|
||||
#[McpResource(uri: 'schema://database', name: 'schema-database', description: '数据库表结构信息资源', contentType: 'application/json')]
|
||||
public function handleSchemaResource(): string
|
||||
{
|
||||
try {
|
||||
@@ -727,7 +793,10 @@ class McpService
|
||||
return json_encode($schema, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE);
|
||||
|
||||
} catch (\Exception $e) {
|
||||
Log::channel('mcp')->error('MCP数据库模式获取错误: ' . $e->getMessage());
|
||||
// 安全地记录错误
|
||||
if (method_exists($this->logger, 'error')) {
|
||||
$this->logger->error('MCP数据库模式获取错误: ' . $e->getMessage());
|
||||
}
|
||||
return json_encode(['error' => $e->getMessage()], JSON_UNESCAPED_UNICODE);
|
||||
}
|
||||
}
|
||||
@@ -772,11 +841,17 @@ class McpService
|
||||
$server = $this->buildServer();
|
||||
$transport = new StdioServerTransport();
|
||||
|
||||
Log::channel('mcp')->info('MCP STDIO服务器启动成功');
|
||||
// 安全地记录日志
|
||||
if (method_exists($this->logger, 'info')) {
|
||||
$this->logger->info('MCP STDIO服务器启动成功');
|
||||
}
|
||||
$server->listen($transport);
|
||||
|
||||
} catch (\Exception $e) {
|
||||
Log::channel('mcp')->error('MCP STDIO服务器启动失败: ' . $e->getMessage());
|
||||
// 安全地记录错误
|
||||
if (method_exists($this->logger, 'error')) {
|
||||
$this->logger->error('MCP STDIO服务器启动失败: ' . $e->getMessage());
|
||||
}
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
@@ -793,10 +868,16 @@ class McpService
|
||||
$server = $this->buildServer();
|
||||
$server->listen($transport);
|
||||
|
||||
Log::channel('mcp')->info('MCP服务器启动成功');
|
||||
// 安全地记录日志
|
||||
if (method_exists($this->logger, 'info')) {
|
||||
$this->logger->info('MCP服务器启动成功');
|
||||
}
|
||||
|
||||
} catch (\Exception $e) {
|
||||
Log::channel('mcp')->error('MCP服务器启动失败: ' . $e->getMessage());
|
||||
// 安全地记录错误
|
||||
if (method_exists($this->logger, 'error')) {
|
||||
$this->logger->error('MCP服务器启动失败: ' . $e->getMessage());
|
||||
}
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
@@ -811,13 +892,19 @@ class McpService
|
||||
$this->startHeartbeat();
|
||||
|
||||
$server = $this->buildServer();
|
||||
$transport = new \PhpMcp\Server\Transports\StreamableHttpServerTransport($host, $port, $mcpPath);
|
||||
$transport = new StreamableHttpServerTransport($host, $port, $mcpPath);
|
||||
|
||||
Log::channel('mcp')->info("MCP SSE服务器启动成功,监听地址: http://{$host}:{$port}/{$mcpPath}");
|
||||
// 安全地记录日志
|
||||
if (method_exists($this->logger, 'info')) {
|
||||
$this->logger->info("MCP SSE服务器启动成功,监听地址: http://{$host}:{$port}/{$mcpPath}");
|
||||
}
|
||||
$server->listen($transport);
|
||||
|
||||
} catch (\Exception $e) {
|
||||
Log::channel('mcp')->error('MCP SSE服务器启动失败: ' . $e->getMessage());
|
||||
// 安全地记录错误
|
||||
if (method_exists($this->logger, 'error')) {
|
||||
$this->logger->error('MCP SSE服务器启动失败: ' . $e->getMessage());
|
||||
}
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
@@ -832,13 +919,19 @@ class McpService
|
||||
$this->startHeartbeat();
|
||||
|
||||
$server = $this->buildServer();
|
||||
$transport = new \PhpMcp\Server\Transports\HttpServerTransport($host, $port, $mcpPath);
|
||||
$transport = new HttpServerTransport($host, $port, $mcpPath);
|
||||
|
||||
Log::channel('mcp')->info("MCP HTTP服务器启动成功,监听地址: http://{$host}:{$port}/{$mcpPath}");
|
||||
// 安全地记录日志
|
||||
if (method_exists($this->logger, 'info')) {
|
||||
$this->logger->info("MCP HTTP服务器启动成功,监听地址: http://{$host}:{$port}/{$mcpPath}");
|
||||
}
|
||||
$server->listen($transport);
|
||||
|
||||
} catch (\Exception $e) {
|
||||
Log::channel('mcp')->error('MCP HTTP服务器启动失败: ' . $e->getMessage());
|
||||
// 安全地记录错误
|
||||
if (method_exists($this->logger, 'error')) {
|
||||
$this->logger->error('MCP HTTP服务器启动失败: ' . $e->getMessage());
|
||||
}
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
@@ -877,7 +970,17 @@ class McpService
|
||||
* @param string $description 控制器描述 (可选)
|
||||
* @return array
|
||||
*/
|
||||
public function handleCreateController(string $module, string $controller, array $fields = [], string $description = ''): array
|
||||
#[McpTool(name: 'controller', description: '生成webman控制器文件')]
|
||||
public function handleCreateController(
|
||||
#[Schema(type: 'string', description: '模块名称 (admin/api/frontend等)')]
|
||||
string $module,
|
||||
#[Schema(type: 'string', description: '控制器名称')]
|
||||
string $controller,
|
||||
#[Schema(type: 'array', description: '字段信息 (可选)')]
|
||||
array $fields = [],
|
||||
#[Schema(type: 'string', description: '控制器描述 (可选)')]
|
||||
string $description = ''
|
||||
): array
|
||||
{
|
||||
try {
|
||||
// 生成控制器类名
|
||||
@@ -934,7 +1037,17 @@ class McpService
|
||||
* @param string $description 模型描述 (可选)
|
||||
* @return array
|
||||
*/
|
||||
public function handleCreateModel(string $modelName, array $fields = [], string $tableName = '', string $description = ''): array
|
||||
#[McpTool(name: 'model', description: '生成webman模型文件')]
|
||||
public function handleCreateModel(
|
||||
#[Schema(type: 'string', description: '模型名称')]
|
||||
string $modelName,
|
||||
#[Schema(type: 'array', description: '字段信息 (可选)')]
|
||||
array $fields = [],
|
||||
#[Schema(type: 'string', description: '表名 (可选,默认使用模型名)')]
|
||||
string $tableName = '',
|
||||
#[Schema(type: 'string', description: '模型描述 (可选)')]
|
||||
string $description = ''
|
||||
): array
|
||||
{
|
||||
try {
|
||||
// 生成模型类名
|
||||
@@ -1078,7 +1191,19 @@ class {$modelClass} extends Base
|
||||
* @param string $charset 字符集 (默认 utf8mb4)
|
||||
* @return array
|
||||
*/
|
||||
public function handleCreateTable(string $tableName, array $fields, string $tableComment = '', string $engine = 'InnoDB', string $charset = 'utf8mb4'): array
|
||||
#[McpTool(name: 'table', description: '创建数据库表格,支持字段信息、类型、注释等')]
|
||||
public function handleCreateTable(
|
||||
#[Schema(type: 'string', description: '表名')]
|
||||
string $tableName,
|
||||
#[Schema(type: 'array', description: '字段信息数组')]
|
||||
array $fields,
|
||||
#[Schema(type: 'string', description: '表注释')]
|
||||
string $tableComment = '',
|
||||
#[Schema(type: 'string', description: '存储引擎 (默认 InnoDB)')]
|
||||
string $engine = 'InnoDB',
|
||||
#[Schema(type: 'string', description: '字符集 (默认 utf8mb4)')]
|
||||
string $charset = 'utf8mb4'
|
||||
): array
|
||||
{
|
||||
try {
|
||||
// 验证表名
|
||||
@@ -1293,7 +1418,19 @@ class {$modelClass} extends Base
|
||||
* @param array $options 其他选项
|
||||
* @return array
|
||||
*/
|
||||
public function handleCurd(string $tableName, string $module = 'admin', array $fields = [], string $description = '', array $options = []): array
|
||||
#[McpTool(name: 'curd', description: '生成webman CURD模块')]
|
||||
public function handleCurd(
|
||||
#[Schema(type: 'string', description: '表名')]
|
||||
string $tableName,
|
||||
#[Schema(type: 'string', description: '模块名称 (默认 admin)')]
|
||||
string $module = 'admin',
|
||||
#[Schema(type: 'array', description: '字段信息 (可选)')]
|
||||
array $fields = [],
|
||||
#[Schema(type: 'string', description: '描述 (可选)')]
|
||||
string $description = '',
|
||||
#[Schema(type: 'array', description: '选项 (可选)')]
|
||||
array $options = []
|
||||
): array
|
||||
{
|
||||
try {
|
||||
// 构建命令行参数
|
||||
@@ -1359,7 +1496,15 @@ class {$modelClass} extends Base
|
||||
* @param array $options 其他选项
|
||||
* @return array
|
||||
*/
|
||||
public function handleAddon(string $action, string $addonName = '', array $options = []): array
|
||||
#[McpTool(name: 'addon', description: '生成webman 插件模块')]
|
||||
public function handleAddon(
|
||||
#[Schema(type: 'string', description: '操作类型')]
|
||||
string $action,
|
||||
#[Schema(type: 'string', description: '插件名称 (可选)')]
|
||||
string $addonName = '',
|
||||
#[Schema(type: 'array', description: '选项 (可选)')]
|
||||
array $options = []
|
||||
): array
|
||||
{
|
||||
try {
|
||||
// 构建命令行参数
|
||||
@@ -1459,7 +1604,15 @@ class {$modelClass} extends Base
|
||||
* @param array $options 其他选项
|
||||
* @return array
|
||||
*/
|
||||
public function handleMenu(string $action, array $menuData = [], array $options = []): array
|
||||
#[McpTool(name: 'menu', description: '生成webman 菜单模块')]
|
||||
public function handleMenu(
|
||||
#[Schema(type: 'string', description: '操作类型')]
|
||||
string $action,
|
||||
#[Schema(type: 'array', description: '菜单数据 (可选)')]
|
||||
array $menuData = [],
|
||||
#[Schema(type: 'array', description: '选项 (可选)')]
|
||||
array $options = []
|
||||
): array
|
||||
{
|
||||
try {
|
||||
// 构建命令行参数
|
||||
@@ -1533,7 +1686,13 @@ class {$modelClass} extends Base
|
||||
* @param string $type 生成类型 (table/controller/model/js/api/view/all)
|
||||
* @return array
|
||||
*/
|
||||
public function handleWithPrompt(string $prompt, string $type = 'all'): array
|
||||
#[McpPrompt(name: 'with-prompt', description: '通过自然语言描述生成数据库表、控制器、模型等')]
|
||||
public function handleWithPrompt(
|
||||
#[Schema(type: 'string', description: '自然语言描述')]
|
||||
string $prompt,
|
||||
#[Schema(type: 'string', description: '生成类型')]
|
||||
string $type = 'all'
|
||||
): array
|
||||
{
|
||||
try {
|
||||
if (empty($prompt)) {
|
||||
@@ -2181,11 +2340,21 @@ class {$modelClass} extends Base
|
||||
* @param string $description 描述 (可选)
|
||||
* @return array
|
||||
*/
|
||||
public function handleCreateJs(string $module, string $controller, array $fields = [], string $description = ''): array
|
||||
#[McpTool(name: 'js', description: '生成webman JS文件')]
|
||||
public function handleCreateJs(
|
||||
#[Schema(type: 'string', description: '模块名称')]
|
||||
string $module,
|
||||
#[Schema(type: 'string', description: 'JS文件名')]
|
||||
string $name,
|
||||
#[Schema(type: 'array', description: 'JS数据 (可选)')]
|
||||
array $data = [],
|
||||
#[Schema(type: 'string', description: 'JS描述 (可选)')]
|
||||
string $description = ''
|
||||
): array
|
||||
{
|
||||
try {
|
||||
// 生成JS文件名
|
||||
$jsFileName = strtolower($controller);
|
||||
$jsFileName = strtolower($name);
|
||||
|
||||
$jsPath = "plugin/admin/public/js/{$jsFileName}.js";
|
||||
if ($module == 'frontend') {
|
||||
@@ -2200,7 +2369,7 @@ class {$modelClass} extends Base
|
||||
}
|
||||
|
||||
// 生成JS内容
|
||||
$jsContent = $this->generateJsContent($module, $controller, $fields, $description);
|
||||
$jsContent = $this->generateJsContent($module, $name, $data, $description);
|
||||
|
||||
// 确保目录存在
|
||||
$dir = dirname($jsPath);
|
||||
@@ -2238,7 +2407,17 @@ class {$modelClass} extends Base
|
||||
* @param string $description 描述 (可选)
|
||||
* @return array
|
||||
*/
|
||||
public function handleCreateApi(string $controller, string $module = 'api', array $fields = [], string $description = ''): array
|
||||
#[McpTool(name: 'api', description: '生成webman API接口文件')]
|
||||
public function handleCreateApi(
|
||||
#[Schema(type: 'string', description: '控制器名称')]
|
||||
string $controller,
|
||||
#[Schema(type: 'string', description: '模块名称 (默认 api)')]
|
||||
string $module = 'api',
|
||||
#[Schema(type: 'array', description: '字段信息 (可选)')]
|
||||
array $fields = [],
|
||||
#[Schema(type: 'string', description: 'API描述 (可选)')]
|
||||
string $description = ''
|
||||
): array
|
||||
{
|
||||
try {
|
||||
// 生成API控制器类名
|
||||
@@ -2455,7 +2634,17 @@ class {$modelClass} extends Base
|
||||
* @param string $description 描述 (可选)
|
||||
* @return array
|
||||
*/
|
||||
public function handleCreateView(string $module, string $controller, array $fields = [], string $description = ''): array
|
||||
#[McpTool(name: 'view', description: '生成webman视图文件')]
|
||||
public function handleCreateView(
|
||||
#[Schema(type: 'string', description: '模块名称')]
|
||||
string $module,
|
||||
#[Schema(type: 'string', description: '控制器名称')]
|
||||
string $controller,
|
||||
#[Schema(type: 'array', description: '字段信息 (可选)')]
|
||||
array $fields = [],
|
||||
#[Schema(type: 'string', description: '视图描述 (可选)')]
|
||||
string $description = ''
|
||||
): array
|
||||
{
|
||||
try {
|
||||
// 生成视图文件名
|
||||
@@ -3143,7 +3332,15 @@ class {$modelClass} extends Base
|
||||
* @param array $options 命令选项 (可选)
|
||||
* @return array
|
||||
*/
|
||||
public function handleThinkCommand(string $command, array $params = [], array $options = []): array
|
||||
#[McpTool(name: 'think-command', description: '执行webman框架命令')]
|
||||
public function handleThinkCommand(
|
||||
#[Schema(type: 'string', description: '命令名称')]
|
||||
string $command,
|
||||
#[Schema(type: 'array', description: '命令参数 (可选)')]
|
||||
array $params = [],
|
||||
#[Schema(type: 'array', description: '命令选项 (可选)')]
|
||||
array $options = []
|
||||
): array
|
||||
{
|
||||
try {
|
||||
// 验证命令是否为安全的内置命令
|
||||
@@ -3283,7 +3480,15 @@ class {$modelClass} extends Base
|
||||
* @param array $options 选项
|
||||
* @return array
|
||||
*/
|
||||
public function handleMcpCommand(string $command, array $params = [], array $options = []): array
|
||||
#[McpTool(name: 'mcp-command', description: '执行MCP专用命令')]
|
||||
public function handleMcpCommand(
|
||||
#[Schema(type: 'string', description: '命令名称')]
|
||||
string $command,
|
||||
#[Schema(type: 'array', description: '命令参数 (可选)')]
|
||||
array $params = [],
|
||||
#[Schema(type: 'array', description: '命令选项 (可选)')]
|
||||
array $options = []
|
||||
): array
|
||||
{
|
||||
try {
|
||||
// MCP 专用命令列表
|
||||
@@ -3374,7 +3579,15 @@ class {$modelClass} extends Base
|
||||
* @param array $options 选项
|
||||
* @return array
|
||||
*/
|
||||
public function handleWebmanCommand(string $command, array $params = [], array $options = []): array
|
||||
#[McpTool(name: 'webman-command', description: '执行webman框架命令')]
|
||||
public function handleWebmanCommand(
|
||||
#[Schema(type: 'string', description: '命令名称')]
|
||||
string $command,
|
||||
#[Schema(type: 'array', description: '命令参数 (可选)')]
|
||||
array $params = [],
|
||||
#[Schema(type: 'array', description: '命令选项 (可选)')]
|
||||
array $options = []
|
||||
): array
|
||||
{
|
||||
try {
|
||||
// webman 框架命令列表
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
namespace app\api\controller;
|
||||
use think\facade\Db;
|
||||
use support\Request;
|
||||
use taoser\facade\Validate;
|
||||
use Tinywan\Validate\Facade\Validate;
|
||||
use support\Jwt;
|
||||
use hg\apidoc\annotation as Apidoc;
|
||||
/**
|
||||
@@ -64,7 +64,7 @@ class {$controllerClass} extends BaseController
|
||||
*/
|
||||
public function create()
|
||||
{
|
||||
//captcha_verfiy('image','create_address');
|
||||
//captcha_verify('image','create_address');
|
||||
//* @Apidoc\Param("code", type="string", require=true, desc="图形验证码 event=create_address")
|
||||
//$trade_password = input('trade_password');
|
||||
//\support\Jwt::verify_trade_password($trade_password);
|
||||
@@ -95,7 +95,7 @@ class {$controllerClass} extends BaseController
|
||||
*/
|
||||
public function update()
|
||||
{
|
||||
//captcha_verfiy('image','update_address');
|
||||
//captcha_verify('image','update_address');
|
||||
//$trade_password = input('trade_password');
|
||||
//\support\Jwt::verify_trade_password($trade_password);
|
||||
$data = [
|
||||
|
||||
@@ -23,7 +23,6 @@ class ActionHook implements MiddlewareInterface
|
||||
$response = response('',204,$headers);
|
||||
return $response;
|
||||
}
|
||||
log_alert($request->controller);
|
||||
// 禁止直接访问beforeAction afterAction
|
||||
if (substr($request->action,0,9) === '__before_' || substr($request->action,0,8) === '__after_') {
|
||||
$callback = Route::getFallback() ?? function () {
|
||||
|
||||
@@ -4,7 +4,7 @@ namespace app\middleware;
|
||||
|
||||
use app\controller\MetricsController;
|
||||
use support\Request;
|
||||
use support\Response;
|
||||
use Webman\Http\Response;
|
||||
|
||||
/**
|
||||
* 指标收集中间件
|
||||
|
||||
+27
-9
@@ -5,9 +5,12 @@ namespace app\model;
|
||||
* 相册模型
|
||||
* @property integer $id 主键(ID)
|
||||
* @property integer $user_id 用户ID
|
||||
* @property integer $group_id 内容
|
||||
* @property string $url 图片
|
||||
* @property integer $group_id 群组ID
|
||||
* @property integer $userID 用户ID
|
||||
* @property integer $groupID 群组ID
|
||||
* @property string $title 标题
|
||||
* @property int $image 封面图片ID
|
||||
* @property int $weigh 排序权重,越小越靠前
|
||||
* @property integer $created_at 创建时间
|
||||
* @property integer $updated_at 更新时间
|
||||
* @property integer $status 状态(0:隐藏 1:正常)
|
||||
@@ -22,30 +25,45 @@ class Album extends Base
|
||||
'insert' => [
|
||||
'status' => 1,
|
||||
],
|
||||
'append'=>[
|
||||
'userID',
|
||||
'groupID'
|
||||
]
|
||||
]);
|
||||
}
|
||||
public static function onAfterInsert($row){
|
||||
$changeData = $row->getChangedData();
|
||||
if(isset($changeData['url'])) {
|
||||
Files::where('path',$changeData['url'])->inc('use_count');
|
||||
if(isset($changeData['image'])) {
|
||||
Files::where('path',$changeData['image'])->inc('use_count');
|
||||
};
|
||||
}
|
||||
|
||||
public static function onAfterUpdate($row){
|
||||
$OrgData = $row->getOrigin();
|
||||
$changeData = $row->getChangedData();
|
||||
if(isset($OrgData['url']) && $OrgData['url']) {
|
||||
if(isset($OrgData['image']) && $OrgData['image']) {
|
||||
\support\Log::info('OrgData string');
|
||||
Files::where('path',$OrgData['url'])->dec('use_count');
|
||||
Files::where('path',$OrgData['image'])->dec('use_count');
|
||||
};
|
||||
if(isset($changeData['url']) && $changeData['url']) {
|
||||
if(isset($changeData['image']) && $changeData['image']) {
|
||||
\support\Log::info('changeData string');
|
||||
Files::where('path',$changeData['url'])->inc('use_count');
|
||||
Files::where('path',$changeData['image'])->inc('use_count');
|
||||
};
|
||||
}
|
||||
|
||||
public static function onBeforeDelete($row){
|
||||
if($row->total>0){
|
||||
return false;
|
||||
}
|
||||
}
|
||||
public static function onAfterDelete($row){
|
||||
Files::where('path',$row->url)->dec('use_count');
|
||||
Files::where('path',$row->image)->dec('use_count');
|
||||
}
|
||||
function getGroupIDAttr($v,$row){
|
||||
return $v?:$row['group_id'];
|
||||
}
|
||||
function getUserIDAttr($v,$row){
|
||||
return $v?:$row['user_id'];
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -42,9 +42,9 @@ class Archives extends Base
|
||||
Db::name('content')->where('id',$row->id)->update($values);
|
||||
}
|
||||
}
|
||||
public static function onAfterDelete($row)
|
||||
{
|
||||
Db::name('content')->where('id',$row->id)->delete();
|
||||
public static function onAfterDelete($row){
|
||||
//删除副表
|
||||
Db::name('content')->where("id", $row['id'])->delete();
|
||||
}
|
||||
/**
|
||||
* 批量设置数据
|
||||
|
||||
@@ -33,7 +33,7 @@ class BalanceLog extends Base
|
||||
// ],
|
||||
]);
|
||||
}
|
||||
public static function create(array|object $data, array $allowField = [], bool $replace = false):\think\model\contract\Modelable
|
||||
public static function create(array|object $data, array $allowField = [], bool $replace = false, string $suffix = ''):\think\model\contract\Modelable
|
||||
{
|
||||
$model = new static();
|
||||
if(isset($data['currency'])){
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
<?php
|
||||
|
||||
namespace app\model;
|
||||
|
||||
use app\model\Base;
|
||||
/**
|
||||
* @property integer $id 主键(ID) - 无注释
|
||||
* @property integer $user_id 用户ID
|
||||
* @property string $content_type 内容类型 enum('text', 'image', 'file', 'video', 'link')
|
||||
* @property string $content 收藏内容本体
|
||||
* @property array $tags 用户自定义标签
|
||||
* @property bool $is_pinned 是否置顶
|
||||
* @property integer $status 状态
|
||||
* @property integer $created_at 创建时间
|
||||
* @property integer $updated_at 更新时间
|
||||
*/
|
||||
class Collection extends Base
|
||||
{
|
||||
|
||||
public function user()
|
||||
{
|
||||
return $this->belongsTo('User', 'user_id', 'id');//->setEagerlyType(0);
|
||||
}
|
||||
public function setTagsAttr($v)
|
||||
{
|
||||
if(is_array($v)){
|
||||
return implode(',',$v);
|
||||
}
|
||||
return $v;
|
||||
}
|
||||
public function getTagsAttr($v)
|
||||
{
|
||||
if($v && is_string($v)){
|
||||
return explode(',',$v);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
function getStatusList(){
|
||||
return [
|
||||
'0' => '兑换中',
|
||||
'1' => '成功',
|
||||
'-1' => '失败',
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
<?php
|
||||
namespace app\model;
|
||||
|
||||
/**
|
||||
* 相册模型
|
||||
* @property integer $id 主键(ID)
|
||||
* @property integer $album_id 用户ID
|
||||
* @property integer $user_id 用户ID
|
||||
* @property integer $group_id 内容
|
||||
* @property string $url 图片
|
||||
* @property string $title 标题
|
||||
* @property integer $created_at 创建时间
|
||||
* @property integer $updated_at 更新时间
|
||||
* @property integer $status 状态(0:隐藏 1:正常)
|
||||
*/
|
||||
class Gallery extends Base
|
||||
{
|
||||
protected $name = 'gallery';
|
||||
|
||||
protected function getOptions(): array
|
||||
{
|
||||
return array_merge(parent::getOptions(), [
|
||||
'insert' => [
|
||||
'status' => 1,
|
||||
],
|
||||
'append'=>[
|
||||
'userID',
|
||||
'groupID'
|
||||
]
|
||||
]);
|
||||
}
|
||||
function getGroupIDAttr($v,$row){
|
||||
return $v?:$row['group_id'];
|
||||
}
|
||||
function getUserIDAttr($v,$row){
|
||||
return $v?:$row['user_id'];
|
||||
}
|
||||
public static function onAfterInsert($row){
|
||||
$changeData = $row->getChangedData();
|
||||
if(isset($changeData['url'])) {
|
||||
Files::where('path',$changeData['url'])->inc('use_count');
|
||||
};
|
||||
}
|
||||
|
||||
public static function onAfterUpdate($row){
|
||||
$OrgData = $row->getOrigin();
|
||||
$changeData = $row->getChangedData();
|
||||
if(isset($OrgData['url']) && $OrgData['url']) {
|
||||
\support\Log::info('OrgData string');
|
||||
Files::where('path',$OrgData['url'])->dec('use_count');
|
||||
};
|
||||
if(isset($changeData['url']) && $changeData['url']) {
|
||||
\support\Log::info('changeData string');
|
||||
Files::where('path',$changeData['url'])->inc('use_count');
|
||||
};
|
||||
}
|
||||
|
||||
public static function onAfterDelete($row){
|
||||
Files::where('path',$row->url)->dec('use_count');
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,102 @@
|
||||
<?php
|
||||
|
||||
namespace app\model;
|
||||
|
||||
/**
|
||||
* 支付订单模型
|
||||
*/
|
||||
class PaymentOrder extends Base
|
||||
{
|
||||
/**
|
||||
* 数据表名
|
||||
* @var string
|
||||
*/
|
||||
protected $name = 'payment_order';
|
||||
|
||||
/**
|
||||
* 获取状态列表
|
||||
* @return array
|
||||
*/
|
||||
public static function getStatusList(): array
|
||||
{
|
||||
return \app\enum\Payment\Status::toArray();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取类型列表
|
||||
* @return array
|
||||
*/
|
||||
public static function getTypeList(): array
|
||||
{
|
||||
return \app\enum\Payment\Type::toArray();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取支付类型列表
|
||||
* @return array
|
||||
*/
|
||||
public static function getPayTypeList(): array
|
||||
{
|
||||
return \app\enum\Payment\Method::toArray();
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据订单号查询
|
||||
* @param string $orderNo
|
||||
* @return array|null
|
||||
*/
|
||||
public static function findByOrderNo(string $orderNo): ?array
|
||||
{
|
||||
return self::where('order_no', $orderNo)->find();
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新订单状态
|
||||
* @param string $orderNo
|
||||
* @param string $status
|
||||
* @param array $data
|
||||
* @return bool
|
||||
*/
|
||||
public static function updateStatus(string $orderNo, string $status, array $data = []): bool
|
||||
{
|
||||
$updateData = array_merge([
|
||||
'status' => $status,
|
||||
'updated_at' => time(),
|
||||
], $data);
|
||||
|
||||
return self::where('order_no', $orderNo)->update($updateData);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取状态文本
|
||||
* @param string $status
|
||||
* @return string
|
||||
*/
|
||||
public static function getStatusText(string $status): string
|
||||
{
|
||||
$list = self::getStatusList();
|
||||
return $list[$status] ?? $status;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取类型文本
|
||||
* @param string $type
|
||||
* @return string
|
||||
*/
|
||||
public static function getTypeText(string $type): string
|
||||
{
|
||||
$list = self::getTypeList();
|
||||
return $list[$type] ?? $type;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取支付类型文本
|
||||
* @param string $payType
|
||||
* @return string
|
||||
*/
|
||||
public static function getPayTypeText(string $payType): string
|
||||
{
|
||||
$list = self::getPayTypeList();
|
||||
return $list[$payType] ?? $payType;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
<?php
|
||||
|
||||
namespace app\model;
|
||||
|
||||
/**
|
||||
* 退款模型
|
||||
*/
|
||||
class PaymentRefund extends Base
|
||||
{
|
||||
/**
|
||||
* 获取状态列表
|
||||
* @return array
|
||||
*/
|
||||
public static function getStatusList(): array
|
||||
{
|
||||
return [
|
||||
'success' => '退款成功',
|
||||
'fail' => '退款失败',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取支付类型列表
|
||||
* @return array
|
||||
*/
|
||||
public static function getPayTypeList(): array
|
||||
{
|
||||
return \app\enum\Payment\Method::toArray();
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据订单号查询
|
||||
* @param string $orderNo
|
||||
* @return array
|
||||
*/
|
||||
public static function findByOrderNo(string $orderNo): array
|
||||
{
|
||||
return self::where('order_no', $orderNo)->select()->toArray();
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据退款单号查询
|
||||
* @param string $refundNo
|
||||
* @return array|null
|
||||
*/
|
||||
public static function findByRefundNo(string $refundNo): ?array
|
||||
{
|
||||
return self::where('refund_no', $refundNo)->find();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取状态文本
|
||||
* @param string $status
|
||||
* @return string
|
||||
*/
|
||||
public static function getStatusText(string $status): string
|
||||
{
|
||||
$list = self::getStatusList();
|
||||
return $list[$status] ?? $status;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取支付类型文本
|
||||
* @param string $payType
|
||||
* @return string
|
||||
*/
|
||||
public static function getPayTypeText(string $payType): string
|
||||
{
|
||||
$list = self::getPayTypeList();
|
||||
return $list[$payType] ?? $payType;
|
||||
}
|
||||
}
|
||||
+4
-4
@@ -77,7 +77,7 @@ class User extends Base
|
||||
{
|
||||
//use \think\model\concern\SoftDelete;
|
||||
public static function onAfterInsert($row){
|
||||
$res = request()->IM->user->userRegister(idEncode($row->id),$row->nickname,cdnurl($row->avatar));
|
||||
$res = request()->IM->user->userRegister(\support\Encrypt::userIDencode($row->id),$row->nickname,cdnurl($row->avatar));
|
||||
}
|
||||
public static function onAfterUpdate($row){
|
||||
$changeData = $row->getChangedData();
|
||||
@@ -87,10 +87,10 @@ class User extends Base
|
||||
'nickname' => $row->nickname,
|
||||
'faceURL' => cdnurl($row->avatar)
|
||||
];
|
||||
request()->IM->user->updateUserInfo(idEncode($row->id),$sdata);
|
||||
request()->IM->user->updateUserInfo(\support\Encrypt::userIDencode($row->id),$sdata);
|
||||
}
|
||||
if(isset($changeData['status']) || $changeData['status'] == '0'){
|
||||
request()->IM->user->forceLogout(idEncode($row->id));
|
||||
request()->IM->user->forceLogout(\support\Encrypt::userIDencode($row->id));
|
||||
}
|
||||
if(isset($changeData['expire_at']) || isset($changeData['role_id'])){
|
||||
cache('user_rights_'.$row->id,null);
|
||||
@@ -107,7 +107,7 @@ class User extends Base
|
||||
foreach(Config('site.allow_currencys') as $currency){
|
||||
(new \app\model\BalanceLog)->setSuffix('_'.$currency)->where('user_id',(int)$row->id)->delete();
|
||||
}
|
||||
request()->IM->user->forceLogout(idEncode($row->id));
|
||||
request()->IM->user->forceLogout(\support\Encrypt::userIDencode($row->id));
|
||||
}
|
||||
public function role()
|
||||
{
|
||||
|
||||
@@ -6,6 +6,7 @@ use app\model\Base;
|
||||
/**
|
||||
* @property integer $user_id 用户ID
|
||||
* @property integer $direct_total 直推数量
|
||||
* @property integer $vip_total VIP数量
|
||||
* @property integer $team_total 团队成员数量
|
||||
* @property float $consume 消费统计
|
||||
* @property float $sales 销售额
|
||||
|
||||
@@ -36,7 +36,6 @@ class Sms implements Consumer
|
||||
);
|
||||
try {
|
||||
$res = get($url);
|
||||
log_alert($res.$statusStr[$res]);
|
||||
\support\Log::channel('mail')->alert($data['email']."短信已经发送");
|
||||
} catch (\Throwable $th) {
|
||||
\support\Log::channel('mail')->alert('发送短信出错:'.$th->getMessage());
|
||||
|
||||
@@ -0,0 +1,78 @@
|
||||
<?php
|
||||
|
||||
namespace app\queue\single;
|
||||
use support\think\Db;
|
||||
use Webman\RedisQueue\Consumer;
|
||||
/**
|
||||
* 团队奖励
|
||||
*/
|
||||
class Settlement implements Consumer
|
||||
{
|
||||
// 要消费的队列名
|
||||
public $queue = 'Settlement';
|
||||
|
||||
// 连接名,对应 plugin/webman/redis-queue/redis.php 里的连接`
|
||||
public $connection = 'default';
|
||||
|
||||
// 消费
|
||||
public function consume($data)
|
||||
{
|
||||
$commission_rates = [0,0.05,0.1,0.15,0.2,0.25];
|
||||
$amount = $data['amount'];
|
||||
$user_id = $data['user_id'];
|
||||
$list = Db::name('user_team')
|
||||
->alias('ut')
|
||||
->where('ut.ancestor_id',$user_id)
|
||||
->join('user as u','u.id=ut.descendant_id')
|
||||
->field('u.id,u.level')
|
||||
->where('ut.depth','>',0)
|
||||
->order('ut.depth','asc')
|
||||
->column('ut.descendant_id');
|
||||
$yifen = 0;
|
||||
$last_level = 0;
|
||||
foreach($list as $user){
|
||||
if($user['level']<=$last_level){
|
||||
$this->log('用户ID:'.$user['id'].'\t,等级:'.$user['level'].'\t,$last_level:'.$last_level.'\t跳过');
|
||||
continue;
|
||||
}
|
||||
$last_level = $user['level'];
|
||||
$commission_rate = $commission_rates[$user['level']] - $yifen;
|
||||
if($commission_rate>0){
|
||||
$yifen += $commission_rate;
|
||||
$commission_amount = bcmul($amount,$commission_rate,4);
|
||||
$this->log('用户ID:'.$user['id'].'\t,等级:'.$user['level'].'\t,比例:'.$commission_rate.'\t,金额:'.$commission_amount);
|
||||
//\app\model\User::score($user['id'],$commission_amount,\app\enum\BalanceType::SEE_POINT_AWARD,'团队奖励:'.$user_id);
|
||||
}
|
||||
//已经是最大等级了
|
||||
if($yifen>=0.25 || $user['level'] == 5){
|
||||
$this->log('$yifen:'.$yifen.'\t,结束');
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// 无需反序列化
|
||||
//var_export($data); // 输出 ['to' => 'tom@gmail.com', 'content' => 'hello']
|
||||
}
|
||||
function log($data){
|
||||
\support\Log::info('settlement')->info(json_encode($data));
|
||||
}
|
||||
// 消费失败回调
|
||||
/*
|
||||
$package = [
|
||||
'id' => 1357277951, // 消息ID
|
||||
'time' => 1709170510, // 消息时间
|
||||
'delay' => 0, // 延迟时间
|
||||
'attempts' => 2, // 消费次数
|
||||
'queue' => 'send-mail', // 队列名
|
||||
'data' => ['to' => 'tom@gmail.com', 'content' => 'hello'], // 消息内容
|
||||
'max_attempts' => 5, // 最大重试次数
|
||||
'error' => '错误信息' // 错误信息
|
||||
]
|
||||
*/
|
||||
public function onConsumeFailure(\Throwable $e, $package)
|
||||
{
|
||||
if($package['attempts'] >= $package['max_attempts']){
|
||||
$this->log($package['data']);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,42 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace app\queue\single;
|
||||
|
||||
use Webman\RedisQueue\Consumer;
|
||||
/**
|
||||
* 团队奖励
|
||||
*/
|
||||
class buildTeam implements Consumer
|
||||
{
|
||||
// 要消费的队列名
|
||||
public $queue = 'buildTeam';
|
||||
|
||||
// 连接名,对应 plugin/webman/redis-queue/redis.php 里的连接`
|
||||
public $connection = 'default';
|
||||
|
||||
// 消费
|
||||
public function consume($data)
|
||||
{
|
||||
// 无需反序列化
|
||||
//var_export($data); // 输出 ['to' => 'tom@gmail.com', 'content' => 'hello']
|
||||
}
|
||||
// 消费失败回调
|
||||
/*
|
||||
$package = [
|
||||
'id' => 1357277951, // 消息ID
|
||||
'time' => 1709170510, // 消息时间
|
||||
'delay' => 0, // 延迟时间
|
||||
'attempts' => 2, // 消费次数
|
||||
'queue' => 'send-mail', // 队列名
|
||||
'data' => ['to' => 'tom@gmail.com', 'content' => 'hello'], // 消息内容
|
||||
'max_attempts' => 5, // 最大重试次数
|
||||
'error' => '错误信息' // 错误信息
|
||||
]
|
||||
*/
|
||||
public function onConsumeFailure(\Throwable $e, $package)
|
||||
{
|
||||
if($package['attempts'] >= $package['max_attempts']){
|
||||
\support\Log::error(json_encode($package['data']));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
<?php
|
||||
namespace app\validate;
|
||||
|
||||
use taoser\Validate;
|
||||
use Tinywan\Validate\Validate;
|
||||
|
||||
class User extends Validate
|
||||
{
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<?php
|
||||
namespace app\validate;
|
||||
|
||||
use taoser\Validate;
|
||||
use Tinywan\Validate\Validate;
|
||||
|
||||
class Withdrawl extends Validate
|
||||
{
|
||||
|
||||
+587
-61
@@ -10,9 +10,9 @@
|
||||
{literal}
|
||||
<style>
|
||||
:root {
|
||||
--primary: #4361ee;
|
||||
--primary-light: #ebf0ff;
|
||||
--secondary: #3f37c9;
|
||||
--primary: #27ba57;
|
||||
--primary-light: #f4fff7;
|
||||
--secondary: #27ba57;
|
||||
--success: #4cc9f0;
|
||||
--text: #2b2d42;
|
||||
--text-light: #8d99ae;
|
||||
@@ -39,7 +39,7 @@
|
||||
min-height: 100vh;
|
||||
line-height: 1.6;
|
||||
padding: 20px;
|
||||
background-image: radial-gradient(at 0 0,rgba(var(--primary-rgb),0.05) 0,transparent 50%),radial-gradient(at 100% 100%,rgba(var(--secondary-rgb),0.05) 0,transparent 50%)
|
||||
background-image: radial-gradient(at 0 0,rgba(39, 186, 87, 0.05) 0,transparent 50%),radial-gradient(at 100% 100%,rgba(39, 186, 87, 0.05) 0,transparent 50%)
|
||||
}
|
||||
|
||||
.container {
|
||||
@@ -50,7 +50,12 @@
|
||||
box-shadow: var(--shadow);
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
z-index: 1
|
||||
z-index: 1;
|
||||
transition: var(--transition);
|
||||
}
|
||||
|
||||
.container:hover {
|
||||
box-shadow: 0 8px 32px rgba(0,0,0,0.12);
|
||||
}
|
||||
|
||||
.container::before {
|
||||
@@ -67,7 +72,9 @@
|
||||
|
||||
.header {
|
||||
padding: 40px 40px 30px;
|
||||
text-align: center
|
||||
text-align: center;
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.logo {
|
||||
@@ -79,7 +86,13 @@
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
box-shadow: var(--shadow);
|
||||
margin-bottom: 20px
|
||||
margin-bottom: 20px;
|
||||
transition: var(--transition);
|
||||
}
|
||||
|
||||
.logo:hover {
|
||||
transform: scale(1.05);
|
||||
box-shadow: 0 6px 20px rgba(0,0,0,0.12);
|
||||
}
|
||||
|
||||
.logo svg {
|
||||
@@ -93,11 +106,13 @@
|
||||
font-size: 24px;
|
||||
font-weight: 700;
|
||||
color: var(--white);
|
||||
margin-bottom: 8px
|
||||
margin-bottom: 8px;
|
||||
display: none;
|
||||
}
|
||||
|
||||
.header p {
|
||||
font-size: 15px
|
||||
font-size: 14pt;
|
||||
color: var(--text-light);
|
||||
}
|
||||
|
||||
.card {
|
||||
@@ -151,13 +166,37 @@
|
||||
background-color: var(--white);
|
||||
color: var(--text)
|
||||
}
|
||||
.form-control[readonly] {
|
||||
background-color: var(--border);
|
||||
color: #999;
|
||||
cursor: not-allowed
|
||||
}
|
||||
|
||||
.form-control:focus {
|
||||
border-color: var(--primary);
|
||||
box-shadow: 0 0 0 3px rgba(67,97,238,0.15);
|
||||
box-shadow: 0 0 0 3px rgba(39, 186, 87, 0.15);
|
||||
outline: 0
|
||||
}
|
||||
|
||||
.form-control.error {
|
||||
border-color: #f56c6c;
|
||||
}
|
||||
|
||||
.form-control.success {
|
||||
border-color: var(--primary);
|
||||
}
|
||||
|
||||
.error-message {
|
||||
color: #f56c6c;
|
||||
font-size: 12px;
|
||||
margin-top: 4px;
|
||||
display: none;
|
||||
}
|
||||
|
||||
.error-message.show {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.captcha-group {
|
||||
display: flex;
|
||||
gap: 12px
|
||||
@@ -177,11 +216,15 @@
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
transition: var(--transition);
|
||||
padding: 0 16px
|
||||
padding: 0 16px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.captcha-btn:hover {
|
||||
background-color: rgba(67,97,238,0.1)
|
||||
.captcha-btn:hover:not(:disabled) {
|
||||
background-color: rgba(39, 186, 87, 0.1);
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
|
||||
.captcha-btn:disabled {
|
||||
@@ -202,18 +245,26 @@
|
||||
cursor: pointer;
|
||||
transition: var(--transition);
|
||||
margin-top: 10px;
|
||||
box-shadow: 0 4px 12px rgba(67,97,238,0.2)
|
||||
box-shadow: 0 4px 12px rgba(39, 186, 87, 0.2);
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.btn:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 6px 16px rgba(67,97,238,0.3)
|
||||
box-shadow: 0 6px 16px rgba(39, 186, 87, 0.3)
|
||||
}
|
||||
|
||||
.btn:active {
|
||||
transform: translateY(0)
|
||||
}
|
||||
|
||||
.btn:disabled {
|
||||
opacity: 0.7;
|
||||
cursor: not-allowed;
|
||||
transform: none;
|
||||
}
|
||||
|
||||
.footer {
|
||||
margin-top: 30px;
|
||||
text-align: center;
|
||||
@@ -232,7 +283,6 @@
|
||||
text-decoration: underline
|
||||
}
|
||||
|
||||
|
||||
.other-options {
|
||||
margin-top: 20px;
|
||||
text-align: center;
|
||||
@@ -275,6 +325,14 @@
|
||||
height: 60px;
|
||||
border-radius: 15px;
|
||||
}
|
||||
|
||||
.header h1 {
|
||||
font-size: 20px;
|
||||
}
|
||||
|
||||
.header p {
|
||||
font-size: 12pt;
|
||||
}
|
||||
}
|
||||
/*成功卡片*/
|
||||
.success-card {
|
||||
@@ -440,6 +498,184 @@
|
||||
}
|
||||
}
|
||||
|
||||
.region .layui-select-title .layui-input{
|
||||
height: 51px;
|
||||
border-top-left-radius: 12px;
|
||||
border-bottom-left-radius: 12px;
|
||||
}
|
||||
|
||||
/* 密码强度指示器 */
|
||||
.password-strength {
|
||||
display: flex;
|
||||
gap: 4px;
|
||||
margin-top: 8px;
|
||||
}
|
||||
|
||||
.strength-bar {
|
||||
flex: 1;
|
||||
height: 4px;
|
||||
border-radius: 2px;
|
||||
background-color: var(--border);
|
||||
transition: var(--transition);
|
||||
}
|
||||
|
||||
.strength-bar.weak {
|
||||
background-color: #f56c6c;
|
||||
}
|
||||
|
||||
.strength-bar.medium {
|
||||
background-color: #e6a23c;
|
||||
}
|
||||
|
||||
.strength-bar.strong {
|
||||
background-color: var(--primary);
|
||||
}
|
||||
|
||||
.strength-text {
|
||||
font-size: 12px;
|
||||
color: var(--text-light);
|
||||
margin-top: 4px;
|
||||
}
|
||||
|
||||
.strength-text.weak {
|
||||
color: #f56c6c;
|
||||
}
|
||||
|
||||
.strength-text.medium {
|
||||
color: #e6a23c;
|
||||
}
|
||||
|
||||
.strength-text.strong {
|
||||
color: var(--primary);
|
||||
}
|
||||
|
||||
/* 加载动画 */
|
||||
.loading {
|
||||
display: inline-block;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
border: 2px solid rgba(255,255,255,0.3);
|
||||
border-radius: 50%;
|
||||
border-top-color: #fff;
|
||||
animation: spin 1s ease-in-out infinite;
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
to { transform: rotate(360deg); }
|
||||
}
|
||||
|
||||
/* 输入框图标 */
|
||||
.input-icon {
|
||||
position: absolute;
|
||||
left: 16px;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
color: var(--text-light);
|
||||
}
|
||||
|
||||
.form-control.has-icon {
|
||||
padding-left: 40px;
|
||||
}
|
||||
|
||||
/* 验证码按钮加载状态 */
|
||||
.captcha-btn.loading {
|
||||
position: relative;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.captcha-btn.loading::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
border: 2px solid rgba(39, 186, 87, 0.3);
|
||||
border-radius: 50%;
|
||||
border-top-color: var(--primary);
|
||||
animation: spin 1s ease-in-out infinite;
|
||||
}
|
||||
|
||||
/* 响应式优化 */
|
||||
@media (max-width: 768px) {
|
||||
.container {
|
||||
padding: 16px;
|
||||
max-width: 95%;
|
||||
}
|
||||
|
||||
.card {
|
||||
padding: 0 24px 32px;
|
||||
}
|
||||
|
||||
.form-group {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.btn {
|
||||
height: 48px;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.captcha-btn {
|
||||
min-width: 100px;
|
||||
font-size: 13px;
|
||||
}
|
||||
}
|
||||
|
||||
/* 动画效果 */
|
||||
@keyframes fadeInUp {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(20px);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
.form-container {
|
||||
animation: fadeInUp 0.3s ease-out;
|
||||
}
|
||||
|
||||
/* 错误消息优化 */
|
||||
.error-message {
|
||||
font-size: 12px;
|
||||
color: #f56c6c;
|
||||
margin-top: 4px;
|
||||
opacity: 0;
|
||||
height: 0;
|
||||
overflow: hidden;
|
||||
transition: var(--transition);
|
||||
}
|
||||
|
||||
.error-message.show {
|
||||
opacity: 1;
|
||||
height: auto;
|
||||
min-height: 16px;
|
||||
}
|
||||
|
||||
/* 星星装饰优化 */
|
||||
.star {
|
||||
position: absolute;
|
||||
background-color: #fff;
|
||||
border-radius: 50%;
|
||||
animation: twinkle 3s infinite;
|
||||
}
|
||||
|
||||
@keyframes twinkle {
|
||||
0%, 100% {
|
||||
opacity: 0.3;
|
||||
}
|
||||
50% {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
/* 庆祝效果 */
|
||||
.confetti {
|
||||
position: absolute;
|
||||
border-radius: 2px;
|
||||
}
|
||||
|
||||
</style>
|
||||
{/literal}
|
||||
</head>
|
||||
@@ -812,55 +1048,85 @@
|
||||
</svg>
|
||||
</div>
|
||||
<h1>欢迎回来</h1>
|
||||
<p>使用手机号码注册您的账户</p>
|
||||
<p>使用手机号或邮箱注册您的账户</p>
|
||||
</div>
|
||||
<div class="card" data-type="register">
|
||||
<!-- 注册表单 -->
|
||||
<div class="form-container active" id="register-form">
|
||||
<form id="registerForm" class="layui-form" method="post" action="/api/common/register">
|
||||
<div class="form-group">
|
||||
<label for="register-phone">手机号码</label>
|
||||
<div class="input-wrapper">
|
||||
<input type="tel" name="mobile" lay-reqtext="请输入您的手机号码" id="register-phone" class="form-control" placeholder="请输入您的手机号码" required lay-verify="required|phone">
|
||||
<input type="hidden" name="type" id="register_type" value="mobile" />
|
||||
<div class="layui-tab-item layui-show">
|
||||
<div class="form-group">
|
||||
<label for="register-phone">手机号/邮箱</label>
|
||||
<div class="input-wrapper">
|
||||
<i class="layui-icon layui-icon-cellphone input-icon"></i>
|
||||
<input type="text" name="mobile"
|
||||
lay-reqtext="请输入您的手机号或邮箱"
|
||||
id="register-phone"
|
||||
class="form-control has-icon"
|
||||
placeholder="请输入您的手机号或邮箱"
|
||||
required
|
||||
lay-verify="require|username" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div class="error-message" id="phone-error"></div>
|
||||
<div class="form-group" style="display: none;">
|
||||
<label for="register-password">我的昵称</label>
|
||||
<div class="input-wrapper">
|
||||
<input type="text" name="nickname" lay-reqtext="请设置您的昵称" id="register-nickname" class="form-control" placeholder="怎么称呼您" required lay-verify="required">
|
||||
<i class="layui-icon layui-icon-username input-icon"></i>
|
||||
<input type="text" name="nickname" lay-reqtext="请设置您的昵称" id="register-nickname" class="form-control has-icon" placeholder="给自己取一个好听的昵称吧" lay-verify="">
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="register-captcha">验证码</label>
|
||||
<div class="captcha-group">
|
||||
<div class="input-wrapper" style="flex: 1;">
|
||||
<input type="text" lay-reqtext="请输入验证码" name="code" id="register-captcha" class="form-control captcha-input"
|
||||
<i class="layui-icon layui-icon-vercode input-icon"></i>
|
||||
<input type="text" lay-reqtext="请输入验证码" name="code" id="register-captcha" class="form-control captcha-input has-icon"
|
||||
placeholder="请输入验证码" required lay-verify="required|number">
|
||||
</div>
|
||||
<button type="button" class="captcha-btn" id="register-get-captcha">获取验证码</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="error-message" id="captcha-error"></div>
|
||||
<div class="form-group">
|
||||
<label for="register-password">设置密码</label>
|
||||
<div class="input-wrapper">
|
||||
<input type="password" lay-reqtext="请设置您的登录密码" name="password" id="register-password" class="form-control" placeholder="请输入6-20位密码"
|
||||
<i class="layui-icon layui-icon-password input-icon"></i>
|
||||
<input type="password" lay-reqtext="请设置您的登录密码" name="password" id="register-password" class="form-control has-icon" placeholder="请输入6-20位密码"
|
||||
required lay-verify="required|password">
|
||||
</div>
|
||||
<div class="password-strength">
|
||||
<div class="strength-bar" id="strength-bar-1"></div>
|
||||
<div class="strength-bar" id="strength-bar-2"></div>
|
||||
<div class="strength-bar" id="strength-bar-3"></div>
|
||||
</div>
|
||||
<div class="strength-text" id="strength-text">密码强度:弱</div>
|
||||
</div>
|
||||
<div class="error-message" id="password-error"></div>
|
||||
<div class="form-group">
|
||||
<label for="register-password">确认密码</label>
|
||||
<div class="input-wrapper">
|
||||
<input type="password" lay-reqtext="请再次输入密码" name="repassword" id="register-repassword" class="form-control" placeholder="请输入6-20位密码"
|
||||
<i class="layui-icon layui-icon-password input-icon"></i>
|
||||
<input type="password" lay-reqtext="请再次输入密码" name="repassword" id="register-repassword" class="form-control has-icon" placeholder="请输入6-20位密码"
|
||||
required lay-verify="required|repassword">
|
||||
</div>
|
||||
</div>
|
||||
<div class="error-message" id="repassword-error"></div>
|
||||
<div class="form-group">
|
||||
<label for="register-password">邀请码</label>
|
||||
<div class="input-wrapper">
|
||||
<i class="layui-icon layui-icon-code input-icon"></i>
|
||||
<input type="text" readonly class="form-control has-icon" lay-reqtext="参数错误" value="{$invite_code}" name="invite_code" required lay-verify="required">
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<button type="button" class="btn" lay-submit lay-filter="register-form"><span>注册</span></button>
|
||||
</div>
|
||||
<input type="hidden" lay-reqtext="参数错误" value="{$invite_code}" name="invite_code" required lay-verify="required">
|
||||
</form>
|
||||
<div class="other-options">
|
||||
已有账号?<a href="/shunliao336.apk" id="switch-to-login">立即下载APP</a>
|
||||
已有账号?<a href="/" id="switch-to-login">立即下载APP</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="footer">
|
||||
@@ -882,7 +1148,7 @@
|
||||
<i class="layui-icon layui-icon-username"></i>
|
||||
<span id="username">Cansnow</span>
|
||||
</div>
|
||||
<a href="/shunliao336.apk" class="btn">下载</a>
|
||||
<a href="/" class="btn">下载</a>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
@@ -892,15 +1158,162 @@
|
||||
{literal}
|
||||
<script type="text/javascript">
|
||||
layui.use(function(){
|
||||
var element = layui.element;
|
||||
var layer = layui.layer;
|
||||
var form = layui.form;
|
||||
var $ = layui.jquery;
|
||||
$('[data-type="register"]').show();
|
||||
$('[data-type="success"]').hide();
|
||||
|
||||
// 初始化页面
|
||||
function initPage() {
|
||||
$('[data-type="register"]').show();
|
||||
$('[data-type="success"]').hide();
|
||||
generateStars();
|
||||
}
|
||||
|
||||
// 生成星星装饰
|
||||
function generateStars() {
|
||||
const stars = document.getElementById('stars');
|
||||
if (stars) {
|
||||
for (let i = 0; i < 50; i++) {
|
||||
const star = document.createElement('div');
|
||||
star.className = 'star';
|
||||
star.style.left = Math.random() * 100 + '%';
|
||||
star.style.top = Math.random() * 100 + '%';
|
||||
star.style.width = Math.random() * 3 + 1 + 'px';
|
||||
star.style.height = star.style.width;
|
||||
star.style.animationDelay = Math.random() * 3 + 's';
|
||||
stars.appendChild(star);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 密码强度检测
|
||||
function checkPasswordStrength(password) {
|
||||
let strength = 0;
|
||||
if (password.length >= 6) strength++;
|
||||
if (password.length >= 10) strength++;
|
||||
if (/[A-Z]/.test(password)) strength++;
|
||||
if (/[0-9]/.test(password)) strength++;
|
||||
if (/[^A-Za-z0-9]/.test(password)) strength++;
|
||||
|
||||
// 更新密码强度指示器
|
||||
const strengthBars = document.querySelectorAll('.strength-bar');
|
||||
const strengthText = document.getElementById('strength-text');
|
||||
|
||||
strengthBars.forEach((bar, index) => {
|
||||
bar.className = 'strength-bar';
|
||||
if (index < Math.floor(strength / 2)) {
|
||||
if (strength <= 2) {
|
||||
bar.classList.add('weak');
|
||||
} else if (strength <= 4) {
|
||||
bar.classList.add('medium');
|
||||
} else {
|
||||
bar.classList.add('strong');
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if (strength <= 2) {
|
||||
strengthText.textContent = '密码强度:弱';
|
||||
strengthText.className = 'strength-text weak';
|
||||
} else if (strength <= 4) {
|
||||
strengthText.textContent = '密码强度:中';
|
||||
strengthText.className = 'strength-text medium';
|
||||
} else {
|
||||
strengthText.textContent = '密码强度:强';
|
||||
strengthText.className = 'strength-text strong';
|
||||
}
|
||||
}
|
||||
|
||||
// 输入框验证
|
||||
function validateInput(input, errorId, message) {
|
||||
const errorElement = document.getElementById(errorId);
|
||||
if (input.value.trim() === '') {
|
||||
input.classList.add('error');
|
||||
errorElement.textContent = message;
|
||||
errorElement.classList.add('show');
|
||||
return false;
|
||||
} else {
|
||||
input.classList.remove('error');
|
||||
errorElement.classList.remove('show');
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// 实时验证
|
||||
$('#register-phone').on('blur', function() {
|
||||
validateInput(this, 'phone-error', '请输入手机号或邮箱');
|
||||
});
|
||||
|
||||
$('#register-captcha').on('blur', function() {
|
||||
validateInput(this, 'captcha-error', '请输入验证码');
|
||||
});
|
||||
|
||||
$('#register-password').on('input', function() {
|
||||
checkPasswordStrength(this.value);
|
||||
validateInput(this, 'password-error', '请设置密码');
|
||||
});
|
||||
|
||||
$('#register-password').on('blur', function() {
|
||||
validateInput(this, 'password-error', '请设置密码');
|
||||
});
|
||||
|
||||
$('#register-repassword').on('blur', function() {
|
||||
if (this.value !== $('#register-password').val()) {
|
||||
this.classList.add('error');
|
||||
document.getElementById('repassword-error').textContent = '两次密码输入不一致';
|
||||
document.getElementById('repassword-error').classList.add('show');
|
||||
return false;
|
||||
} else {
|
||||
this.classList.remove('error');
|
||||
document.getElementById('repassword-error').classList.remove('show');
|
||||
return true;
|
||||
}
|
||||
});
|
||||
|
||||
// hash 地址定位
|
||||
var hashName = 'register_type'; // hash 名称
|
||||
var layid = location.hash.replace(new RegExp('^#'+ hashName + '='), ''); // 获取 lay-id 值
|
||||
layid = layid || 'mobile';
|
||||
// 初始切换
|
||||
element.tabChange('registe-type', layid);
|
||||
// 切换事件
|
||||
element.on('tab(registe-type)', function(obj){
|
||||
location.hash = hashName +'='+ this.getAttribute('lay-id');
|
||||
$('#register_type').val(this.getAttribute('lay-id'));
|
||||
});
|
||||
|
||||
form.verify({
|
||||
username:function(v,elem){
|
||||
if(v.indexOf('@') == -1){
|
||||
if(!v){return '手机号不能为空';}
|
||||
const reg = /^1[3-9]\d{9}$/;
|
||||
if(!reg.test(v)){
|
||||
return '手机号码格式错误';
|
||||
}
|
||||
$('#register_type').val('mobile');
|
||||
}else{
|
||||
if(!v){return '手机号或邮箱不能为空';}
|
||||
const reg = /^[a-zA-Z0-9._%+-]+@(?:[a-zA-Z0-9-]+\.)+[a-zA-Z]{2,}$/;
|
||||
if(!reg.test(v) || /[<>(){}[\]\\]/.test(v)){
|
||||
return '邮箱格式错误';
|
||||
}
|
||||
$('#register_type').val('email');
|
||||
}
|
||||
},
|
||||
phonerequire: function(value, elem) {
|
||||
if($('#register_type').val() == 'mobile'){
|
||||
if(!value){return '手机号不能为空';}
|
||||
}
|
||||
},
|
||||
emailrequire: function(value, elem) {
|
||||
if($('#register_type').val() == 'email'){
|
||||
if(!value){return '邮箱不能为空';}
|
||||
}
|
||||
},
|
||||
password: function(value, elem) {
|
||||
if (value.length < 6) {
|
||||
return '密码太过简单';
|
||||
return '密码长度不能少于6位';
|
||||
}
|
||||
},
|
||||
repassword: function(value, elem) {
|
||||
@@ -909,68 +1322,181 @@
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
function check_exist(callback){
|
||||
var elem = $('#register-phone');
|
||||
var v = elem.val();
|
||||
var msg='';
|
||||
var data={};
|
||||
var api_url='';
|
||||
if($('#register_type').val() == 'email'){
|
||||
api_url = '/api/Validate/check_email_exist';
|
||||
data={'email':v};
|
||||
msg='邮箱已经存在';
|
||||
}else{
|
||||
api_url = '/api/Validate/check_mobile_exist';
|
||||
data={'mobile':v};
|
||||
msg='手机号码已经存在';
|
||||
}
|
||||
$.post(api_url,data,function(res){
|
||||
if(res.code === 0){
|
||||
elem.removeClass('layui-form-danger').addClass('layui-form-danger');
|
||||
layer.msg(msg,{icon:2});
|
||||
return ;
|
||||
}
|
||||
callback && callback();
|
||||
});
|
||||
}
|
||||
|
||||
$('#register-get-captcha').on('click',function(){
|
||||
var isValid = form.validate('#register-phone');
|
||||
var msg = '请输入正确的手机号或邮箱';
|
||||
var val = $('#register-phone').val();
|
||||
var senddata = {
|
||||
type:$('#register_type').val(),
|
||||
mobile:val,
|
||||
'event':'register'
|
||||
};
|
||||
if($('#register_type').val() == 'email'){
|
||||
delete senddata['mobile'];
|
||||
senddata['email'] = val;
|
||||
}
|
||||
// 验证通过
|
||||
if(!isValid){
|
||||
layer.msg('请输入正确的手机号',{icon:2});
|
||||
layer.msg(msg,{icon:2});
|
||||
return ;
|
||||
}
|
||||
|
||||
var btn = $('#register-get-captcha').get(0);
|
||||
var phone = $('#register-phone').val();
|
||||
// 禁用按钮并开始倒计时
|
||||
btn.disabled = true;
|
||||
let countdown = 60;
|
||||
btn.innerHTML = `${countdown}秒后重新获取`;
|
||||
btn.classList.add('loading');
|
||||
|
||||
check_exist(function(){
|
||||
$.post('/api/common/captcha',senddata,function(res){
|
||||
btn.classList.remove('loading');
|
||||
if(res.code === 0){
|
||||
layer.msg(`验证码已发送`,{icon:1});
|
||||
let countdown = 60;
|
||||
btn.innerHTML = `${countdown}秒后重新获取`;
|
||||
|
||||
const timer = setInterval(() => {
|
||||
countdown--;
|
||||
btn.innerHTML = `${countdown}秒后重新获取`;
|
||||
const timer = setInterval(() => {
|
||||
countdown--;
|
||||
btn.innerHTML = `${countdown}秒后重新获取`;
|
||||
|
||||
if (countdown <= 0) {
|
||||
clearInterval(timer);
|
||||
if (countdown <= 0) {
|
||||
clearInterval(timer);
|
||||
btn.disabled = false;
|
||||
btn.innerHTML = '获取验证码';
|
||||
}
|
||||
}, 1000);
|
||||
}else{
|
||||
btn.disabled = false;
|
||||
btn.innerHTML = '获取验证码';
|
||||
layer.msg(res.msg,{icon:2});
|
||||
}
|
||||
}).fail(function() {
|
||||
btn.classList.remove('loading');
|
||||
btn.disabled = false;
|
||||
btn.innerHTML = '获取验证码';
|
||||
}
|
||||
}, 1000);
|
||||
|
||||
// 这里应该是发送验证码的AJAX请求
|
||||
console.log(`发送验证码到手机: ${phone}`);
|
||||
$.post('/api/common/captcha',{type:'mobile',mobile:phone,'event':'register'},function(res){
|
||||
if(res.code === 0){
|
||||
console.log(res);
|
||||
layer.msg(`验证码已发送到手机 ${phone} (模拟)`,{icon:1});
|
||||
}else{
|
||||
clearInterval(timer);
|
||||
btn.disabled = false;
|
||||
btn.innerHTML = '获取验证码';
|
||||
}
|
||||
});
|
||||
layer.msg('网络错误,请稍后重试',{icon:2});
|
||||
});
|
||||
})
|
||||
});
|
||||
|
||||
// 提交事件
|
||||
form.on('submit(register-form)', function(data){
|
||||
var field = data.field; // 获取表单字段值
|
||||
// 显示填写结果,仅作演示用
|
||||
field['type'] = 'mobile';
|
||||
console.log(field);
|
||||
// 此处可执行 Ajax 等操作
|
||||
|
||||
// 验证所有输入框
|
||||
const phoneValid = validateInput($('#register-phone')[0], 'phone-error', '请输入手机号或邮箱');
|
||||
const captchaValid = validateInput($('#register-captcha')[0], 'captcha-error', '请输入验证码');
|
||||
const passwordValid = validateInput($('#register-password')[0], 'password-error', '请设置密码');
|
||||
const repasswordValid = validateInput($('#register-repassword')[0], 'repassword-error', '请确认密码');
|
||||
|
||||
if (!phoneValid || !captchaValid || !passwordValid || !repasswordValid) {
|
||||
layer.msg('请完善表单信息',{icon:2});
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($('#register-password').val() !== $('#register-repassword').val()) {
|
||||
$('#register-repassword')[0].classList.add('error');
|
||||
document.getElementById('repassword-error').textContent = '两次密码输入不一致';
|
||||
document.getElementById('repassword-error').classList.add('show');
|
||||
layer.msg('两次密码输入不一致',{icon:2});
|
||||
return false;
|
||||
}
|
||||
|
||||
const btn = this;
|
||||
const originalText = btn.innerHTML;
|
||||
btn.innerHTML = `<span class="layui-icon layui-icon-loading-1 layui-anim layui-anim-rotate layui-anim-loop"></span> 注册中...`;
|
||||
btn.disabled = true;
|
||||
$.post('/api/common/register',field,function(res){
|
||||
if(field['type'] == 'email'){
|
||||
field['email'] = field['mobile'];
|
||||
delete field['mobile'];
|
||||
}
|
||||
$.post('/api/common/register', field, function(res){
|
||||
btn.innerHTML = originalText;
|
||||
btn.disabled = false;
|
||||
if(res.code === 0 ){
|
||||
// 更新成功页面的用户名
|
||||
if (res.data && res.data.username) {
|
||||
$('#username').text(res.data.username);
|
||||
}
|
||||
// 显示成功页面
|
||||
$('[data-type="register"]').hide();
|
||||
$('[data-type="success"]').show();
|
||||
// 生成庆祝效果
|
||||
createConfetti();
|
||||
}else{
|
||||
layer.msg(res.msg,{icon:2});
|
||||
}
|
||||
}).fail(function() {
|
||||
btn.innerHTML = originalText;
|
||||
btn.disabled = false;
|
||||
layer.msg('网络错误,请稍后重试',{icon:2});
|
||||
});
|
||||
|
||||
return false; // 阻止默认 form 跳转
|
||||
});
|
||||
|
||||
// 创建庆祝效果
|
||||
function createConfetti() {
|
||||
const successCard = document.querySelector('.success-card');
|
||||
if (!successCard) return;
|
||||
|
||||
for (let i = 0; i < 100; i++) {
|
||||
const confetti = document.createElement('div');
|
||||
confetti.className = 'confetti';
|
||||
confetti.style.left = Math.random() * 100 + '%';
|
||||
confetti.style.top = Math.random() * 100 + '%';
|
||||
confetti.style.backgroundColor = getRandomColor();
|
||||
confetti.style.width = Math.random() * 10 + 5 + 'px';
|
||||
confetti.style.height = Math.random() * 10 + 5 + 'px';
|
||||
confetti.style.opacity = 1;
|
||||
confetti.style.transform = 'translateY(0) rotate(0deg)';
|
||||
confetti.style.transition = 'all ' + (Math.random() * 3 + 2) + 's ease-out';
|
||||
|
||||
successCard.appendChild(confetti);
|
||||
|
||||
setTimeout(() => {
|
||||
confetti.style.opacity = 0;
|
||||
confetti.style.transform = 'translateY(100px) rotate(720deg)';
|
||||
setTimeout(() => {
|
||||
confetti.remove();
|
||||
}, 5000);
|
||||
}, 100);
|
||||
}
|
||||
}
|
||||
|
||||
// 获取随机颜色
|
||||
function getRandomColor() {
|
||||
const colors = ['#f00', '#0f0', '#00f', '#ff0', '#f0f', '#0ff', '#333', '#666', '#999'];
|
||||
return colors[Math.floor(Math.random() * colors.length)];
|
||||
}
|
||||
|
||||
// 初始化页面
|
||||
initPage();
|
||||
});
|
||||
|
||||
</script>
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
+5
-3
@@ -34,9 +34,7 @@
|
||||
"webman/captcha": "^1.0.5",
|
||||
"webman/event": "^1.0.5",
|
||||
"topthink/think-template": "^3.0.2",
|
||||
"bilulanlv/think-cache": "^1.0.9",
|
||||
"symfony/var-dumper": "7.2.6",
|
||||
"taoser/webman-validate": "^1.7.2",
|
||||
"hg/apidoc": "^5.3.0",
|
||||
"isszz/webman-hashids": "^0.0.1",
|
||||
"nette/mail": "^4.0.3",
|
||||
@@ -47,7 +45,11 @@
|
||||
"php-mcp/server": "^3.3",
|
||||
"intervention/image": "^2.7.2",
|
||||
"guzzlehttp/guzzle": "^7.5.0",
|
||||
"league/flysystem-aws-s3-v3": "^3.0"
|
||||
"league/flysystem-aws-s3-v3": "^3.0",
|
||||
"webman/think-cache": "^2.1",
|
||||
"symfony/translation": "^7.4",
|
||||
"tinywan/validate": "^1.0",
|
||||
"yansongda/pay": "^3.7"
|
||||
},
|
||||
"suggest": {
|
||||
"ext-event": "For better performance. "
|
||||
|
||||
+5
-4
@@ -22,14 +22,15 @@ return [
|
||||
'user.delete_successed' => [
|
||||
[app\event\User::class, 'delete_successed'],
|
||||
],
|
||||
'user.roleup' => [
|
||||
[app\event\User::class, 'roleup'],
|
||||
'user.role_up' => [
|
||||
[app\event\User::class, 'role_up'],
|
||||
],
|
||||
'user.role_buy' => [
|
||||
[app\event\User::class, 'role_buy'],
|
||||
],
|
||||
'product.buy' => [
|
||||
[app\event\Product::class, 'buy']
|
||||
],
|
||||
'role.buy' => [
|
||||
],
|
||||
'recharge.success' => [
|
||||
[app\event\Recharge::class, 'success']
|
||||
],
|
||||
|
||||
+16
-80
@@ -30,54 +30,6 @@ return [
|
||||
]
|
||||
],
|
||||
],
|
||||
'server' => [
|
||||
'handlers' => [
|
||||
[
|
||||
'class' => Monolog\Handler\RotatingFileHandler::class,
|
||||
'constructor' => [
|
||||
runtime_path() . '/logs/server.log',
|
||||
7, //$maxFiles
|
||||
Monolog\Logger::ALERT,
|
||||
],
|
||||
'formatter' => [
|
||||
'class' => Monolog\Formatter\LineFormatter::class,
|
||||
'constructor' => [null, 'Y-m-d H:i:s', true],
|
||||
],
|
||||
]
|
||||
],
|
||||
],
|
||||
'studio' => [
|
||||
'handlers' => [
|
||||
[
|
||||
'class' => Monolog\Handler\RotatingFileHandler::class,
|
||||
'constructor' => [
|
||||
runtime_path() . '/logs/studio.log',
|
||||
7, //$maxFiles
|
||||
Monolog\Logger::ALERT,
|
||||
],
|
||||
'formatter' => [
|
||||
'class' => Monolog\Formatter\LineFormatter::class,
|
||||
'constructor' => [null, 'Y-m-d H:i:s', true],
|
||||
],
|
||||
]
|
||||
],
|
||||
],
|
||||
'power' => [
|
||||
'handlers' => [
|
||||
[
|
||||
'class' => Monolog\Handler\RotatingFileHandler::class,
|
||||
'constructor' => [
|
||||
runtime_path() . '/logs/power.log',
|
||||
7, //$maxFiles
|
||||
Monolog\Logger::ALERT,
|
||||
],
|
||||
'formatter' => [
|
||||
'class' => Monolog\Formatter\LineFormatter::class,
|
||||
'constructor' => [null, 'Y-m-d H:i:s', true],
|
||||
],
|
||||
]
|
||||
],
|
||||
],
|
||||
'cansnow' => [
|
||||
'handlers' => [
|
||||
[
|
||||
@@ -94,38 +46,6 @@ return [
|
||||
]
|
||||
],
|
||||
],
|
||||
'role_buy' => [
|
||||
'handlers' => [
|
||||
[
|
||||
'class' => Monolog\Handler\RotatingFileHandler::class,
|
||||
'constructor' => [
|
||||
runtime_path() . '/logs/role_buy.log',
|
||||
7, //$maxFiles
|
||||
Monolog\Logger::DEBUG,
|
||||
],
|
||||
'formatter' => [
|
||||
'class' => Monolog\Formatter\LineFormatter::class,
|
||||
'constructor' => [null, 'Y-m-d H:i:s', true],
|
||||
],
|
||||
]
|
||||
],
|
||||
],
|
||||
'product_buy' => [
|
||||
'handlers' => [
|
||||
[
|
||||
'class' => Monolog\Handler\RotatingFileHandler::class,
|
||||
'constructor' => [
|
||||
runtime_path() . '/logs/product_buy.log',
|
||||
7, //$maxFiles
|
||||
Monolog\Logger::DEBUG,
|
||||
],
|
||||
'formatter' => [
|
||||
'class' => Monolog\Formatter\LineFormatter::class,
|
||||
'constructor' => [null, 'Y-m-d H:i:s', true],
|
||||
],
|
||||
]
|
||||
],
|
||||
],
|
||||
'mail' => [
|
||||
'handlers' => [
|
||||
[
|
||||
@@ -157,5 +77,21 @@ return [
|
||||
],
|
||||
]
|
||||
],
|
||||
],
|
||||
'settlement' => [
|
||||
'handlers' => [
|
||||
[
|
||||
'class' => Monolog\Handler\RotatingFileHandler::class,
|
||||
'constructor' => [
|
||||
runtime_path() . '/logs/settlement.log',
|
||||
7,
|
||||
Monolog\Logger::INFO,
|
||||
],
|
||||
'formatter' => [
|
||||
'class' => Monolog\Formatter\LineFormatter::class,
|
||||
'constructor' => [null, 'Y-m-d H:i:s', true],
|
||||
],
|
||||
]
|
||||
],
|
||||
]
|
||||
];
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
<?php
|
||||
return [
|
||||
'server' => 'http://127.0.0.1:10002',
|
||||
'admin' => 'imAdmin',
|
||||
'secret' => 'n1e5a6s6m7',
|
||||
];
|
||||
@@ -0,0 +1,81 @@
|
||||
<?php
|
||||
/**
|
||||
* @desc 支付配置文件
|
||||
* @author Tinywan(ShaoBo Wan)
|
||||
* @date 2022/03/11 20:15
|
||||
*/
|
||||
return [
|
||||
'_force' => true, // 注意,这个必须是true
|
||||
'alipay' => [
|
||||
'default' => [
|
||||
// 必填-支付宝分配的 app_id
|
||||
'app_id' => '2021006117606688',
|
||||
// 必填-应用私钥 字符串或路径
|
||||
'app_secret_cert' => 'tzNKV16DeoakZbzyN8Wy2g==',
|
||||
// 必填-应用公钥证书 路径
|
||||
'app_public_cert_path' => base_path().'/payment/appCertPublicKey_2021006117606688.crt',
|
||||
// 必填-支付宝公钥证书 路径
|
||||
'alipay_public_cert_path' => base_path().'/payment/alipayCertPublicKey_RSA2.crt',
|
||||
// 必填-支付宝根证书 路径
|
||||
'alipay_root_cert_path' => base_path().'/payment/alipayRootCert.crt',
|
||||
// 选填-同步回调地址
|
||||
'return_url' => 'https://test.shunlkj.cn/api/payment/alipay-return',
|
||||
// 选填-异步回调地址
|
||||
'notify_url' => 'https://test.shunlkj.cn/api/payment/alipay-notify',
|
||||
// 选填-服务商模式下的服务商 id,当 mode 为 Pay::MODE_SERVICE 时使用该参数
|
||||
'service_provider_id' => '',
|
||||
// 选填-默认为正常模式。可选为: MODE_NORMAL, MODE_SANDBOX, MODE_SERVICE
|
||||
'mode' => \Yansongda\Pay\Pay::MODE_SANDBOX,
|
||||
]
|
||||
],
|
||||
'wechat' => [
|
||||
'default' => [
|
||||
// 必填-商户号,服务商模式下为服务商商户号
|
||||
'mch_id' => '',
|
||||
// 必填-商户秘钥
|
||||
'mch_secret_key' => '',
|
||||
// 必填-商户私钥 字符串或路径
|
||||
'mch_secret_cert' => '',
|
||||
// 必填-商户公钥证书路径
|
||||
'mch_public_cert_path' => '',
|
||||
// 必填
|
||||
'notify_url' => 'https://test.shunlkj.cn/wechat/notify',
|
||||
// 选填-公众号 的 app_id
|
||||
'mp_app_id' => '2016082000291234',
|
||||
// 选填-小程序 的 app_id
|
||||
'mini_app_id' => '',
|
||||
// 选填-app 的 app_id
|
||||
'app_id' => '',
|
||||
// 选填-合单 app_id
|
||||
'combine_app_id' => '',
|
||||
// 选填-合单商户号
|
||||
'combine_mch_id' => '',
|
||||
// 选填-服务商模式下,子公众号 的 app_id
|
||||
'sub_mp_app_id' => '',
|
||||
// 选填-服务商模式下,子 app 的 app_id
|
||||
'sub_app_id' => '',
|
||||
// 选填-服务商模式下,子小程序 的 app_id
|
||||
'sub_mini_app_id' => '',
|
||||
// 选填-服务商模式下,子商户id
|
||||
'sub_mch_id' => '',
|
||||
// 选填-微信公钥证书路径, optional,强烈建议 php-fpm 模式下配置此参数
|
||||
'wechat_public_cert_path' => [
|
||||
'45F59D4DABF31918AFCEC556D5D2C6E376675D57' => __DIR__.'/Cert/wechatPublicKey.crt',
|
||||
],
|
||||
// 选填-默认为正常模式。可选为: MODE_NORMAL, MODE_SERVICE
|
||||
'mode' => \Yansongda\Pay\Pay::MODE_SANDBOX,
|
||||
]
|
||||
],
|
||||
'logger' => [
|
||||
'enable' => false,
|
||||
'file' => runtime_path().'/logs/alipay.log',
|
||||
'level' => 'debug', // 建议生产环境等级调整为 info,开发环境为 debug
|
||||
'type' => 'single', // optional, 可选 daily.
|
||||
'max_file' => 30, // optional, 当 type 为 daily 时有效,默认 30 天
|
||||
],
|
||||
'http' => [ // optional
|
||||
'timeout' => 5.0,
|
||||
'connect_timeout' => 5.0,
|
||||
// 更多配置项请参考 [Guzzle](https://guzzle-cn.readthedocs.io/zh_CN/latest/request-options.html)
|
||||
]
|
||||
];
|
||||
@@ -0,0 +1,4 @@
|
||||
<?php
|
||||
return [
|
||||
'enable' => true,
|
||||
];
|
||||
@@ -0,0 +1,4 @@
|
||||
<?php
|
||||
return [
|
||||
\Tinywan\Validate\Facade\Validate::class
|
||||
];
|
||||
Executable → Regular
+15
-9
@@ -1,10 +1,5 @@
|
||||
<?php
|
||||
// 如果ThinkORM需要使用缓存,请取消注释,或者在其他合适的地方引入
|
||||
//\think\facade\Db::setCache(new \Bilulanlv\ThinkCache\CacheManager());
|
||||
\support\think\Db::setCache(new \Bilulanlv\ThinkCache\CacheManager());
|
||||
return [
|
||||
// 开启插件
|
||||
'enable' => true,
|
||||
// 默认缓存驱动
|
||||
'default' => 'redis',
|
||||
// 缓存连接方式配置
|
||||
@@ -15,17 +10,28 @@ return [
|
||||
'type' => 'redis',
|
||||
// 服务器地址
|
||||
'host' => \support\Env::get('redis.host'),
|
||||
// 缓存端口
|
||||
'port' => \support\Env::get('redis.port'),
|
||||
// 缓存密码
|
||||
'password' => \support\Env::get('redis.password',null),
|
||||
'port' => \support\Env::get('redis.port',6379),
|
||||
// 缓存数据库
|
||||
'database' => \support\Env::get('redis.database',0),
|
||||
// 缓存前缀
|
||||
'prefix' => \support\Env::get('redis.prefix','cache:'),
|
||||
'select' => \support\Env::get('redis.database','0'),
|
||||
// 默认缓存有效期 0表示永久缓存
|
||||
'expire' => 0,
|
||||
// Thinkphp官方没有这个参数,由于生成的tag键默认不过期,如果tag键数量很大,避免长时间占用内存,可以设置一个超过其他缓存的过期时间,0为不设置
|
||||
'tag_expire' => 86400 * 7,
|
||||
'tag_expire' => 86400 * 30,
|
||||
// 缓存标签前缀
|
||||
'tag_prefix' => 'tag:',
|
||||
// 连接池配置
|
||||
'pool' => [
|
||||
'max_connections' => 5, // 最大连接数
|
||||
'min_connections' => 1, // 最小连接数
|
||||
'wait_timeout' => 3, // 从连接池获取连接等待超时时间
|
||||
'idle_timeout' => 60, // 连接最大空闲时间,超过该时间会被回收
|
||||
'heartbeat_interval' => 50, // 心跳检测间隔,需要小于60秒
|
||||
],
|
||||
],
|
||||
// 文件缓存
|
||||
'file' => [
|
||||
@@ -35,4 +41,4 @@ return [
|
||||
'path' => runtime_path() . '/file/',
|
||||
],
|
||||
],
|
||||
];
|
||||
];
|
||||
@@ -43,6 +43,7 @@ return [
|
||||
'trigger_sql' => true,
|
||||
'params' => [
|
||||
'persist' => 'mongodb_persist', // 持久化连接标识
|
||||
'authSource' => 'admin',
|
||||
],
|
||||
],
|
||||
'immongodb' => [
|
||||
|
||||
-742
File diff suppressed because one or more lines are too long
@@ -1,49 +0,0 @@
|
||||
-- 朋友圈功能数据库表结构
|
||||
|
||||
-- 朋友圈动态表
|
||||
CREATE TABLE IF NOT EXISTS `friend_circle` (
|
||||
`id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键ID',
|
||||
`user_id` int(11) NOT NULL COMMENT '用户ID',
|
||||
`body` text COMMENT '内容',
|
||||
`files` text COMMENT '图片列表(JSON)',
|
||||
`like_count` int(11) NOT NULL DEFAULT '0' COMMENT '点赞数',
|
||||
`comment_count` int(11) NOT NULL DEFAULT '0' COMMENT '评论数',
|
||||
`created_at` int(11) NOT NULL COMMENT '创建时间',
|
||||
`updated_at` int(11) NOT NULL COMMENT '更新时间',
|
||||
`status` tinyint(1) NOT NULL DEFAULT '1' COMMENT '状态(0:隐藏 1:正常)',
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `user_id` (`user_id`),
|
||||
KEY `status` (`status`),
|
||||
KEY `created_at` (`created_at`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='朋友圈动态表';
|
||||
|
||||
-- 朋友圈点赞表
|
||||
CREATE TABLE IF NOT EXISTS `friend_circle_like` (
|
||||
`id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键ID',
|
||||
`circle_id` int(11) NOT NULL COMMENT '朋友圈动态ID',
|
||||
`user_id` int(11) NOT NULL COMMENT '用户ID',
|
||||
`created_at` int(11) NOT NULL COMMENT '创建时间',
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE KEY `circle_user` (`circle_id`,`user_id`),
|
||||
KEY `circle_id` (`circle_id`),
|
||||
KEY `user_id` (`user_id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='朋友圈点赞表';
|
||||
|
||||
-- 朋友圈评论表
|
||||
CREATE TABLE IF NOT EXISTS `friend_circle_comment` (
|
||||
`id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键ID',
|
||||
`circle_id` int(11) NOT NULL COMMENT '朋友圈动态ID',
|
||||
`user_id` int(11) NOT NULL COMMENT '用户ID',
|
||||
`reply_user_id` int(11) NOT NULL DEFAULT '0' COMMENT '回复的用户ID(0表示直接评论)',
|
||||
`body` text NOT NULL COMMENT '评论内容',
|
||||
`created_at` int(11) NOT NULL COMMENT '创建时间',
|
||||
`updated_at` int(11) NOT NULL COMMENT '更新时间',
|
||||
`status` tinyint(1) NOT NULL DEFAULT '1' COMMENT '状态(0:隐藏 1:正常)',
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `circle_id` (`circle_id`),
|
||||
KEY `user_id` (`user_id`),
|
||||
KEY `reply_user_id` (`reply_user_id`),
|
||||
KEY `status` (`status`),
|
||||
KEY `created_at` (`created_at`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='朋友圈评论表';
|
||||
|
||||
@@ -0,0 +1,495 @@
|
||||
-- 数据库结构生成时间: 2026-04-09 14:03:43
|
||||
|
||||
CREATE TABLE `wa_address` (
|
||||
`id` int NOT NULL AUTO_INCREMENT,
|
||||
`user_id` int DEFAULT NULL,
|
||||
`title` varchar(100) DEFAULT NULL,
|
||||
`network` varchar(10) DEFAULT 'BEP-20',
|
||||
`address` varchar(80) NOT NULL,
|
||||
`status` tinyint(1) DEFAULT '1',
|
||||
`created_at` int NOT NULL DEFAULT '0',
|
||||
`updated_at` int DEFAULT NULL,
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `user_id` (`user_id`),
|
||||
KEY `network` (`network`),
|
||||
KEY `status` (`status`)
|
||||
) ENGINE=InnoDB AUTO_INCREMENT=770 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
|
||||
|
||||
CREATE TABLE `wa_admin` (
|
||||
`id` int unsigned NOT NULL AUTO_INCREMENT COMMENT 'ID',
|
||||
`username` varchar(32) NOT NULL COMMENT '用户名',
|
||||
`nickname` varchar(40) NOT NULL COMMENT '昵称',
|
||||
`password` varchar(255) NOT NULL COMMENT '密码',
|
||||
`avatar` varchar(255) DEFAULT '/app/admin/avatar.png' COMMENT '头像',
|
||||
`email` varchar(100) DEFAULT NULL COMMENT '邮箱',
|
||||
`mobile` varchar(16) DEFAULT NULL COMMENT '手机',
|
||||
`totp_secret` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL,
|
||||
`created_at` int DEFAULT NULL,
|
||||
`updated_at` int DEFAULT NULL,
|
||||
`login_at` int DEFAULT NULL,
|
||||
`status` tinyint DEFAULT NULL COMMENT '禁用',
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE KEY `username` (`username`)
|
||||
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='管理员表';
|
||||
|
||||
CREATE TABLE `wa_admin_access` (
|
||||
`id` int NOT NULL AUTO_INCREMENT COMMENT '主键',
|
||||
`role_id` int NOT NULL COMMENT '角色id',
|
||||
`admin_id` int NOT NULL COMMENT '管理员id',
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE KEY `role_admin_id` (`role_id`,`admin_id`)
|
||||
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='管理员角色表';
|
||||
|
||||
CREATE TABLE `wa_admin_role` (
|
||||
`id` int unsigned NOT NULL AUTO_INCREMENT COMMENT '主键',
|
||||
`name` varchar(80) NOT NULL COMMENT '角色组',
|
||||
`rules` text COMMENT '权限',
|
||||
`created_at` int DEFAULT NULL,
|
||||
`updated_at` int DEFAULT NULL,
|
||||
`pid` int unsigned DEFAULT NULL COMMENT '父级',
|
||||
PRIMARY KEY (`id`)
|
||||
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='管理员角色';
|
||||
|
||||
CREATE TABLE `wa_admin_rule` (
|
||||
`id` int unsigned NOT NULL AUTO_INCREMENT COMMENT '主键',
|
||||
`extend` varchar(255) DEFAULT NULL,
|
||||
`title` varchar(255) NOT NULL COMMENT '标题',
|
||||
`icon` varchar(255) DEFAULT NULL COMMENT '图标',
|
||||
`key` varchar(255) NOT NULL COMMENT '标识',
|
||||
`pid` int unsigned DEFAULT '0' COMMENT '上级菜单',
|
||||
`created_at` datetime DEFAULT NULL COMMENT '创建时间',
|
||||
`updated_at` datetime DEFAULT NULL COMMENT '更新时间',
|
||||
`href` varchar(255) DEFAULT NULL COMMENT 'url',
|
||||
`type` int NOT NULL DEFAULT '1' COMMENT '类型',
|
||||
`weight` int DEFAULT '0' COMMENT '排序',
|
||||
`status` tinyint(1) NOT NULL DEFAULT '1',
|
||||
PRIMARY KEY (`id`)
|
||||
) ENGINE=InnoDB AUTO_INCREMENT=293 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='权限规则';
|
||||
|
||||
CREATE TABLE `wa_album` (
|
||||
`id` int NOT NULL AUTO_INCREMENT COMMENT '主键',
|
||||
`groupID` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '群ID',
|
||||
`userID` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '用户ID',
|
||||
`title` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '标题',
|
||||
`image` int NOT NULL COMMENT '封面ID',
|
||||
`total` int NOT NULL COMMENT '相片总数',
|
||||
`status` tinyint(1) NOT NULL COMMENT '状态',
|
||||
`created_at` int NOT NULL COMMENT '创建时间',
|
||||
`updated_at` int NOT NULL COMMENT '更新时间',
|
||||
`weigh` int NOT NULL COMMENT '排序权重',
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `groupID` (`groupID`,`userID`)
|
||||
) ENGINE=InnoDB AUTO_INCREMENT=26 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
|
||||
|
||||
CREATE TABLE `wa_archives` (
|
||||
`id` int unsigned NOT NULL AUTO_INCREMENT,
|
||||
`type` varchar(10) NOT NULL DEFAULT 'article',
|
||||
`user_id` int unsigned NOT NULL DEFAULT '0' COMMENT '会员ID',
|
||||
`category_id` int DEFAULT NULL COMMENT '分类ID',
|
||||
`title` varchar(255) DEFAULT '' COMMENT '文章标题',
|
||||
`name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL COMMENT '标识',
|
||||
`flag` varchar(100) DEFAULT '' COMMENT '标志',
|
||||
`style` varchar(100) DEFAULT '' COMMENT '样式',
|
||||
`image` varchar(255) DEFAULT '' COMMENT '缩略图',
|
||||
`images` varchar(1500) DEFAULT '' COMMENT '组图',
|
||||
`tags` varchar(255) DEFAULT '' COMMENT 'TAG',
|
||||
`intro` text,
|
||||
`weigh` int NOT NULL DEFAULT '0' COMMENT '权重',
|
||||
`views` int unsigned NOT NULL DEFAULT '0' COMMENT '浏览次数',
|
||||
`created_at` int DEFAULT NULL COMMENT '创建时间',
|
||||
`updated_at` int DEFAULT NULL COMMENT '更新时间',
|
||||
`publishtime` int DEFAULT NULL COMMENT '发布时间',
|
||||
`deleted_at` int DEFAULT NULL COMMENT '删除时间',
|
||||
`memo` varchar(100) DEFAULT '' COMMENT '备注',
|
||||
`status` enum('normal','hidden','rejected','pulloff') NOT NULL DEFAULT 'normal' COMMENT '状态',
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `weigh` (`weigh`,`publishtime`)
|
||||
) ENGINE=InnoDB AUTO_INCREMENT=618 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='内容表';
|
||||
|
||||
CREATE TABLE `wa_archives_read` (
|
||||
`user_id` int NOT NULL,
|
||||
`source_id` int DEFAULT NULL,
|
||||
`value` tinyint(1) DEFAULT '1',
|
||||
KEY `user_id` (`user_id`) USING BTREE,
|
||||
KEY `source_id` (`source_id`) USING BTREE,
|
||||
KEY `value` (`value`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
|
||||
|
||||
CREATE TABLE `wa_card` (
|
||||
`id` int NOT NULL AUTO_INCREMENT,
|
||||
`type` int DEFAULT NULL,
|
||||
`title` varchar(64) NOT NULL,
|
||||
`total` int NOT NULL DEFAULT '0',
|
||||
`used` int DEFAULT '0',
|
||||
`expires` int NOT NULL,
|
||||
`days` int NOT NULL DEFAULT '0',
|
||||
`status` tinyint(1) NOT NULL DEFAULT '1',
|
||||
`created_at` int NOT NULL,
|
||||
`updated_at` int DEFAULT NULL,
|
||||
PRIMARY KEY (`id`)
|
||||
) ENGINE=InnoDB AUTO_INCREMENT=12 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
|
||||
|
||||
CREATE TABLE `wa_category` (
|
||||
`id` int NOT NULL AUTO_INCREMENT,
|
||||
`title` varchar(64) NOT NULL,
|
||||
`type` varchar(10) DEFAULT NULL,
|
||||
`status` tinyint(1) NOT NULL DEFAULT '1',
|
||||
`created_at` int DEFAULT NULL,
|
||||
`updated_at` int DEFAULT NULL,
|
||||
PRIMARY KEY (`id`)
|
||||
) ENGINE=InnoDB AUTO_INCREMENT=11 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
|
||||
|
||||
CREATE TABLE `wa_cdkey` (
|
||||
`id` int NOT NULL AUTO_INCREMENT,
|
||||
`type` int DEFAULT NULL,
|
||||
`category_id` int DEFAULT NULL,
|
||||
`account` varchar(64) NOT NULL,
|
||||
`passworrd` varchar(64) DEFAULT NULL,
|
||||
`days` int DEFAULT '0',
|
||||
`expires` int NOT NULL,
|
||||
`is_used` tinyint(1) NOT NULL DEFAULT '0',
|
||||
`record_id` int DEFAULT NULL,
|
||||
`use_time` int DEFAULT NULL,
|
||||
`status` tinyint(1) NOT NULL DEFAULT '1',
|
||||
`created_at` int NOT NULL,
|
||||
`updated_at` int DEFAULT NULL,
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE KEY `account` (`account`)
|
||||
) ENGINE=InnoDB AUTO_INCREMENT=11314 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
|
||||
|
||||
CREATE TABLE `wa_collection` (
|
||||
`id` int NOT NULL AUTO_INCREMENT,
|
||||
`user_id` int NOT NULL DEFAULT '0',
|
||||
`content_type` enum('text','image','file','video','link','audio') CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT 'text' COMMENT '内容类型',
|
||||
`content` json NOT NULL COMMENT '收藏内容本体',
|
||||
`tags` varchar(255) DEFAULT NULL COMMENT '用户自定义标签',
|
||||
`is_pinned` tinyint(1) DEFAULT '0' COMMENT '是否置顶',
|
||||
`status` tinyint(1) NOT NULL DEFAULT '1',
|
||||
`created_at` int DEFAULT NULL,
|
||||
`updated_at` int DEFAULT NULL,
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `user_id` (`user_id`)
|
||||
) ENGINE=InnoDB AUTO_INCREMENT=23 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
|
||||
|
||||
CREATE TABLE `wa_config` (
|
||||
`id` int unsigned NOT NULL AUTO_INCREMENT,
|
||||
`name` varchar(30) DEFAULT '' COMMENT '变量名',
|
||||
`group` varchar(30) DEFAULT '' COMMENT '分组',
|
||||
`title` varchar(100) DEFAULT '' COMMENT '变量标题',
|
||||
`tip` varchar(100) DEFAULT '' COMMENT '变量描述',
|
||||
`type` varchar(30) DEFAULT '' COMMENT '类型:string,text,int,bool,array,datetime,date,file',
|
||||
`visible` varchar(255) DEFAULT '' COMMENT '可见条件',
|
||||
`is_show` tinyint NOT NULL DEFAULT '1',
|
||||
`value` text COMMENT '变量值',
|
||||
`content` text COMMENT '变量字典数据',
|
||||
`rule` varchar(100) DEFAULT '' COMMENT '验证规则',
|
||||
`extend` varchar(255) DEFAULT '' COMMENT '扩展属性',
|
||||
`setting` varchar(255) DEFAULT '' COMMENT '配置',
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE KEY `name` (`name`)
|
||||
) ENGINE=InnoDB AUTO_INCREMENT=55 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='系统配置';
|
||||
|
||||
CREATE TABLE `wa_content` (
|
||||
`id` int NOT NULL,
|
||||
`content` longtext NOT NULL,
|
||||
`content1` longtext,
|
||||
`content2` longtext,
|
||||
PRIMARY KEY (`id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='内容';
|
||||
|
||||
CREATE TABLE `wa_files` (
|
||||
`id` int NOT NULL AUTO_INCREMENT,
|
||||
`adapter` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL,
|
||||
`category` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL,
|
||||
`user_id` int DEFAULT NULL COMMENT '用户ID',
|
||||
`admin_id` int DEFAULT NULL,
|
||||
`origin_name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '本地文件名',
|
||||
`file_name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '保存路径',
|
||||
`file_url` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL,
|
||||
`size` int DEFAULT NULL COMMENT '大小',
|
||||
`mime_type` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT 'mime类型',
|
||||
`extension` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '扩展名',
|
||||
`file_height` int DEFAULT NULL COMMENT '图片高度',
|
||||
`file_width` int DEFAULT NULL COMMENT 'tup宽度',
|
||||
`sha1` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT 'sha1确定文件唯一性',
|
||||
`use_count` int DEFAULT NULL COMMENT '关联次数',
|
||||
`created_at` int DEFAULT NULL COMMENT '创建时间',
|
||||
`updated_at` int DEFAULT NULL COMMENT '更新时间',
|
||||
`deleted_at` int DEFAULT NULL COMMENT '删除时间',
|
||||
PRIMARY KEY (`id`)
|
||||
) ENGINE=InnoDB AUTO_INCREMENT=2272 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
|
||||
|
||||
CREATE TABLE `wa_friend_circle` (
|
||||
`id` int NOT NULL AUTO_INCREMENT COMMENT '主键ID',
|
||||
`user_id` int NOT NULL COMMENT '用户ID',
|
||||
`releaseType` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL,
|
||||
`body` text CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci COMMENT '内容',
|
||||
`files` text CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci COMMENT '图片列表(JSON)',
|
||||
`address` text CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci,
|
||||
`like_count` int NOT NULL DEFAULT '0' COMMENT '点赞数',
|
||||
`comment_count` int NOT NULL DEFAULT '0' COMMENT '评论数',
|
||||
`created_at` int NOT NULL COMMENT '创建时间',
|
||||
`updated_at` int NOT NULL COMMENT '更新时间',
|
||||
`status` tinyint(1) NOT NULL DEFAULT '1' COMMENT '状态(0:隐藏 1:正常)',
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `user_id` (`user_id`),
|
||||
KEY `status` (`status`),
|
||||
KEY `created_at` (`created_at`)
|
||||
) ENGINE=InnoDB AUTO_INCREMENT=375 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='朋友圈动态表';
|
||||
|
||||
CREATE TABLE `wa_friend_circle_comment` (
|
||||
`id` int NOT NULL AUTO_INCREMENT COMMENT '主键ID',
|
||||
`circle_id` int NOT NULL COMMENT '朋友圈动态ID',
|
||||
`user_id` int NOT NULL COMMENT '用户ID',
|
||||
`reply_user_id` int NOT NULL DEFAULT '0' COMMENT '回复的用户ID(0表示直接评论)',
|
||||
`body` text CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '评论内容',
|
||||
`created_at` int NOT NULL COMMENT '创建时间',
|
||||
`updated_at` int NOT NULL COMMENT '更新时间',
|
||||
`status` tinyint(1) NOT NULL DEFAULT '1' COMMENT '状态(0:隐藏 1:正常)',
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `circle_id` (`circle_id`),
|
||||
KEY `user_id` (`user_id`),
|
||||
KEY `reply_user_id` (`reply_user_id`),
|
||||
KEY `status` (`status`),
|
||||
KEY `created_at` (`created_at`)
|
||||
) ENGINE=InnoDB AUTO_INCREMENT=39 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='朋友圈评论表';
|
||||
|
||||
CREATE TABLE `wa_friend_circle_like` (
|
||||
`id` int NOT NULL AUTO_INCREMENT COMMENT '主键ID',
|
||||
`circle_id` int NOT NULL COMMENT '朋友圈动态ID',
|
||||
`user_id` int NOT NULL COMMENT '用户ID',
|
||||
`created_at` int NOT NULL COMMENT '创建时间',
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE KEY `circle_user` (`circle_id`,`user_id`),
|
||||
KEY `circle_id` (`circle_id`),
|
||||
KEY `user_id` (`user_id`)
|
||||
) ENGINE=InnoDB AUTO_INCREMENT=156 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='朋友圈点赞表';
|
||||
|
||||
CREATE TABLE `wa_friend_circle_setting` (
|
||||
`id` int NOT NULL AUTO_INCREMENT COMMENT '主键ID',
|
||||
`user_id` int NOT NULL COMMENT '用户ID',
|
||||
`bg` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '背景',
|
||||
`allow_days` int NOT NULL COMMENT '允许查看最近几天的朋友圈',
|
||||
`created_at` int NOT NULL COMMENT '创建时间',
|
||||
PRIMARY KEY (`id`)
|
||||
) ENGINE=InnoDB AUTO_INCREMENT=79 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='朋友圈设置';
|
||||
|
||||
CREATE TABLE `wa_gallery` (
|
||||
`id` int NOT NULL AUTO_INCREMENT,
|
||||
`album_id` int DEFAULT NULL COMMENT '相册ID',
|
||||
`group_id` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL,
|
||||
`user_id` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL,
|
||||
`url` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL,
|
||||
`title` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL,
|
||||
`status` tinyint(1) NOT NULL DEFAULT '1',
|
||||
`created_at` int NOT NULL,
|
||||
`updated_at` int NOT NULL,
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `album_id` (`album_id`),
|
||||
KEY `group_id` (`group_id`),
|
||||
KEY `user_id` (`user_id`)
|
||||
) ENGINE=InnoDB AUTO_INCREMENT=379 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
|
||||
|
||||
CREATE TABLE `wa_payment_order` (
|
||||
`id` int unsigned NOT NULL AUTO_INCREMENT,
|
||||
`order_no` varchar(50) NOT NULL COMMENT '订单号',
|
||||
`trade_no` varchar(100) DEFAULT NULL COMMENT '支付交易号',
|
||||
`pay_type` varchar(20) NOT NULL COMMENT '支付类型: alipay, wechat',
|
||||
`type` varchar(20) NOT NULL DEFAULT 'goods' COMMENT '订单类型: recharge(充值), goods(商品), service(服务), other(其他)',
|
||||
`amount` decimal(10,2) NOT NULL COMMENT '支付金额',
|
||||
`subject` varchar(255) NOT NULL COMMENT '订单标题',
|
||||
`status` varchar(20) NOT NULL DEFAULT 'PENDING' COMMENT '订单状态: PENDING, SUCCESS, FAIL, CLOSED, REFUNDED',
|
||||
`extra` text COMMENT '额外信息',
|
||||
`created_at` int NOT NULL COMMENT '创建时间',
|
||||
`updated_at` int NOT NULL COMMENT '更新时间',
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE KEY `order_no` (`order_no`),
|
||||
KEY `pay_type` (`pay_type`),
|
||||
KEY `type` (`type`),
|
||||
KEY `status` (`status`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='支付订单表';
|
||||
|
||||
CREATE TABLE `wa_payment_refund` (
|
||||
`id` int unsigned NOT NULL AUTO_INCREMENT,
|
||||
`order_no` varchar(50) NOT NULL COMMENT '原订单号',
|
||||
`refund_no` varchar(50) NOT NULL COMMENT '退款单号',
|
||||
`pay_type` varchar(20) NOT NULL COMMENT '支付类型: alipay, wechat',
|
||||
`amount` decimal(10,2) NOT NULL COMMENT '退款金额',
|
||||
`reason` varchar(255) NOT NULL COMMENT '退款原因',
|
||||
`status` varchar(20) NOT NULL DEFAULT 'SUCCESS' COMMENT '退款状态: SUCCESS, FAIL',
|
||||
`extra` text COMMENT '额外信息',
|
||||
`created_at` int NOT NULL COMMENT '创建时间',
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE KEY `refund_no` (`refund_no`),
|
||||
KEY `order_no` (`order_no`),
|
||||
KEY `pay_type` (`pay_type`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='退款表';
|
||||
|
||||
CREATE TABLE `wa_recharge` (
|
||||
`id` int NOT NULL AUTO_INCREMENT,
|
||||
`user_id` int NOT NULL,
|
||||
`amount` int NOT NULL,
|
||||
`created_at` int NOT NULL,
|
||||
PRIMARY KEY (`id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
|
||||
|
||||
CREATE TABLE `wa_team` (
|
||||
`id` int NOT NULL AUTO_INCREMENT,
|
||||
`user_id` int DEFAULT NULL,
|
||||
`parent_user_id` int DEFAULT NULL,
|
||||
`members` text NOT NULL,
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `user_id` (`user_id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
|
||||
|
||||
CREATE TABLE `wa_thali` (
|
||||
`id` int NOT NULL AUTO_INCREMENT COMMENT 'ID',
|
||||
`role_id` int DEFAULT NULL COMMENT '关联角色',
|
||||
`title` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '名称',
|
||||
`price` decimal(10,2) DEFAULT NULL COMMENT '价格',
|
||||
`month_discount` decimal(6,4) DEFAULT '1.0000' COMMENT '月折扣',
|
||||
`quarter_discount` decimal(6,4) DEFAULT '1.0000' COMMENT '季折扣',
|
||||
`year_discount` decimal(6,4) NOT NULL DEFAULT '1.0000' COMMENT '年折扣',
|
||||
`label` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '标签',
|
||||
`created_at` int DEFAULT NULL COMMENT '创建时间',
|
||||
`updated_at` int DEFAULT NULL COMMENT '更新时间',
|
||||
`status` tinyint DEFAULT NULL COMMENT '状态',
|
||||
PRIMARY KEY (`id`)
|
||||
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
|
||||
|
||||
CREATE TABLE `wa_user` (
|
||||
`id` mediumint NOT NULL AUTO_INCREMENT COMMENT '主键',
|
||||
`userID` varchar(16) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL,
|
||||
`role_id` int DEFAULT NULL,
|
||||
`parent_id` int DEFAULT NULL,
|
||||
`group_id` tinyint NOT NULL DEFAULT '0' COMMENT '用戶分組',
|
||||
`username` varchar(32) NOT NULL COMMENT '用户名',
|
||||
`nickname` varchar(40) NOT NULL COMMENT '昵称',
|
||||
`password` varchar(255) NOT NULL COMMENT '密码',
|
||||
`trade_password` varchar(64) DEFAULT NULL,
|
||||
`empty_password` varchar(255) DEFAULT NULL COMMENT '清空密码',
|
||||
`sex` enum('0','1','2') CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL DEFAULT '0' COMMENT '性别',
|
||||
`avatar` varchar(255) DEFAULT NULL COMMENT '头像',
|
||||
`email` varchar(128) DEFAULT NULL COMMENT '邮箱',
|
||||
`region` varchar(6) DEFAULT NULL COMMENT '国家',
|
||||
`mobile` varchar(16) DEFAULT NULL COMMENT '手机',
|
||||
`level` tinyint DEFAULT '0' COMMENT '等级',
|
||||
`birthday` date DEFAULT NULL COMMENT '生日',
|
||||
`bio` varchar(255) DEFAULT NULL,
|
||||
`money` decimal(20,10) DEFAULT '0.0000000000' COMMENT '余额(元)',
|
||||
`score` int DEFAULT '0' COMMENT '积分',
|
||||
`currency1` decimal(20,10) NOT NULL DEFAULT '0.0000000000',
|
||||
`currency2` decimal(20,10) NOT NULL DEFAULT '0.0000000000',
|
||||
`currency3` decimal(20,10) NOT NULL DEFAULT '0.0000000000',
|
||||
`currency4` decimal(20,10) NOT NULL DEFAULT '0.0000000000',
|
||||
`currency5` decimal(20,10) NOT NULL DEFAULT '0.0000000000',
|
||||
`currency6` decimal(20,10) NOT NULL DEFAULT '0.0000000000',
|
||||
`currency7` decimal(20,10) NOT NULL DEFAULT '0.0000000000',
|
||||
`currency8` decimal(20,10) NOT NULL DEFAULT '0.0000000000',
|
||||
`currency9` decimal(20,10) NOT NULL DEFAULT '0.0000000000',
|
||||
`maxsuccessions` tinyint NOT NULL DEFAULT '0',
|
||||
`successions` tinyint NOT NULL DEFAULT '0',
|
||||
`loginfailure` tinyint NOT NULL DEFAULT '0',
|
||||
`prev_time` int DEFAULT NULL,
|
||||
`last_time` int DEFAULT NULL COMMENT '登录时间',
|
||||
`last_ip` varchar(50) DEFAULT NULL COMMENT '登录ip',
|
||||
`join_time` int DEFAULT NULL COMMENT '注册时间',
|
||||
`join_ip` varchar(50) DEFAULT NULL COMMENT '注册ip',
|
||||
`token` varchar(50) DEFAULT NULL COMMENT 'token',
|
||||
`invite_code` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL,
|
||||
`expire_at` int DEFAULT NULL COMMENT '过期时间',
|
||||
`status` tinyint DEFAULT '0' COMMENT '禁用',
|
||||
`created_at` int DEFAULT NULL,
|
||||
`updated_at` int DEFAULT NULL,
|
||||
`deleted_at` int DEFAULT NULL,
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE KEY `username` (`username`),
|
||||
UNIQUE KEY `userID` (`userID`),
|
||||
KEY `join_time` (`join_time`),
|
||||
KEY `mobile` (`mobile`),
|
||||
KEY `email` (`email`),
|
||||
KEY `region` (`region`)
|
||||
) ENGINE=InnoDB AUTO_INCREMENT=105244 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='用户表';
|
||||
|
||||
CREATE TABLE `wa_user_extend` (
|
||||
`user_id` int NOT NULL,
|
||||
`team_total` int DEFAULT '0' COMMENT '团队总人数',
|
||||
`direct_total` int DEFAULT '0' COMMENT '直属团队人数',
|
||||
`vip_total` int NOT NULL DEFAULT '0' COMMENT '旗下VIP总数',
|
||||
`consume` int DEFAULT '0',
|
||||
`sales` int DEFAULT '0',
|
||||
`profile_banner` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '个人信息背景',
|
||||
`moments_banner` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '朋友圈背景',
|
||||
`moments_allow_view_days` tinyint NOT NULL DEFAULT '0' COMMENT '允许查看朋友圈的天数,0不限制,-1:不允许查看,>0,具体的天数',
|
||||
PRIMARY KEY (`user_id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
|
||||
|
||||
CREATE TABLE `wa_user_role` (
|
||||
`id` int unsigned NOT NULL AUTO_INCREMENT COMMENT '主键',
|
||||
`pid` int unsigned DEFAULT NULL COMMENT '父级',
|
||||
`name` varchar(80) NOT NULL COMMENT '角色组',
|
||||
`rules` text COMMENT '权限',
|
||||
`right` json DEFAULT NULL COMMENT '权益',
|
||||
`max_send_msg_count` int DEFAULT '0' COMMENT '最大消息数量',
|
||||
`max_friend_count` int DEFAULT '0' COMMENT '最大好友数量',
|
||||
`max_group_join_count` int DEFAULT '0' COMMENT '最大加入的群组数量',
|
||||
`max_gourp_create_count` int DEFAULT NULL COMMENT '最大创建的群组数量',
|
||||
`created_at` int DEFAULT NULL COMMENT '创建时间',
|
||||
`updated_at` int DEFAULT NULL COMMENT '更新时间',
|
||||
`status` tinyint DEFAULT NULL COMMENT '状态',
|
||||
PRIMARY KEY (`id`)
|
||||
) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='管理员角色';
|
||||
|
||||
CREATE TABLE `wa_user_rule` (
|
||||
`id` int unsigned NOT NULL AUTO_INCREMENT COMMENT '主键',
|
||||
`title` varchar(255) NOT NULL COMMENT '标题',
|
||||
`icon` varchar(255) DEFAULT NULL COMMENT '图标',
|
||||
`key` varchar(255) NOT NULL COMMENT '标识',
|
||||
`pid` int unsigned DEFAULT '0' COMMENT '上级菜单',
|
||||
`created_at` int DEFAULT NULL,
|
||||
`updated_at` int DEFAULT NULL,
|
||||
`href` varchar(255) DEFAULT NULL COMMENT 'url',
|
||||
`type` int NOT NULL DEFAULT '1' COMMENT '类型',
|
||||
`weight` int DEFAULT '0' COMMENT '排序',
|
||||
`status` tinyint(1) NOT NULL DEFAULT '1',
|
||||
PRIMARY KEY (`id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='权限规则';
|
||||
|
||||
CREATE TABLE `wa_user_team` (
|
||||
`descendant_id` int NOT NULL,
|
||||
`ancestor_id` int NOT NULL,
|
||||
`depth` int DEFAULT NULL,
|
||||
`status` tinyint DEFAULT '0'
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
|
||||
|
||||
CREATE TABLE `wa_version` (
|
||||
`id` int NOT NULL AUTO_INCREMENT,
|
||||
`type` tinyint DEFAULT '2',
|
||||
`platform` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL,
|
||||
`version` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL,
|
||||
`version_wgt` int DEFAULT NULL,
|
||||
`content` text CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci,
|
||||
`force` tinyint DEFAULT NULL,
|
||||
`source` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL,
|
||||
`status` tinyint DEFAULT '1',
|
||||
PRIMARY KEY (`id`)
|
||||
) ENGINE=InnoDB AUTO_INCREMENT=42 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
|
||||
|
||||
CREATE TABLE `wa_withdrawl` (
|
||||
`id` int NOT NULL AUTO_INCREMENT,
|
||||
`user_id` int NOT NULL DEFAULT '0',
|
||||
`deduction_amount` decimal(20,10) DEFAULT '0.0000000000',
|
||||
`recive_amount` decimal(20,10) NOT NULL DEFAULT '0.0000000000',
|
||||
`fee` decimal(20,10) NOT NULL DEFAULT '0.0000000000',
|
||||
`created_at` int DEFAULT NULL,
|
||||
`updated_at` int DEFAULT NULL,
|
||||
`network` varchar(10) DEFAULT NULL,
|
||||
`address` varchar(80) DEFAULT NULL,
|
||||
`status` tinyint(1) NOT NULL DEFAULT '0',
|
||||
`memo` varchar(200) DEFAULT NULL,
|
||||
`transfer_at` int DEFAULT NULL,
|
||||
`txid` varchar(100) DEFAULT NULL,
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `user_id` (`user_id`)
|
||||
) ENGINE=InnoDB AUTO_INCREMENT=1120 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
|
||||
|
||||
@@ -0,0 +1,204 @@
|
||||
-- 数据库索引优化
|
||||
-- 生成时间: 2026-04-09 14:09:58
|
||||
|
||||
CREATE INDEX idx_created_at ON `wa_address` (`created_at`);
|
||||
CREATE INDEX idx_updated_at ON `wa_address` (`updated_at`);
|
||||
CREATE INDEX idx_created_at ON `wa_address` (`created_at`);
|
||||
CREATE INDEX idx_updated_at ON `wa_address` (`updated_at`);
|
||||
CREATE INDEX idx_user_id_created_at ON `wa_address` (`user_id`, `created_at`);
|
||||
CREATE INDEX idx_status_created_at ON `wa_address` (`status`, `created_at`);
|
||||
CREATE INDEX idx_status ON `wa_admin` (`status`);
|
||||
CREATE INDEX idx_created_at ON `wa_admin` (`created_at`);
|
||||
CREATE INDEX idx_updated_at ON `wa_admin` (`updated_at`);
|
||||
CREATE INDEX idx_email ON `wa_admin` (`email`);
|
||||
CREATE INDEX idx_created_at ON `wa_admin` (`created_at`);
|
||||
CREATE INDEX idx_updated_at ON `wa_admin` (`updated_at`);
|
||||
CREATE INDEX idx_status ON `wa_admin` (`status`);
|
||||
CREATE INDEX idx_email ON `wa_admin` (`email`);
|
||||
CREATE INDEX idx_status_created_at ON `wa_admin` (`status`, `created_at`);
|
||||
CREATE INDEX idx_role_id ON `wa_admin_access` (`role_id`);
|
||||
CREATE INDEX idx_admin_id ON `wa_admin_access` (`admin_id`);
|
||||
CREATE INDEX idx_admin_id ON `wa_admin_access` (`admin_id`);
|
||||
CREATE INDEX idx_created_at ON `wa_admin_role` (`created_at`);
|
||||
CREATE INDEX idx_updated_at ON `wa_admin_role` (`updated_at`);
|
||||
CREATE INDEX idx_created_at ON `wa_admin_role` (`created_at`);
|
||||
CREATE INDEX idx_updated_at ON `wa_admin_role` (`updated_at`);
|
||||
CREATE INDEX idx_status ON `wa_admin_rule` (`status`);
|
||||
CREATE INDEX idx_type ON `wa_admin_rule` (`type`);
|
||||
CREATE INDEX idx_created_at ON `wa_admin_rule` (`created_at`);
|
||||
CREATE INDEX idx_updated_at ON `wa_admin_rule` (`updated_at`);
|
||||
CREATE INDEX idx_created_at ON `wa_admin_rule` (`created_at`);
|
||||
CREATE INDEX idx_updated_at ON `wa_admin_rule` (`updated_at`);
|
||||
CREATE INDEX idx_status ON `wa_admin_rule` (`status`);
|
||||
CREATE INDEX idx_type ON `wa_admin_rule` (`type`);
|
||||
CREATE INDEX idx_status_created_at ON `wa_admin_rule` (`status`, `created_at`);
|
||||
CREATE INDEX idx_type_created_at ON `wa_admin_rule` (`type`, `created_at`);
|
||||
CREATE INDEX idx_status ON `wa_album` (`status`);
|
||||
CREATE INDEX idx_created_at ON `wa_album` (`created_at`);
|
||||
CREATE INDEX idx_updated_at ON `wa_album` (`updated_at`);
|
||||
CREATE INDEX idx_created_at ON `wa_album` (`created_at`);
|
||||
CREATE INDEX idx_updated_at ON `wa_album` (`updated_at`);
|
||||
CREATE INDEX idx_status ON `wa_album` (`status`);
|
||||
CREATE INDEX idx_status_created_at ON `wa_album` (`status`, `created_at`);
|
||||
CREATE INDEX idx_user_id ON `wa_archives` (`user_id`);
|
||||
CREATE INDEX idx_category_id ON `wa_archives` (`category_id`);
|
||||
CREATE INDEX idx_user_id ON `wa_archives` (`user_id`);
|
||||
CREATE INDEX idx_status ON `wa_archives` (`status`);
|
||||
CREATE INDEX idx_type ON `wa_archives` (`type`);
|
||||
CREATE INDEX idx_created_at ON `wa_archives` (`created_at`);
|
||||
CREATE INDEX idx_updated_at ON `wa_archives` (`updated_at`);
|
||||
CREATE INDEX idx_created_at ON `wa_archives` (`created_at`);
|
||||
CREATE INDEX idx_updated_at ON `wa_archives` (`updated_at`);
|
||||
CREATE INDEX idx_status ON `wa_archives` (`status`);
|
||||
CREATE INDEX idx_type ON `wa_archives` (`type`);
|
||||
CREATE INDEX idx_user_id_created_at ON `wa_archives` (`user_id`, `created_at`);
|
||||
CREATE INDEX idx_status_created_at ON `wa_archives` (`status`, `created_at`);
|
||||
CREATE INDEX idx_type_created_at ON `wa_archives` (`type`, `created_at`);
|
||||
CREATE INDEX idx_status ON `wa_card` (`status`);
|
||||
CREATE INDEX idx_type ON `wa_card` (`type`);
|
||||
CREATE INDEX idx_created_at ON `wa_card` (`created_at`);
|
||||
CREATE INDEX idx_updated_at ON `wa_card` (`updated_at`);
|
||||
CREATE INDEX idx_created_at ON `wa_card` (`created_at`);
|
||||
CREATE INDEX idx_updated_at ON `wa_card` (`updated_at`);
|
||||
CREATE INDEX idx_status ON `wa_card` (`status`);
|
||||
CREATE INDEX idx_type ON `wa_card` (`type`);
|
||||
CREATE INDEX idx_status_created_at ON `wa_card` (`status`, `created_at`);
|
||||
CREATE INDEX idx_type_created_at ON `wa_card` (`type`, `created_at`);
|
||||
CREATE INDEX idx_status ON `wa_category` (`status`);
|
||||
CREATE INDEX idx_type ON `wa_category` (`type`);
|
||||
CREATE INDEX idx_created_at ON `wa_category` (`created_at`);
|
||||
CREATE INDEX idx_updated_at ON `wa_category` (`updated_at`);
|
||||
CREATE INDEX idx_created_at ON `wa_category` (`created_at`);
|
||||
CREATE INDEX idx_updated_at ON `wa_category` (`updated_at`);
|
||||
CREATE INDEX idx_status ON `wa_category` (`status`);
|
||||
CREATE INDEX idx_type ON `wa_category` (`type`);
|
||||
CREATE INDEX idx_status_created_at ON `wa_category` (`status`, `created_at`);
|
||||
CREATE INDEX idx_type_created_at ON `wa_category` (`type`, `created_at`);
|
||||
CREATE INDEX idx_category_id ON `wa_cdkey` (`category_id`);
|
||||
CREATE INDEX idx_record_id ON `wa_cdkey` (`record_id`);
|
||||
CREATE INDEX idx_status ON `wa_cdkey` (`status`);
|
||||
CREATE INDEX idx_type ON `wa_cdkey` (`type`);
|
||||
CREATE INDEX idx_created_at ON `wa_cdkey` (`created_at`);
|
||||
CREATE INDEX idx_updated_at ON `wa_cdkey` (`updated_at`);
|
||||
CREATE INDEX idx_created_at ON `wa_cdkey` (`created_at`);
|
||||
CREATE INDEX idx_updated_at ON `wa_cdkey` (`updated_at`);
|
||||
CREATE INDEX idx_status ON `wa_cdkey` (`status`);
|
||||
CREATE INDEX idx_type ON `wa_cdkey` (`type`);
|
||||
CREATE INDEX idx_status_created_at ON `wa_cdkey` (`status`, `created_at`);
|
||||
CREATE INDEX idx_type_created_at ON `wa_cdkey` (`type`, `created_at`);
|
||||
CREATE INDEX idx_status ON `wa_collection` (`status`);
|
||||
CREATE INDEX idx_created_at ON `wa_collection` (`created_at`);
|
||||
CREATE INDEX idx_updated_at ON `wa_collection` (`updated_at`);
|
||||
CREATE INDEX idx_created_at ON `wa_collection` (`created_at`);
|
||||
CREATE INDEX idx_updated_at ON `wa_collection` (`updated_at`);
|
||||
CREATE INDEX idx_status ON `wa_collection` (`status`);
|
||||
CREATE INDEX idx_user_id_created_at ON `wa_collection` (`user_id`, `created_at`);
|
||||
CREATE INDEX idx_status_created_at ON `wa_collection` (`status`, `created_at`);
|
||||
CREATE INDEX idx_type ON `wa_config` (`type`);
|
||||
CREATE INDEX idx_type ON `wa_config` (`type`);
|
||||
CREATE INDEX idx_user_id ON `wa_files` (`user_id`);
|
||||
CREATE INDEX idx_admin_id ON `wa_files` (`admin_id`);
|
||||
CREATE INDEX idx_user_id ON `wa_files` (`user_id`);
|
||||
CREATE INDEX idx_admin_id ON `wa_files` (`admin_id`);
|
||||
CREATE INDEX idx_created_at ON `wa_files` (`created_at`);
|
||||
CREATE INDEX idx_updated_at ON `wa_files` (`updated_at`);
|
||||
CREATE INDEX idx_created_at ON `wa_files` (`created_at`);
|
||||
CREATE INDEX idx_updated_at ON `wa_files` (`updated_at`);
|
||||
CREATE INDEX idx_user_id_created_at ON `wa_files` (`user_id`, `created_at`);
|
||||
CREATE INDEX idx_admin_id_created_at ON `wa_files` (`admin_id`, `created_at`);
|
||||
CREATE INDEX idx_updated_at ON `wa_friend_circle` (`updated_at`);
|
||||
CREATE INDEX idx_updated_at ON `wa_friend_circle` (`updated_at`);
|
||||
CREATE INDEX idx_user_id_created_at ON `wa_friend_circle` (`user_id`, `created_at`);
|
||||
CREATE INDEX idx_status_created_at ON `wa_friend_circle` (`status`, `created_at`);
|
||||
CREATE INDEX idx_updated_at ON `wa_friend_circle_comment` (`updated_at`);
|
||||
CREATE INDEX idx_updated_at ON `wa_friend_circle_comment` (`updated_at`);
|
||||
CREATE INDEX idx_user_id_created_at ON `wa_friend_circle_comment` (`user_id`, `created_at`);
|
||||
CREATE INDEX idx_status_created_at ON `wa_friend_circle_comment` (`status`, `created_at`);
|
||||
CREATE INDEX idx_created_at ON `wa_friend_circle_like` (`created_at`);
|
||||
CREATE INDEX idx_created_at ON `wa_friend_circle_like` (`created_at`);
|
||||
CREATE INDEX idx_user_id_created_at ON `wa_friend_circle_like` (`user_id`, `created_at`);
|
||||
CREATE INDEX idx_user_id ON `wa_friend_circle_setting` (`user_id`);
|
||||
CREATE INDEX idx_user_id ON `wa_friend_circle_setting` (`user_id`);
|
||||
CREATE INDEX idx_created_at ON `wa_friend_circle_setting` (`created_at`);
|
||||
CREATE INDEX idx_created_at ON `wa_friend_circle_setting` (`created_at`);
|
||||
CREATE INDEX idx_user_id_created_at ON `wa_friend_circle_setting` (`user_id`, `created_at`);
|
||||
CREATE INDEX idx_status ON `wa_gallery` (`status`);
|
||||
CREATE INDEX idx_created_at ON `wa_gallery` (`created_at`);
|
||||
CREATE INDEX idx_updated_at ON `wa_gallery` (`updated_at`);
|
||||
CREATE INDEX idx_created_at ON `wa_gallery` (`created_at`);
|
||||
CREATE INDEX idx_updated_at ON `wa_gallery` (`updated_at`);
|
||||
CREATE INDEX idx_status ON `wa_gallery` (`status`);
|
||||
CREATE INDEX idx_user_id_created_at ON `wa_gallery` (`user_id`, `created_at`);
|
||||
CREATE INDEX idx_status_created_at ON `wa_gallery` (`status`, `created_at`);
|
||||
CREATE INDEX idx_created_at ON `wa_payment_order` (`created_at`);
|
||||
CREATE INDEX idx_updated_at ON `wa_payment_order` (`updated_at`);
|
||||
CREATE INDEX idx_trade_no ON `wa_payment_order` (`trade_no`);
|
||||
CREATE INDEX idx_created_at ON `wa_payment_order` (`created_at`);
|
||||
CREATE INDEX idx_updated_at ON `wa_payment_order` (`updated_at`);
|
||||
CREATE INDEX idx_trade_no ON `wa_payment_order` (`trade_no`);
|
||||
CREATE INDEX idx_status_created_at ON `wa_payment_order` (`status`, `created_at`);
|
||||
CREATE INDEX idx_type_created_at ON `wa_payment_order` (`type`, `created_at`);
|
||||
CREATE INDEX idx_status ON `wa_payment_refund` (`status`);
|
||||
CREATE INDEX idx_created_at ON `wa_payment_refund` (`created_at`);
|
||||
CREATE INDEX idx_created_at ON `wa_payment_refund` (`created_at`);
|
||||
CREATE INDEX idx_status ON `wa_payment_refund` (`status`);
|
||||
CREATE INDEX idx_status_created_at ON `wa_payment_refund` (`status`, `created_at`);
|
||||
CREATE INDEX idx_user_id ON `wa_recharge` (`user_id`);
|
||||
CREATE INDEX idx_user_id ON `wa_recharge` (`user_id`);
|
||||
CREATE INDEX idx_created_at ON `wa_recharge` (`created_at`);
|
||||
CREATE INDEX idx_created_at ON `wa_recharge` (`created_at`);
|
||||
CREATE INDEX idx_user_id_created_at ON `wa_recharge` (`user_id`, `created_at`);
|
||||
CREATE INDEX idx_parent_user_id ON `wa_team` (`parent_user_id`);
|
||||
CREATE INDEX idx_role_id ON `wa_thali` (`role_id`);
|
||||
CREATE INDEX idx_status ON `wa_thali` (`status`);
|
||||
CREATE INDEX idx_created_at ON `wa_thali` (`created_at`);
|
||||
CREATE INDEX idx_updated_at ON `wa_thali` (`updated_at`);
|
||||
CREATE INDEX idx_created_at ON `wa_thali` (`created_at`);
|
||||
CREATE INDEX idx_updated_at ON `wa_thali` (`updated_at`);
|
||||
CREATE INDEX idx_status ON `wa_thali` (`status`);
|
||||
CREATE INDEX idx_status_created_at ON `wa_thali` (`status`, `created_at`);
|
||||
CREATE INDEX idx_role_id ON `wa_user` (`role_id`);
|
||||
CREATE INDEX idx_parent_id ON `wa_user` (`parent_id`);
|
||||
CREATE INDEX idx_group_id ON `wa_user` (`group_id`);
|
||||
CREATE INDEX idx_status ON `wa_user` (`status`);
|
||||
CREATE INDEX idx_created_at ON `wa_user` (`created_at`);
|
||||
CREATE INDEX idx_updated_at ON `wa_user` (`updated_at`);
|
||||
CREATE INDEX idx_created_at ON `wa_user` (`created_at`);
|
||||
CREATE INDEX idx_updated_at ON `wa_user` (`updated_at`);
|
||||
CREATE INDEX idx_status ON `wa_user` (`status`);
|
||||
CREATE INDEX idx_status_created_at ON `wa_user` (`status`, `created_at`);
|
||||
CREATE INDEX idx_user_id ON `wa_user_extend` (`user_id`);
|
||||
CREATE INDEX idx_user_id ON `wa_user_extend` (`user_id`);
|
||||
CREATE INDEX idx_status ON `wa_user_role` (`status`);
|
||||
CREATE INDEX idx_created_at ON `wa_user_role` (`created_at`);
|
||||
CREATE INDEX idx_updated_at ON `wa_user_role` (`updated_at`);
|
||||
CREATE INDEX idx_created_at ON `wa_user_role` (`created_at`);
|
||||
CREATE INDEX idx_updated_at ON `wa_user_role` (`updated_at`);
|
||||
CREATE INDEX idx_status ON `wa_user_role` (`status`);
|
||||
CREATE INDEX idx_status_created_at ON `wa_user_role` (`status`, `created_at`);
|
||||
CREATE INDEX idx_status ON `wa_user_rule` (`status`);
|
||||
CREATE INDEX idx_type ON `wa_user_rule` (`type`);
|
||||
CREATE INDEX idx_created_at ON `wa_user_rule` (`created_at`);
|
||||
CREATE INDEX idx_updated_at ON `wa_user_rule` (`updated_at`);
|
||||
CREATE INDEX idx_created_at ON `wa_user_rule` (`created_at`);
|
||||
CREATE INDEX idx_updated_at ON `wa_user_rule` (`updated_at`);
|
||||
CREATE INDEX idx_status ON `wa_user_rule` (`status`);
|
||||
CREATE INDEX idx_type ON `wa_user_rule` (`type`);
|
||||
CREATE INDEX idx_status_created_at ON `wa_user_rule` (`status`, `created_at`);
|
||||
CREATE INDEX idx_type_created_at ON `wa_user_rule` (`type`, `created_at`);
|
||||
CREATE INDEX idx_descendant_id ON `wa_user_team` (`descendant_id`);
|
||||
CREATE INDEX idx_ancestor_id ON `wa_user_team` (`ancestor_id`);
|
||||
CREATE INDEX idx_status ON `wa_user_team` (`status`);
|
||||
CREATE INDEX idx_status ON `wa_user_team` (`status`);
|
||||
CREATE INDEX idx_status ON `wa_version` (`status`);
|
||||
CREATE INDEX idx_type ON `wa_version` (`type`);
|
||||
CREATE INDEX idx_status ON `wa_version` (`status`);
|
||||
CREATE INDEX idx_type ON `wa_version` (`type`);
|
||||
CREATE INDEX idx_status ON `wa_withdrawl` (`status`);
|
||||
CREATE INDEX idx_created_at ON `wa_withdrawl` (`created_at`);
|
||||
CREATE INDEX idx_updated_at ON `wa_withdrawl` (`updated_at`);
|
||||
CREATE INDEX idx_created_at ON `wa_withdrawl` (`created_at`);
|
||||
CREATE INDEX idx_updated_at ON `wa_withdrawl` (`updated_at`);
|
||||
CREATE INDEX idx_status ON `wa_withdrawl` (`status`);
|
||||
CREATE INDEX idx_user_id_created_at ON `wa_withdrawl` (`user_id`, `created_at`);
|
||||
CREATE INDEX idx_status_created_at ON `wa_withdrawl` (`status`, `created_at`);
|
||||
Binary file not shown.
@@ -0,0 +1,14 @@
|
||||
CREATE TABLE `wa_address` (
|
||||
`id` int NOT NULL AUTO_INCREMENT,
|
||||
`user_id` int DEFAULT NULL,
|
||||
`title` varchar(100) DEFAULT NULL,
|
||||
`network` varchar(10) DEFAULT 'BEP-20',
|
||||
`address` varchar(80) NOT NULL,
|
||||
`status` tinyint(1) DEFAULT '1',
|
||||
`created_at` int NOT NULL DEFAULT '0',
|
||||
`updated_at` int DEFAULT NULL,
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `user_id` (`user_id`),
|
||||
KEY `network` (`network`),
|
||||
KEY `status` (`status`)
|
||||
) ENGINE=InnoDB AUTO_INCREMENT=770 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
|
||||
@@ -0,0 +1,16 @@
|
||||
CREATE TABLE `wa_admin` (
|
||||
`id` int unsigned NOT NULL AUTO_INCREMENT COMMENT 'ID',
|
||||
`username` varchar(32) NOT NULL COMMENT '用户名',
|
||||
`nickname` varchar(40) NOT NULL COMMENT '昵称',
|
||||
`password` varchar(255) NOT NULL COMMENT '密码',
|
||||
`avatar` varchar(255) DEFAULT '/app/admin/avatar.png' COMMENT '头像',
|
||||
`email` varchar(100) DEFAULT NULL COMMENT '邮箱',
|
||||
`mobile` varchar(16) DEFAULT NULL COMMENT '手机',
|
||||
`totp_secret` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL,
|
||||
`created_at` int DEFAULT NULL,
|
||||
`updated_at` int DEFAULT NULL,
|
||||
`login_at` int DEFAULT NULL,
|
||||
`status` tinyint DEFAULT NULL COMMENT '禁用',
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE KEY `username` (`username`)
|
||||
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='管理员表';
|
||||
@@ -0,0 +1,7 @@
|
||||
CREATE TABLE `wa_admin_access` (
|
||||
`id` int NOT NULL AUTO_INCREMENT COMMENT '主键',
|
||||
`role_id` int NOT NULL COMMENT '角色id',
|
||||
`admin_id` int NOT NULL COMMENT '管理员id',
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE KEY `role_admin_id` (`role_id`,`admin_id`)
|
||||
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='管理员角色表';
|
||||
@@ -0,0 +1,9 @@
|
||||
CREATE TABLE `wa_admin_role` (
|
||||
`id` int unsigned NOT NULL AUTO_INCREMENT COMMENT '主键',
|
||||
`name` varchar(80) NOT NULL COMMENT '角色组',
|
||||
`rules` text COMMENT '权限',
|
||||
`created_at` int DEFAULT NULL,
|
||||
`updated_at` int DEFAULT NULL,
|
||||
`pid` int unsigned DEFAULT NULL COMMENT '父级',
|
||||
PRIMARY KEY (`id`)
|
||||
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='管理员角色';
|
||||
@@ -0,0 +1,15 @@
|
||||
CREATE TABLE `wa_admin_rule` (
|
||||
`id` int unsigned NOT NULL AUTO_INCREMENT COMMENT '主键',
|
||||
`extend` varchar(255) DEFAULT NULL,
|
||||
`title` varchar(255) NOT NULL COMMENT '标题',
|
||||
`icon` varchar(255) DEFAULT NULL COMMENT '图标',
|
||||
`key` varchar(255) NOT NULL COMMENT '标识',
|
||||
`pid` int unsigned DEFAULT '0' COMMENT '上级菜单',
|
||||
`created_at` datetime DEFAULT NULL COMMENT '创建时间',
|
||||
`updated_at` datetime DEFAULT NULL COMMENT '更新时间',
|
||||
`href` varchar(255) DEFAULT NULL COMMENT 'url',
|
||||
`type` int NOT NULL DEFAULT '1' COMMENT '类型',
|
||||
`weight` int DEFAULT '0' COMMENT '排序',
|
||||
`status` tinyint(1) NOT NULL DEFAULT '1',
|
||||
PRIMARY KEY (`id`)
|
||||
) ENGINE=InnoDB AUTO_INCREMENT=293 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='权限规则';
|
||||
@@ -0,0 +1,14 @@
|
||||
CREATE TABLE `wa_album` (
|
||||
`id` int NOT NULL AUTO_INCREMENT COMMENT '主键',
|
||||
`groupID` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '群ID',
|
||||
`userID` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '用户ID',
|
||||
`title` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '标题',
|
||||
`image` int NOT NULL COMMENT '封面ID',
|
||||
`total` int NOT NULL COMMENT '相片总数',
|
||||
`status` tinyint(1) NOT NULL COMMENT '状态',
|
||||
`created_at` int NOT NULL COMMENT '创建时间',
|
||||
`updated_at` int NOT NULL COMMENT '更新时间',
|
||||
`weigh` int NOT NULL COMMENT '排序权重',
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `groupID` (`groupID`,`userID`)
|
||||
) ENGINE=InnoDB AUTO_INCREMENT=26 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
|
||||
@@ -0,0 +1,24 @@
|
||||
CREATE TABLE `wa_archives` (
|
||||
`id` int unsigned NOT NULL AUTO_INCREMENT,
|
||||
`type` varchar(10) NOT NULL DEFAULT 'article',
|
||||
`user_id` int unsigned NOT NULL DEFAULT '0' COMMENT '会员ID',
|
||||
`category_id` int DEFAULT NULL COMMENT '分类ID',
|
||||
`title` varchar(255) DEFAULT '' COMMENT '文章标题',
|
||||
`name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL COMMENT '标识',
|
||||
`flag` varchar(100) DEFAULT '' COMMENT '标志',
|
||||
`style` varchar(100) DEFAULT '' COMMENT '样式',
|
||||
`image` varchar(255) DEFAULT '' COMMENT '缩略图',
|
||||
`images` varchar(1500) DEFAULT '' COMMENT '组图',
|
||||
`tags` varchar(255) DEFAULT '' COMMENT 'TAG',
|
||||
`intro` text,
|
||||
`weigh` int NOT NULL DEFAULT '0' COMMENT '权重',
|
||||
`views` int unsigned NOT NULL DEFAULT '0' COMMENT '浏览次数',
|
||||
`created_at` int DEFAULT NULL COMMENT '创建时间',
|
||||
`updated_at` int DEFAULT NULL COMMENT '更新时间',
|
||||
`publishtime` int DEFAULT NULL COMMENT '发布时间',
|
||||
`deleted_at` int DEFAULT NULL COMMENT '删除时间',
|
||||
`memo` varchar(100) DEFAULT '' COMMENT '备注',
|
||||
`status` enum('normal','hidden','rejected','pulloff') NOT NULL DEFAULT 'normal' COMMENT '状态',
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `weigh` (`weigh`,`publishtime`)
|
||||
) ENGINE=InnoDB AUTO_INCREMENT=618 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='内容表';
|
||||
@@ -0,0 +1,8 @@
|
||||
CREATE TABLE `wa_archives_read` (
|
||||
`user_id` int NOT NULL,
|
||||
`source_id` int DEFAULT NULL,
|
||||
`value` tinyint(1) DEFAULT '1',
|
||||
KEY `user_id` (`user_id`) USING BTREE,
|
||||
KEY `source_id` (`source_id`) USING BTREE,
|
||||
KEY `value` (`value`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
|
||||
@@ -0,0 +1,13 @@
|
||||
CREATE TABLE `wa_card` (
|
||||
`id` int NOT NULL AUTO_INCREMENT,
|
||||
`type` int DEFAULT NULL,
|
||||
`title` varchar(64) NOT NULL,
|
||||
`total` int NOT NULL DEFAULT '0',
|
||||
`used` int DEFAULT '0',
|
||||
`expires` int NOT NULL,
|
||||
`days` int NOT NULL DEFAULT '0',
|
||||
`status` tinyint(1) NOT NULL DEFAULT '1',
|
||||
`created_at` int NOT NULL,
|
||||
`updated_at` int DEFAULT NULL,
|
||||
PRIMARY KEY (`id`)
|
||||
) ENGINE=InnoDB AUTO_INCREMENT=12 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
|
||||
@@ -0,0 +1,9 @@
|
||||
CREATE TABLE `wa_category` (
|
||||
`id` int NOT NULL AUTO_INCREMENT,
|
||||
`title` varchar(64) NOT NULL,
|
||||
`type` varchar(10) DEFAULT NULL,
|
||||
`status` tinyint(1) NOT NULL DEFAULT '1',
|
||||
`created_at` int DEFAULT NULL,
|
||||
`updated_at` int DEFAULT NULL,
|
||||
PRIMARY KEY (`id`)
|
||||
) ENGINE=InnoDB AUTO_INCREMENT=11 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
|
||||
@@ -0,0 +1,17 @@
|
||||
CREATE TABLE `wa_cdkey` (
|
||||
`id` int NOT NULL AUTO_INCREMENT,
|
||||
`type` int DEFAULT NULL,
|
||||
`category_id` int DEFAULT NULL,
|
||||
`account` varchar(64) NOT NULL,
|
||||
`passworrd` varchar(64) DEFAULT NULL,
|
||||
`days` int DEFAULT '0',
|
||||
`expires` int NOT NULL,
|
||||
`is_used` tinyint(1) NOT NULL DEFAULT '0',
|
||||
`record_id` int DEFAULT NULL,
|
||||
`use_time` int DEFAULT NULL,
|
||||
`status` tinyint(1) NOT NULL DEFAULT '1',
|
||||
`created_at` int NOT NULL,
|
||||
`updated_at` int DEFAULT NULL,
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE KEY `account` (`account`)
|
||||
) ENGINE=InnoDB AUTO_INCREMENT=11314 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user