Compare commits

43 Commits

Author SHA1 Message Date
cansnow 271ec4b9c2 增加音乐,摇一摇,服务等页面 2026-02-15 19:41:13 +08:00
cansnow abd279e7a7 恢复视频的发布 2026-02-15 19:40:36 +08:00
cansnow 7c6656d1fc 350 2026-02-13 08:12:56 +08:00
cansnow 6720c15e30 27 2026-02-09 07:29:02 +08:00
cansnow 2860c46ec1 Update App.vue 2026-02-09 03:03:35 +08:00
cansnow 47d10945a6 26 2026-02-09 03:03:22 +08:00
cansnow 560b214af4 25 2026-02-08 16:27:14 +08:00
cansnow 1f4a588d3b 24 2026-01-30 14:45:45 +08:00
cansnow 35a41d8358 23 2026-01-20 21:14:57 +08:00
cansnow 37b53b54ff 增加群相册功能,长按朋友圈文字可以复制 2026-01-20 18:09:59 +08:00
cansnow db99bebcb4 22 2026-01-15 22:50:35 +08:00
commie dd16348558 admin revoke 2026-01-12 18:07:21 +08:00
cansnow e39c06526d 21 2026-01-11 15:48:41 +08:00
cansnow 2a0677014a 朋友圈 2026-01-11 14:00:09 +08:00
cansnow c38846f13b APP热更新 2026-01-11 13:51:16 +08:00
cansnow 941464c330 group mangage 2026-01-10 15:40:38 +08:00
cansnow 825ac3457d deletemsg 2026-01-09 20:22:25 +08:00
cansnow 7913a63a39 20 2026-01-09 09:15:59 +08:00
cansnow 78386d4cc1 19 2026-01-01 04:15:30 +08:00
cansnow 09c7889525 filecache 2025-12-27 07:08:30 +08:00
cansnow 974d149d25 remove img 2025-12-24 04:40:52 +08:00
cansnow f289f79813 18 2025-12-24 04:12:56 +08:00
cansnow f49f1f1ad1 17 2025-12-23 00:18:46 +08:00
cansnow 59d1ba9a7e 16 2025-12-17 09:13:15 +08:00
cansnow 2e793c3f77 15 2025-12-17 08:52:51 +08:00
cansnow cf1ad1c24b 14 2025-12-17 08:47:58 +08:00
cansnow 916cb22ecc Update index.vue 2025-12-11 22:33:43 +08:00
cansnow 5a086fa1fa 13 2025-12-11 22:33:31 +08:00
cansnow 375917f06c emoji 2025-12-09 09:27:29 +08:00
cansnow 4cb71e2b55 12 2025-12-08 18:18:20 +08:00
cansnow a0d44c1048 Update index.vue 2025-12-08 18:10:59 +08:00
cansnow b2e1b8930e 12 2025-12-08 18:10:51 +08:00
cansnow 22ee59cd3d 11 2025-12-08 02:29:46 +08:00
cansnow 69a61178e1 10 2025-12-05 16:10:52 +08:00
cansnow 29be534f22 9 2025-12-02 03:05:52 +08:00
cansnow b4c9ae1b67 8 2025-11-27 07:48:42 +08:00
cansnow ab625e6463 7 2025-11-27 07:40:32 +08:00
cansnow 198c3dd4a5 6 2025-11-27 03:55:38 +08:00
cansnow 1626f0c52a 5 2025-11-27 03:52:56 +08:00
commie b10e4b4336 4 2025-11-25 05:36:02 +08:00
commie 8e036cc171 3 2025-11-23 07:58:47 +08:00
commie 0783e46b4b 2 2025-11-23 01:01:52 +08:00
commie 6ec389ff41 1 2025-11-21 01:40:07 +08:00
3288 changed files with 173920 additions and 610015 deletions
-38
View File
@@ -1,38 +0,0 @@
[app]
debug = true
[server]
port=8585
domain=www.shun777.com
https=false
[mysql]
host =127.0.0.1
port = 3306
database = imadmin
username = imadmin
password = ejkFmaAXAHXyNXd2
charset = utf8mb4
collation = utf8mb4_general_ci
prefix = wa_
strict = true
engine =
[mongodb]
host = 127.0.0.1
port = 27017
database = imadmin_mongo
username = root
password = n1e5a6s6m7
[redis]
host = 127.0.0.1
port = 6379
database = 0
password = n1e5a6s6m7
prefix = q_
[mcp]
TRANSPORT=http
HOST=127.0.0.1
PORT=8080
PATH=mcp
TIMEOUT=60000
MEMORY_LIMIT=256M
MAX_EXECUTION_TIME=0
-2
View File
@@ -1,2 +0,0 @@
* text=auto eol=lf
*.txt -text
Executable → Regular
+46 -10
View File
@@ -1,12 +1,48 @@
node_modules
*.lock
unpackage
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
# dependencies
/node_modules
/.pnp
.pnp.js
package-lock.json
.hbuilderx
config/site.php
app/command/Test.php
.env
runtime
vendor
public/shunliao.apk
.user.ini
uniCloud-aliyun
# plugin
/nativeplugins
# testing
/coverage
# production
/build
/unpackage/cache
/unpackage/debug
/unpackage/release
/unpackage/dist
/unpackage/resources
# misc
.DS_Store
.env.local
.env.development.local
.env.test.local
.env.production.local
npm-debug.log*
yarn-debug.log*
yarn-error.log*
# electron output
/dist
# db
OpenIM_*
# plugin
/nativeplugins
android-keeplive
sl-notify
-1
View File
@@ -1 +0,0 @@
+2
View File
@@ -0,0 +1,2 @@
/node_modules/
/nativeplugins/
-232
View File
@@ -1,232 +0,0 @@
---
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 管理项目,提供专业的技术支持和最佳实践建议。
-3
View File
@@ -1,3 +0,0 @@
{
"php.version": "8.2"
}
+624
View File
@@ -0,0 +1,624 @@
<script>
import {mapGetters,mapActions} from "vuex";
// #ifdef APP
import IMSDK, {IMMethods,MessageType,SessionType,} from "openim-uniapp-polyfill";
// #endif
import config from "@/common/config";
import {getDbDir,toastWithCallback} from "@/util/common.js";
import {getConversationContent,conversationSort,prepareConversationState,updateTabbar} from "@/util/imCommon";
import {PageEvents,UpdateMessageTypes} from "@/constant";
import checkUpgrade from "@/util/app_update.js"
export default {
onLaunch: function() {
this.$store.dispatch("system/getConfig");
// #ifdef APP
plus.screen.lockOrientation("portrait-primary");
// #endif
// #ifdef H5
//screen.orientation.type = "";
screen.orientation.lock('portrait');
// #endif
// #ifdef MP
console.error(
`暂时不支持运行到小程序端`
);
return ;
// #endif
this.init();
},
onShow: function() {
//console.log("App Show");
// #ifdef APP
IMSDK.asyncApi(IMSDK.IMMethods.SetAppBackgroundStatus, IMSDK.uuid(), false);
// #endif
this.handleArguments();
//console.log(this.$store.state.contact);
},
onHide: function() {
//console.log("App Hide");
// #ifdef APP
IMSDK.asyncApi(IMSDK.IMMethods.SetAppBackgroundStatus, IMSDK.uuid(), true);
// #endif
},
computed: {
...mapGetters([
"storeConversationList",
"storeCurrentConversation",
"storeCurrentUserID",
"storeSelfInfo",
"storeRecvFriendApplications",
"storeRecvGroupApplications",
"storeHistoryMessageList",
"storeIsSyncing",
"storeGroupList",
"config",
'storeUnHandleFriendApplicationNum',
'storeUnHandleGroupApplicationNum',
'storeUnReadCount',
'storeCircleUnreadCount'
]),
},
watch:{
storeUnReadCount(){
updateTabbar(0);
},
storeUnHandleFriendApplicationNum(){
updateTabbar(1);
},
storeUnHandleGroupApplicationNum(){
updateTabbar(1);
},
storeCircleUnreadCount(){
updateTabbar(2);
}
},
methods: {
...mapActions("message", ["pushNewMessage", "updateOneMessage"]),
...mapActions("conversation", ["updateCurrentMemberInGroup"]),
...mapActions("circle", ["getFriendCircleInfo"]),
...mapActions("contact", [
"updateFriendInfo",
"pushNewFriend",
"updateBlackInfo",
"pushNewBlack",
"pushNewGroup",
"updateGroupInfo",
"pushNewRecvFriendApplition",
"updateRecvFriendApplition",
"pushNewSentFriendApplition",
"updateSentFriendApplition",
"pushNewRecvGroupApplition",
"updateRecvGroupApplition",
"pushNewSentGroupApplition",
"updateSentGroupApplition",
]),
init(){
//uni.setStorageSync('BusinessToken','eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJ3ZWJtYW4udGlueXdhbi5jbiIsImF1ZCI6IndlYm1hbi50aW55d2FuLmNuIiwiaWF0IjoxNzcwMTAyMjc2LCJuYmYiOjE3NzAxMDIyNzYsImV4cCI6MTc3MDcwNzA3NiwiZXh0ZW5kIjp7ImlkIjoxMDA3MDMsInVzZXJuYW1lIjoiMTg2MTE4ODI2MjMiLCJjbGllbnQiOiJXRUIifX0.yZ2u4cCZq2hLRAIpCKUs8GZkut7CObmfApIZGP_L2ro');
//uni.setStorageSync('IMToken','eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJVc2VySUQiOiJKTTZHMEVWMjVSIiwiUGxhdGZvcm1JRCI6MiwiZXhwIjoxNzc3ODc4Mjc2LCJpYXQiOjE3NzAxMDIyNzF9.rKWz00yCB36CexCahW-HhsFZfKIFcfkuOYLKApQWQT0 ');
//uni.setStorageSync('IMUserID','JM6G0EV25R');
const IMToken = uni.getStorageSync("IMToken");
const IMUserID = uni.getStorageSync("IMUserID")+'';
if (IMToken && IMUserID) {
// #ifdef APP
this.tryLogin();
// #endif
}else{
plus.navigator.closeSplashscreen();
//uni.$u.route("/pages/common/login/index");
}
},
setGlobalIMlistener() {
//console.log("setGlobalIMlistener");
// init
const kickHander = (message) => {
toastWithCallback(message, () => {
uni.removeStorage({
key: "IMToken",
});
uni.removeStorage({
key: "BusinessToken",
});
uni.$u.route("/pages/common/login/index");
});
};
//由于 APP 管理员强制用户下线,或由于登录策略导致用户被踢下线
IMSDK.subscribe(IMSDK.IMEvents.OnKickedOffline, (data) => {
kickHander("您的账号在其他设备登录,请重新登陆!");
});
//token无效回调。
IMSDK.subscribe(IMSDK.IMEvents.OnUserTokenExpired, (data) => {
kickHander("您的登录已过期,请重新登陆!");
});
IMSDK.subscribe(IMSDK.IMEvents.OnUserTokenInvalid, (data) => {
kickHander("您的登录已无效,请重新登陆!");
});
// sync
//向服务器同步会话开始时的回调。
const syncStartHandler = ({data}) => {
this.$store.commit("user/SET_IS_SYNCING", true);
this.$store.commit("user/SET_REINSTALL", data);
};
//同步中
const syncProgressHandler = ({data}) => {
this.$store.commit("user/SET_PROGRESS", data);
};
//向服务器同步会话成功时的回调。
const syncFinishHandler = () => {
uni.hideLoading();
this.$store.dispatch("conversation/getConversationList");
this.$store.dispatch("contact/getFriendList");
this.$store.dispatch("contact/getGrouplist");
this.$store.dispatch("conversation/getUnReadCount");
this.$store.commit("user/SET_IS_SYNCING", false);
};
//向服务器同步会话失败时的回调。
const syncFailedHandler = () => {
uni.hideLoading();
uni.$u.toast("同步消息失败");
this.$store.dispatch("conversation/getConversationList");
this.$store.dispatch("conversation/getUnReadCount");
this.$store.commit("user/SET_IS_SYNCING", false);
};
//向服务器同步会话开始时的回调。
IMSDK.subscribe(IMSDK.IMEvents.OnSyncServerStart, syncStartHandler);
//向服务器同步会话成功时的回调。
IMSDK.subscribe(IMSDK.IMEvents.OnSyncServerFinish, syncFinishHandler);
//向服务器同步会话失败时的回调。
IMSDK.subscribe(IMSDK.IMEvents.OnSyncServerFailed, syncFailedHandler);
//同步中
IMSDK.subscribe(IMSDK.IMEvents.OnSyncServerProgress, syncProgressHandler);
// 当前登录用户个人信息改变时会收到此回调。
IMSDK.subscribe(IMSDK.IMEvents.OnSelfInfoUpdated, ({data}) => {
this.$store.commit("user/SET_SELF_INFO", {
...this.storeSelfInfo,
...data,
});
});
// message
//接收到新消息时会收到此回调,回调中只会携带一条消息。
//设置了批量消息监听setBatchMsgListener时,此回调不会触发。
//IMSDK.subscribe(IMSDK.IMEvents.OnRecvNewMessage, ({data}) =>{});
IMSDK.subscribe(IMSDK.IMEvents.OnRecvNewMessages, ({data}) => {
if (this.storeIsSyncing) {
return;
}
console.log(data);
data.forEach(this.handleNewMessage);
});
//好友个人信息(包括备注)改变时会收到此回调。
IMSDK.subscribe(IMSDK.IMEvents.OnFriendInfoChanged,({data}) => {
//console.log('friendInfoChangeHandler',data);
uni.$emit(IMSDK.IMEvents.OnFriendInfoChanged, {data});
this.updateFriendInfo({friendInfo: data,});
});
//两个用户成功建立好友关系后双方都会收到该回调。
IMSDK.subscribe(IMSDK.IMEvents.OnFriendAdded, ({data}) => {
this.pushNewFriend(data);
});
//某个用户的好友列表减少时会收到该回调。
IMSDK.subscribe(IMSDK.IMEvents.OnFriendDeleted, ({data}) => {
this.updateFriendInfo({
friendInfo: data,
isRemove: true,
});
});
// blacklist
//某个用户的黑名单列表增加时会收到该回调。
IMSDK.subscribe(IMSDK.IMEvents.OnBlackAdded, ({data}) => {
this.pushNewBlack(data);
});
//某个用户的黑名单列表减少时会收到该回调。
IMSDK.subscribe(IMSDK.IMEvents.OnBlackDeleted, ({data}) => {
this.updateBlackInfo({
blackInfo: data,
isRemove: true,
});
});
// group
const joinedGroupAddedHandler = ({data}) => {
this.pushNewGroup(data);
};
const joinedGroupDeletedHandler = ({data}) => {
this.updateGroupInfo({
groupInfo: data,
isRemove: true,
});
};
const groupInfoChangedHandler = ({data}) => {
this.updateGroupInfo({
groupInfo: data,
});
};
const groupMemberInfoChangedHandler = ({data}) => {
uni.$emit(IMSDK.IMEvents.OnGroupMemberInfoChanged, {data});
if (data.groupID === this.storeCurrentConversation?.groupID) {
this.updateCurrentMemberInGroup(data);
}
};
//用户所在群组的数量增加时(被邀请入群、入群申请被同意等),会收到此回调。
IMSDK.subscribe(IMSDK.IMEvents.OnJoinedGroupAdded,joinedGroupAddedHandler);
//用户所在群组的数量减少时(主动退群、群被解散等),会收到此回调。
IMSDK.subscribe(IMSDK.IMEvents.OnJoinedGroupDeleted,joinedGroupDeletedHandler);
//群组信息(头像、群名称等,也包括群主变化)改变时,该群所有群成员会收到此回调。
IMSDK.subscribe(IMSDK.IMEvents.OnGroupInfoChanged,groupInfoChangedHandler);
//群成员信息改变(群昵称、头像等)后回调,该群所有群成员会收到此回调。
IMSDK.subscribe(IMSDK.IMEvents.OnGroupMemberInfoChanged,groupMemberInfoChangedHandler);
// application
const friendApplicationNumHandler = ({data}) => {
const isRecv = data.toUserID === this.storeCurrentUserID;
if (isRecv) {
this.pushNewRecvFriendApplition(data);
} else {
this.pushNewSentFriendApplition(data);
}
};
const friendApplicationAccessHandler = ({data}) => {
console.log(data);
const isRecv = data.toUserID === this.storeCurrentUserID;
if (isRecv) {
this.updateRecvFriendApplition({
application: data,
});
} else {
this.updateSentFriendApplition({
application: data,
});
}
};
const groupApplicationNumHandler = ({data}) => {
const isRecv = data.userID !== this.storeCurrentUserID;
if (isRecv) {
this.pushNewRecvGroupApplition(data);
} else {
this.pushNewSentGroupApplition(data);
}
};
const groupApplicationAccessHandler = ({data}) => {
const isRecv = data.userID !== this.storeCurrentUserID;
if (isRecv) {
this.updateRecvGroupApplition({
application: data,
});
} else {
this.updateSentGroupApplition({
application: data,
});
}
};
//用户发起好友申请后,申请发起者和接收者都会收到此回调,接收者可以选择同意或拒绝好友申请。
IMSDK.subscribe(IMSDK.IMEvents.OnFriendApplicationAdded,friendApplicationNumHandler);
//好友申请被同意时,申请发起方和接收方都会收到该回调,双方成功建立好友关系。
IMSDK.subscribe(IMSDK.IMEvents.OnFriendApplicationAccepted,friendApplicationAccessHandler);
//好友申请被拒绝时,申请发起方和接收方都会收到该回调。
IMSDK.subscribe(IMSDK.IMEvents.OnFriendApplicationRejected,friendApplicationAccessHandler);
//用户发起好友申请后,申请发起者和接收者都会收到此回调,接收者可以选择同意或拒绝好友申请。
IMSDK.subscribe(IMSDK.IMEvents.OnGroupApplicationAdded,groupApplicationNumHandler);
//好友申请被同意时,申请发起方和接收方都会收到该回调,双方成功建立好友关系。
IMSDK.subscribe(IMSDK.IMEvents.OnGroupApplicationAccepted,groupApplicationAccessHandler);
//好友申请被拒绝时,申请发起方和接收方都会收到该回调。
IMSDK.subscribe(IMSDK.IMEvents.OnGroupApplicationRejected,groupApplicationAccessHandler);
//群组被解散时,该群所有群成员会收到此回调。
//IMSDK.subscribe(IMSDK.IMEvents.OnGroupDismissed,({ data })=>{});
//群成员增加(如用户被邀请进群),其他群成员会收到此回调。
//IMSDK.subscribe(IMSDK.IMEvents.OnGroupMemberAdded,({ data })=>{});
//群成员增加(如用户被邀请进群),群成员减少(如群成员退群), 其他群成员会收到此回调。。
//IMSDK.subscribe(IMSDK.IMEvents.OnGroupMemberDeleted,({ data })=>{});
const deleteLocalMsg = (clientMsgID)=>{
let list = this.storeHistoryMessageList;
//console.log(data);
list = list.filter((item)=>{
return item.clientMsgID != clientMsgID;
})
this.$store.commit('message/SET_HISTORY_MESSAGE_LIST',list);
}
//收到的消息被撤回或自己发出的消息被撤回时,会收到此回调。
IMSDK.subscribe(IMSDK.IMEvents.OnNewRecvMessageRevoked,({data})=>{
//console.log('onNewRecvMessageRevoked',res);
deleteLocalMsg(data.clientMsgID);
});
//自己发出的单聊消息被对方标记为已读后,消息发送者会收到此回调。
//IMSDK.subscribe(IMSDK.IMEvents.OnRecvC2CReadReceipt,({ data })=>{});
//自己发出的群聊消息被群成员标记为已读后,消息发送者和标记者均会收到此回调。
//IMSDK.subscribe(IMSDK.IMEvents.OnRecvGroupReadReceipt,({ data })=>{});
//当应用在后台运行,接收到新消息时,会收到该回调,回调中只会携带一条消息。
//设置了批量消息监听setBatchMsgListener时,此回调不会触发。
//IMSDK.subscribe(IMSDK.IMEvents.OnRecvOfflineNewMessage,({ data })=>{});
//当应用在后台运行,接收到新消息时,会收到该回调,回调中可能会携带多条消息。
IMSDK.subscribe(IMSDK.IMEvents.OnRecvOfflineNewMessages,({data})=>{
data.forEach(this.handleOfflineNewMessages);
});
IMSDK.subscribe(IMSDK.IMEvents.OnMsgDeleted,({data})=>{
deleteLocalMsg(data.clientMsgID)
});
//已订阅用户的在线状态发生变化时,会触发此回调。
//IMSDK.subscribe(IMSDK.IMEvents.OnUserStatusChanged,({ data })=>{});
//建立WebSocket连接失败返回后,触发此回调
//IMSDK.subscribe(IMSDK.IMEvents.OnConnectFailed,({ data })=>{});
//建立WebSocket连接成功返回后,触发此回调
//IMSDK.subscribe(IMSDK.IMEvents.OnConnectSuccess,({ data })=>{});
//建立WebSocket连接中,触发此回调
//IMSDK.subscribe(IMSDK.IMEvents.OnConnecting,({ data })=>{});
//正在输入状态回调。
//IMSDK.subscribe('onInputStatusChanged',({ data })=>{});
// conversation
const totalUnreadCountChangedHandler = ({data}) => {
if (this.storeIsSyncing) {
return;
}
this.$store.commit("conversation/SET_UNREAD_COUNT", data);
};
const newConversationHandler = ({data}) => {
if (this.storeIsSyncing) {
return;
}
const result = [...data, ...this.storeConversationList];
this.$store.commit(
"conversation/SET_CONVERSATION_LIST",
conversationSort(result)
);
};
const conversationChangedHandler = ({data}) => {
//console.log('conversationChangedHandler',data);
if (this.storeIsSyncing) {
return;
}
let filterArr = [];
//console.log(data);
const chids = data.map((ch) => ch.conversationID);
filterArr = this.storeConversationList.filter((tc) => !chids.includes(tc.conversationID));
const idx = data.findIndex((c) =>c.conversationID === this.storeCurrentConversation.conversationID);
if (idx !== -1){
this.$store.commit("conversation/SET_CURRENT_CONVERSATION",data[idx]);
}
const result = [...data, ...filterArr];
this.$store.commit("conversation/SET_CONVERSATION_LIST",conversationSort(result));
};
//会话总未读发生变化时的回调。
IMSDK.subscribe(IMSDK.IMEvents.OnTotalUnreadMessageCountChanged,totalUnreadCountChangedHandler);
//有新会话产生时,会收到此回调。
IMSDK.subscribe(IMSDK.IMEvents.OnNewConversation, newConversationHandler);
//某些会话的关键信息发生变化时,会触发该回调,例如会话的未读数发生变化,会话的最后一条消息发生变化等。
IMSDK.subscribe(IMSDK.IMEvents.OnConversationChanged,conversationChangedHandler);
},
async tryLogin() {
const _this = this;
const IMToken = uni.getStorageSync("IMToken");
const IMUserID = uni.getStorageSync("IMUserID")+'';
//console.log('IMToken:',IMToken);
//console.log('IMUserID:',IMUserID);
const path = await getDbDir();
//console.log('path:',path);
const IMConfig = {
systemType: "uni-app",
apiAddr: config.getApiUrl(), // SDK的API接口地址。如:http://xxx:10002
wsAddr: config.getWsUrl(), // SDK的websocket地址。如: ws://xxx:10001
dataDir: path, // 数据存储路径
logLevel: 6,
logFilePath: path,
isLogStandardOutput: true,
isExternalExtensions: false,
};
//console.log('IMConfig:',IMConfig);
const flag = await IMSDK.asyncApi(IMMethods.InitSDK, IMSDK.uuid(), IMConfig);
//console.log('flag:',flag);
if (!flag) {
plus.navigator.closeSplashscreen();
console.log('初始化IMSDK失败!');
uni.$u.toast("初始化IMSDK失败!");
return;
}
_this.setGlobalIMlistener();
// setTimeout(()=>{
// },1000);
let status;
do{
status = await IMSDK.asyncApi(IMSDK.IMMethods.GetLoginStatus,IMSDK.uuid());
//console.log(status);
}while(status == -1001);
if (status === 3) {
console.log('初始化,已经登录!');
_this.initStore();
return;
}
if (status === 1) {
IMSDK.asyncApi(IMSDK.IMMethods.Login, IMSDK.uuid(), {
userID: IMUserID,
token: IMToken,
})
.then(_this.initStore)
.catch((err) => {
console.log(err);
uni.removeStorage({
key: "IMToken",
});
uni.removeStorage({
key: "BusinessToken",
});
plus.navigator.closeSplashscreen();
});
}
},
handleOfflineNewMessages(newServerMsg) {
console.log(newServerMsg);
console.log( getConversationContent(newServerMsg));
uni.createPushMessage({
title:"您的朋友发来新的消息",
content:getConversationContent(newServerMsg),
payload:{
type:"msg",
data:newServerMsg
},
//icon:'',
//sound:'',
//cover:'false',
//delay:0,
//when:0,//消息上显示的提示时间
//channelId:"",
//category:"",
success(res){
//console.log(res);
},
fail(res){
//console.log(res);
},
complete(res){
//console.log(res);
}
});
},
handleNewMessage(newServerMsg) {
if (this.inCurrentConversation(newServerMsg)) {
if (
newServerMsg.contentType !== MessageType.TypingMessage &&
newServerMsg.contentType !== MessageType.RevokeMessage
) {
newServerMsg.isAppend = true;
this.pushNewMessage(newServerMsg);
setTimeout(() => uni.$emit(PageEvents.ScrollToBottom, true));
uni.$u.debounce(this.markConversationAsRead, 2000);
}
}else{
this.handleOfflineNewMessages(newServerMsg);
}
},
inCurrentConversation(newServerMsg) {
switch (newServerMsg.sessionType) {
case SessionType.Single:
return (
newServerMsg.sendID === this.storeCurrentConversation.userID ||
(newServerMsg.sendID === this.storeCurrentUserID &&
newServerMsg.recvID === this.storeCurrentConversation.userID)
);
case SessionType.WorkingGroup:
return newServerMsg.groupID === this.storeCurrentConversation.groupID;
case SessionType.Notification:
return newServerMsg.sendID === this.storeCurrentConversation.userID;
default:
return false;
}
},
markConversationAsRead() {
IMSDK.asyncApi(
IMSDK.IMMethods.MarkConversationMessageAsRead,
IMSDK.uuid(),
this.storeCurrentConversation.conversationID
);
},
initStore() {
const _this = this;
this.$store.dispatch("user/getSelfInfo");
this.$store.dispatch("conversation/getConversationList");
this.$store.dispatch("conversation/getUnReadCount");
this.$store.dispatch("contact/getBlacklist");
this.$store.dispatch("contact/getRecvFriendApplications");
this.$store.dispatch("contact/getSentFriendApplications");
this.$store.dispatch("contact/getRecvGroupApplications");
this.$store.dispatch("contact/getSentGroupApplications");
this.$store.dispatch("contact/getFriendList");
this.$store.dispatch("circle/getFriendCircleInfo");
if(true !== this.handleArguments()){
uni.switchTab({
url: "/pages/conversation/conversationList/index?isRedirect=true",
complete() {
_this.keppAlive();
_this.checkUpdate();
},
fail(e){
console.log(e);
}
});
}
},
// 验证是否升级
checkUpdate() {
const _this = this;
checkUpgrade();
},
keppAlive(){
// #ifdef APP-NVUE
uni.requestPermissions(['android.permission.RECEIVE_BOOT_COMPLETED'], (result) => {
if (result.granted) {
console.log('权限已获得');
} else {
console.log('权限被拒绝');
uni.showModal({
title: '权限申请',
content: '您需要授权后台运行权限才能正常使用该功能',
showCancel: false
});
}
});
// #endif
},
handleArguments(){
var args= plus.runtime.arguments;
if(args){
if(args.startsWith('shunliao://')){
console.log(args);
return ;
}
if(args.startsWith('{')){
const json = JSON.parse(args);
if(json.type == 'msg'){
if(this.inCurrentConversation(json.data)){
}else{
let conversation = this.storeConversationList.find((item) => item === json.data);
if(conversation){
plus.navigator.closeSplashscreen();
prepareConversationState(conversation);
return true;
}
}
}
return ;
}
// 处理args参数,如直达到某新页面等
console.log(args);
}
}
}
};
</script>
<style lang="scss">
@import "@/static/imfont/iconfont.css";
/*每个页面公共css */
text,view{
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol";
}
@import "@/uni_modules/uview-ui/index.scss";
@import "@/styles/login.scss";
@import "@/styles/global.scss";
uni-page-body {
height: 100vh;
overflow: hidden;
}
.uni-tabbar .uni-tabbar__icon {
width: 28px !important;
height: 28px !important;
}
</style>
+123
View File
@@ -0,0 +1,123 @@
```
安卓App Key:7105a186f961b5d418914c5d1b12f6af
n1e5a6s6m7
```
```
conversation详情
{
"conversationID": "si_100003_100004",
"conversationType": 1,
"userID": "100004",
"groupID": "",
"showName": "1111",
"faceURL": "/static/img/avatar.png",
"recvMsgOpt": 2,
"unreadCount": 0,
"groupAtType": 0,
"latestMsg": "{\"clientMsgID\":\"748ac3ce532afd7dc6638ee13154d5f4\",\"serverMsgID\":\"00ba977273b73af7ae8be68e70b24100\",\"createTime\":1764798647940,\"sendTime\":1764798644531,\"sessionType\":1,\"sendID\":\"100004\",\"recvID\":\"100003\",\"msgFrom\":100,\"contentType\":101,\"senderPlatformID\":2,\"senderNickname\":\"131****1111\",\"senderFaceUrl\":\"/static/img/avatar.png\",\"seq\":17,\"isRead\":true,\"status\":2,\"attachedInfo\":\"null\",\"textElem\":{\"content\":\"馃槆馃槆馃槆馃槆馃槆馃槆馃槆馃槆馃槆馃槆馃槆馃槆馃槆馃槆馃槆馃槆馃槆馃槆馃槆馃槆馃槆\"},\"attachedInfoElem\":{\"groupHasReadInfo\":{\"hasReadCount\":0,\"groupMemberCount\":0},\"isPrivateChat\":false,\"burnDuration\":0,\"hasReadTime\":0,\"isEncryption\":false,\"inEncryptStatus\":false}}",
"latestMsgSendTime": 1764798644531,
"draftText": "",
"draftTextTime": 0,
"isPinned": true,
"isPrivateChat": false,
"burnDuration": 0,
"isNotInGroup": false,
"updateUnreadCountTime": 0,
"attachedInfo": "",
"ex": "",
"maxSeq": 0,
"minSeq": 0,
"msgDestructTime": 0,
"isMsgDestruct": false
}
```
```
group detail
{
"groupID": "1793688611",
"groupName": "5676uy",
"notification": "",
"introduction": "",
"faceURL": "",
"createTime": 1764103081757,
"status": 0,
"creatorUserID": "100003",
"groupType": 2,
"ownerUserID": "100003",
"memberCount": 3,
"ex": "",
"attachedInfo": "",
"needVerification": 0,
"lookMemberInfo": 0,
"applyMemberFriend": 0,
"notificationUpdateTime": 0,
"notificationUserID": ""
}
```
```
group member
{
"groupID": "1793688611",
"userID": "100004",
"nickname": "131****1111",
"faceURL": "/static/img/avatar.png",
"roleLevel": 20,
"joinTime": 1764103081761,
"joinSource": 2,
"inviterUserID": "100003",
"muteEndTime": 0,
"operatorUserID": "100003",
"ex": "",
"attachedInfo": ""
}
```
```
user detail
{
"userID": "100003",
"nickname": "131****2222",
"faceURL": "/static/img/avatar.png",
"createTime": 1764100199726,
"ex": "",
"attachedInfo": "",
"globalRecvMsgOpt": 0,
"id": 100003,
"role_id": 0,
"parent_id": null,
"group_id": 0,
"username": "13122222222",
"sex": "1",
"email": null,
"region": "86",
"mobile": "13122222222",
"level": 1,
"birthday": null,
"bio": null,
"money": "0.0000000000",
"score": 0,
"currency1": "0.0000000000",
"currency2": "0.0000000000",
"currency3": "0.0000000000",
"currency4": "0.0000000000",
"currency5": "0.0000000000",
"currency6": "0.0000000000",
"currency7": "0.0000000000",
"currency8": "0.0000000000",
"currency9": "0.0000000000",
"maxsuccessions": 0,
"successions": 0,
"loginfailure": 0,
"prev_time": null,
"last_time": 1764833943,
"last_ip": "139.202.159.214",
"join_time": 1764100199,
"join_ip": "139.202.158.3",
"token": null,
"invite_code": "WBDFAQSL",
"online": 0,
"status": 1,
"created_at": 1764100199,
"updated_at": 1764833943,
"deleted_at": null
}
```
BIN
View File
Binary file not shown.
+187
View File
@@ -0,0 +1,187 @@
import config from "@/common/config";
// 登录
export const businessConfig = (params) =>
uni.$u?.http.post("/common/init", JSON.stringify(params));
// 验证是否升级
export const checkUpgrade = (params) =>{
const _this = this;
return new Promise((resolve,reject)=>{
let system = uni.getSystemInfoSync()
plus.runtime.getProperty(plus.runtime.appid, function(inf) {
uni.$u?.http.post("/common/checkUpgrade", JSON.stringify({
version:system.appVersion,
platform:system.platform,
version_wgt:inf.versionCode,
})).then(res=>{
console.log(res);
if(!res || !res.version){
return reject(true);
}
let skip_version = uni.getStorageSync('skip_version')
//console.log(res.version,skip_version);
if(res && res.version!=skip_version){
uni.$emit('closeWebview')
uni.setStorageSync('upgrade_model',res)
uni.navigateTo({
url: '/pages/common/upgrade',
animationType:"fade-in",
complete(res1) {
//console.log(res1);
return resolve();
}
});
}
reject();
}).catch(e=>{
reject(e);
})
});
});
};
export const businessLogin = (params) =>
uni.$u?.http.post("/common/login", JSON.stringify(params));
export const businessSendSms = (params) =>
uni.$u?.http.post("/common/captcha", JSON.stringify(params));
export const businessVerifyCode = (params) =>
uni.$u?.http.post("/common/verify_captcha", JSON.stringify(params));
export const businessRegister = (params) =>
uni.$u?.http.post("/common/register", JSON.stringify(params));
export const businessReset = (params) =>
uni.$u?.http.post("/common/resetpwd", JSON.stringify(params));
export const businessModify = (params) =>
uni.$u?.http.post(
"/user/change_password",
JSON.stringify({
...params,
}), {
header: {
token: uni.getStorageSync("BusinessToken"),
},
}
);
// 用户信息
export const businessInfoUpdate = (params) =>
uni.$u?.http.post(
"/user/profile",
JSON.stringify({...params}),
{
header: {
token: uni.getStorageSync("BusinessToken"),
},
}
);
export const businessGetUserInfo = (userID) =>
uni.$u?.http.post(
"/user/find",
JSON.stringify({userIDs: [userID],}),
{
header: {
token: uni.getStorageSync("BusinessToken"),
},
}
);
export const businessSearchUserInfo = (keyword,searchtype) =>
uni.$u?.http.post(
"/user/search",
JSON.stringify({
keyword,
searchtype:(searchtype? searchtype : 'id'),
page: 1,
limit: 10
}), {
header: {
token: uni.getStorageSync("BusinessToken"),
},
}
);
export const businessSearchUser = (keyword,searchtype) =>
uni.$u?.http.post(
"/friend/search",
JSON.stringify({
keyword,
searchtype:(searchtype? searchtype : 'id'),
page: 1,
limit: 99,
}), {
header: {
token: uni.getStorageSync("BusinessToken"),
},
}
);
export const getSpage = (name) => uni.$u?.http.get(`/article/singpage?name=${name}`);
export const getArticle = (id) => uni.$u?.http.get(`/article/detail?id=${id}`);
export const getFriendCircle = (page=1,limit=10) =>{
return uni.$u?.http.get(`/friendcircle/list?limit=${limit}&page=${page}`);
}
export const getFriendCircleNewcount = () =>{
return uni.$u?.http.get("/friendcircle/newcount");
}
export const getFriendCircleInfo = () =>{
return uni.$u?.http.get("/friendcircle/info");
}
export const upload = (files,data,onProgress) =>{
if(typeof data == 'function'){
onProgress = data;
data = {};
}
data.data = data.data ? data.data : {};
let headers = {};
if(data.token){
headers = data.headers;
delete data.headers;
}
headers = {
...headers,
client:uni.getSystemInfoSync().osName,
token:uni.getStorageSync("BusinessToken"),
operationID: (Math.random() * 36).toString(36).slice(2) + new Date().getTime().toString(),
// #ifdef APP-PLUS
ClientVersion:plus.runtime.versionCode,
// #endif
// #ifndef APP-PLUS
ClientVersion:350,
// #endif
}
console.log(typeof files);
let url = "/user/upload";
if(data.url){
url = data.url;
delete data.url;
}
url= config.getRegisterUrl()+url;
return new Promise((resolve,reject)=>{
var u = uni.uploadFile({
url: url,
filePath: files,
//files:files.length > 1 ? files : files[0],
name: "file",
formData:data,
header:headers,
success({data,errMsg}){
console.log(data);
data = JSON.parse(data);
if(data.code == 0){
resolve(data);
}else{
reject(data.msg);
}
},
fail(res) {
console.log(e);
reject(e);
}
});
u.onProgressUpdate((e)=>{
var res = {
'code' : 99999,
'progress' : e.progress
}
onProgress && onProgress.call(this,res);
})
})
}
-125
View File
@@ -1,125 +0,0 @@
<?php
namespace app\api\controller;
use support\Request;
use app\model\Address as AddressModel;
use hg\apidoc\annotation as Apidoc;
/**
* 提现地址
* @Apidoc\NotParse()
* @Apidoc\NotDebug()
*/
class AddressController extends BaseController{
/**
* 不需要鉴权的方法
* @var array
*/
public $noNeedAuth = ['*'];
/**
* 无需登录及鉴权的方法
* @var array
*/
public $noNeedLogin = [];
/**
* 列表
* @Apidoc\Method("POST")
* @Apidoc\Query("network", type="string", require=true, desc="网络")
* @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);
$page = (int)input('page',1);
$status = (int)input('status',-1);
$network = input('network');
$type = input('type');
$model = AddressModel::where('user_id',\support\Jwt\JwtToken::getCurrentId());
//->where('network','BEP-20');
if($type){
$model = $model->where('status',1);
}
if($network){
$model = $model->where('network',$network);
}
$list = $model->paginate($limit);
return $this->success(__('successful'),$list->toArray());
}
/**
* 创建
* @Apidoc\Method("POST")
* @Apidoc\Param("network", type="string", require=true, desc="网络,BEP-20,TRC-20,ALIPAY,WECHAT",default="BEP-20")
* @Apidoc\Param("address", type="string", require=true, desc="地址")
* @Apidoc\Param("title", type="string", require=true, desc="名称")
* @Apidoc\Param("status", type="string", require=true, desc="状态,可选,1,0,默认1")
*/
public function create()
{
//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);
//* @Apidoc\Param("trade_password", type="string", require=true, desc="交易密码")
$data = [
'network' => input('network','BEP-20'),
'title' => input('title'),
'address' => input('address'),
'img' => input('img'),
'is_default' => input('is_default'),
'status' => input('status',0),
'user_id' => \support\Jwt\JwtToken::getCurrentId()
];
if(!$data['title']){
return $this->error(__('Invalid title'));
}
if(!$data['address']){
return $this->error(__('Invalid address'));
}
AddressModel::create($data);
return $this->success(__('successful'));
}
/**
* 编辑
* @Apidoc\Method("POST")
* @Apidoc\Param("id", type="string", require=true, desc="id")
* @Apidoc\Param("title", type="string", require=true, desc="名称")
* @Apidoc\Param("status", type="string", require=true, desc="状态,可选,1,0,默认1")
*/
public function update()
{
//captcha_verify('image','update_address');
//$trade_password = input('trade_password');
//\support\Jwt::verify_trade_password($trade_password);
$data = [
'id' => input('id',''),
'title' => input('title',''),
//'is_default' => input('is_default'),
'status' => input('status',1),
];
if(!$data['id']){
return $this->error(__('Invalid parameters'));
}
if(!$data['title']){
return $this->error(__('Invalid title'));
}
AddressModel::where('id',$data['id'])->save($data);
return $this->success(__('successful'));
}
/**
* 详情
* @Apidoc\Query("id", type="int", require=true, desc="id")
*/
public function detail(){
$appid = input('id');
$vo = AddressModel::where('id',$appid)->find();
if($vo) {
return $this->success(__('successful'),$vo->toArray());
}else{
return $this->error(__("Address is not exist"));
}
}
}
-110
View File
@@ -1,110 +0,0 @@
<?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');
}
}
-250
View File
@@ -1,250 +0,0 @@
<?php
namespace app\api\controller;
use app\model\Archives as ArchivesModel;
use app\model\Content;
use support\Request;
use hg\apidoc\annotation as Apidoc;
use support\Jwt\JwtToken;
use support\think\Db;
/**
* 文章模块
*/
class ArticleController extends BaseController
{
public $noNeedLogin = ['*'];
private const CACHE_PREFIX_READ = 'article_read_';
private const CACHE_TTL = 86400;
/**
* 列表
* @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);
$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 = $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());
}
/**
* faq
* @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);
$list = ArchivesModel::alias('a')
->join('content c', 'a.id = c.id')
->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()
{
$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()
{
$id = (int)input('id');
$name = input('name');
$vo = null;
if ($name) {
$vo = ArchivesModel::where('name', $name)->find();
} elseif ($id) {
$vo = ArchivesModel::where('id', $id)->find();
}
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()
{
$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' => ''],
];
return $this->success(__('successful'), $list);
}
/**
* 设为已读
* @Apidoc\Query("id", type="int", require=true, desc="ID,多个逗号隔开")
*/
public function mask_as_read()
{
$ids = input('id');
$user_id = $this->getCurrentUserId();
if (!$user_id) {
return $this->success(__('successful'));
}
$ids = array_filter(explode(',', $ids));
foreach ($ids as $id) {
$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;
}
}
@@ -1,53 +0,0 @@
<?php
namespace app\api\controller;
use app\model\User as UserModel;
use support\Request;
use hg\apidoc\annotation as Apidoc;
/**
* 余额日志
*/
class BalanceLogController extends BaseController{
/**
* 不需要鉴权的方法
* @var array
*/
public $noNeedAuth = ['*'];
/**
* 无需登录及鉴权的方法
* @var array
*/
public $noNeedLogin = [];
/**
* 余额日志
* @Apidoc\Query("currency", type="string", require=true, desc="货币",default="money")
* @Apidoc\Query("type", type="string", require=true, desc="类型")
* @Apidoc\Query("startTime", type="string", require=true, desc="开始时间")
* @Apidoc\Query("endTime", type="string", require=true, 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){
$user_id = \support\Jwt\JwtToken::getCurrentId();
$type = Input('type',0);
$currency = Input('currency','money');
$startTime = Input('startTime');
$endTime = Input('endTime');
$list = \app\model\BalanceLog::queryLogs($user_id,$currency,$type,$startTime,$endTime);
$BalanceTypeList= \app\enum\BalanceType::toArray();
$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 = \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);
}
}
-123
View File
@@ -1,123 +0,0 @@
<?php
namespace app\api\controller;
use support\Request;
use support\Response;
use Shopwwi\WebmanFilesystem\FilesystemFactory;
use Shopwwi\WebmanFilesystem\Facade\Storage;
use hg\apidoc\annotation as Apidoc;
use Tinywan\Validate\Facade\Validate;
/**
* 基础控制器
* @Apidoc\NotParse()
* @Apidoc\NotDebug()
*/
class BaseController
{
/**
* 不需要鉴权的方法
* @var array
*/
public $noNeedAuth = [];
/**
* 无需登录及鉴权的方法
* @var array
*/
public $noNeedLogin = [];
function __construct()
{
$this->_init();
}
protected function _init(){
}
/**
* 返回格式化json数据
*
* @param int $code
* @param string $msg
* @param array $data
* @return Response
*/
protected function json(int $code, string $msg = 'ok', array|object|null $data = []): Response
{
return json(['code' => $code, 'data' => $data, 'msg' => __($msg)]);
}
protected function success(string $msg = '成功', array|object|null $data = []): Response
{
return $this->json(0, $msg, $data);
}
protected function fail(string $msg = '失败', array|object|null $data = []): Response
{
return $this->json(1,$msg, $data);
}
protected function error(string $msg = '失败', array|object|null $data = []): Response
{
return $this->json(1,$msg, $data);
}
protected function _upload($request)
{
try{
$user = \support\Jwt::getUser();
}catch(\Exception $e){
$user = ['id'=>0];
}
$savePath = $request->post('savePath','files');
$validate = Validate::rule('savePath', 'alphaNum');
$data = ['savePath' => $savePath];
if (!$validate->check($data)) {
return '参数错误:'.$validate->getError();
}
$savePath = trim($savePath,'/');
//$savePath = 'upload/'.$savePath.'/'.$user['id'];
$savePath = $savePath.'/'.$user['id'];
\support\Log::alert('savePath:'.$savePath);
$mimetype = explode(',',Config('site.upload_mimetype'));
$maxsize = Config('site.upload_maxsize')*1024*1024;
//多文件上传
$files = $request->file();
$result = Storage::adapter('oss')
->path($savePath)
->size($maxsize)
->extYes($mimetype)
->uploads($files,0,$maxsize * count($files),false);
$save_datas = [];
foreach($result as $k=>$fileinfo){
$save_datas[] = [
'user_id' => $user['id'],
'category' => 'default',
'adapter' => $fileinfo->adapter,
'origin_name' => $fileinfo->origin_name,
'file_name' => $fileinfo->file_name,
'size' => $fileinfo->size,
'mime_type' => $fileinfo->mime_type,
'extension' => $fileinfo->extension,
'file_height' => $fileinfo->file_height,
'file_width' => $fileinfo->file_width,
'file_url' => $fileinfo->file_url,
'sha1' => $fileinfo->storage_key ?:sha1_file(public_path($fileinfo->file_name)),
'use_count' => 0,
];
}
$res = \app\model\Files::saveAll($save_datas);
return $res;
}
/**
* @Apidoc\Title("上传")
* @Apidoc\Method("POST")
* @Apidoc\NotParse()
* @Apidoc\NotDebug()
*/
function upload(Request $request,$return = false)
{
$res = $this->_upload($request);
if(is_string($res)){
return $this->fail( $res);
}
return $this->success(__('successful'),$res);
}
}
-124
View File
@@ -1,124 +0,0 @@
<?php
namespace app\api\controller;
use app\model\Card as CardModel;
use app\model\Cdkey as CdkeyModel;
use app\model\User as UserModel;
use support\think\Db;
use hg\apidoc\annotation as Apidoc;
/**
* 卡密模块
* @Apidoc\NotParse()
* @Apidoc\NotDebug()
*/
class CardController extends BaseController{
/**
* 不需要鉴权的方法
* @var array
*/
public $noNeedAuth = ['*'];
public $noNeedLogin = ['detail','list'];
/**
* 列表
* @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)
*/
public function list(){
$limit = (int)input('limit',10);
$kw = (string)input('kw');
$model = CardModel::where('status',1);
if($kw){
$model = $model->whereLike('title',$kw);
}
$list = $model->order('id desc')->paginate($limit);
return $this->success(__('successful'),$list->toArray());
}
/**
* 详情
* @Apidoc\Query("id", type="int", require=true, desc="ID")
* @Apidoc\Query("status", type="string", require=true, desc="状态")
* @Apidoc\Query("page", type="int", require=true, desc="页码",default=1)
* @Apidoc\Query("limit", type="int", require=true, desc="分页大小",default=10)
*/
public function detail(){
$limit = (int)input('limit',10);
$id = (int)input('id',0);
$status = (string)input('status','all');
$model = CdkeyModel::where('category_id',$id);
if($status!='all'){
$model = $model->where('status',$status);
}
$list = $model->order('id desc')->paginate($limit);
return $this->success(__('successful'),$list->toArray());
}
/**
* 详情
* @Apidoc\Query("id", type="int", require=true, desc="ID")
*/
public function download(){
$id = (int)input('id',0);
$model = CdkeyModel::where('category_id',$id);
$list = $model->order('id desc')->select();
return $this->success(__('successful'),$list->toArray());
}
/**
* 购买产品
* @Apidoc\Method("POST")
* @Apidoc\Param("title", type="string", require=true, desc="标题")
* @Apidoc\Param("total", type="int", require=true, desc="数量")
* @Apidoc\Param("days", type="int", require=true, desc="金额")
* @Apidoc\Param("trade_password", type="string", require=true, desc="交易密码")
*/
public function create()
{
$user = \support\Jwt::getUser();
$data = [
'user_id' => $user->id,
'title'=> input('title'),
'total' => (int)input('total'),
'days' => input('days'),
'status' => 0
];
if(!$data['title']){
return $this->error(__('Incorrect title'));
}
/** @var CardModel $gift */
if(!in_array($data['days'],[100,200,300,500])){
return $this->error(__('Incorrect amount'));
}
if($data['total'] < 1){
return $this->error(__('Incorrect quantity'));
}
$amount = $data['days'] * $data['total'];
$data['user_id'] = $user->id;
$data['amount'] = $amount;
$data['type'] = 'active';
$data['expires'] = strtotime('2030-12-31');
//验证交易密码
$trade_password = input('trade_password');
\support\Jwt::verify_trade_password($trade_password);
//var_dump($user);
//验证余额
if($data['amount'] < 1){
return $this->error(__('Incorrect amount'));
}
if($data['amount'] > $user->score){
return $this->error(__('Insufficient balance'));
}
Db::startTrans();
try{
$data = CardModel::create($data);
UserModel::score($data['user_id'],-$data['amount'],\app\enum\BalanceType::CDKEY,$data['id']);
Hook('card.create',$data);
Db::commit();
}catch(\Exception $e){
Db::rollback();
return $this->error($e->getMessage());
}
return $this->success(__('successful'));
}
}
-106
View File
@@ -1,106 +0,0 @@
<?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('删除成功');
}
}
-553
View File
@@ -1,553 +0,0 @@
<?php
namespace app\api\controller;
use Tinywan\Validate\Facade\Validate;
use app\model\User as UserModel;
use support\Request;
use support\Response;
use Webman\Captcha\CaptchaBuilder;
use Webman\Captcha\PhraseBuilder;
use Shopwwi\WebmanFilesystem\FilesystemFactory;
use Shopwwi\WebmanFilesystem\Facade\Storage;
use hg\apidoc\annotation as Apidoc;
use think\facade\Db;
/**
* 公共接口
*/
class CommonController extends BaseController{
/**
* 不需要鉴权的方法
* @var array
*/
public $noNeedAuth = [];
/**
* 无需登录及鉴权的方法
* @var array
*/
public $noNeedLogin = ['*'];
/**
* 加载初始化
*
* @Apidoc\Query("version", type="string", require=true, desc="版本号")
*/
public function init(Request $request)
{
$lang = $request->header('lang','zh-Hans');
locale( $lang);
$config = Config('site');
$disallowFields = [
'api_token','reward_time_limit',
'mail_type','mail_smtp_host','mail_smtp_port','mail_smtp_user','mail_smtp_pass','mail_verify_type','mail_from',
'attachment_category','categorytype','cdkey_category','configgroup','flagtype',
'languages','forbiddenip','fixedpage','admin_login_captcha',
'upload_mimetype','upload_multipart','upload_multiple','upload_thumbstyle','upload_previewtpl','upload_timeout','upload_maxsize',
'yeji_jicha_reward','suanli_rate','agent_expirs_retention','allow_currencys','allow_currency_logs',
'agent_commission_total_rate','agent_commission_layer_rate','differential_commission_total_rate'
];
$config = array_diff_key($config, array_flip($disallowFields));
if(Request()->client != "web"){
$config["steps"] = Config('step');
}
$config['balance_type_list'] = \app\enum\BalanceType::toArray();
$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);
}
/**
* 验证是否升级
*/
public function checkUpgrade(Request $request)
{
$field = 'id,type,force,source,version,content';
$verUpdate = new \app\model\Version;
$version = Input('version');
$platform = Input('platform');
$version_wgt = Input('version_wgt');
// 查询整包、外链数据
$update_data = $verUpdate->whereIn('type','0,2')
->where('status',1)
->where('version','>', $version)
->where('platform',$platform)
->field($field)
->order('id desc')->find();
if($update_data) {
return $this->success('',$update_data);
}
// 查询WGT数据
$update_wgt_data = $verUpdate->where('type',1)
->where('status',1)
->where('version_wgt','>', $version_wgt)
->where('platform',$platform)
->field($field)->order('id desc')->find();
if($update_wgt_data) {
return $this->success('',$update_wgt_data);
}
return $this->success('',[]);
}
/**
* 注册会员
*
* @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="推荐码")
* @Apidoc\Param("code", type="string",require=true, desc="邮箱验证码,event=register")
*/
public function register()
{
$email = input('email');
$password = input('password');
$trade_password= input( 'trade_password');
$username = input('username');
$mobile = input('mobile');
$invite_code = input('invite_code');
$type = input('type');
if (!in_array($type,Config('site.user_register_way')) ) {
return $this->error(__('Unknown register way'));
}
if ($type == 'email') {
if(!$email || !Validate::is($email, "email")){
return $this->error(__('Email is incorrect'));
}
$username = $email;
unset($mobile);
captcha_verify('email','register',$email,false);
}
if ($type == 'mobile') {
if(!$mobile || !Validate::regex($mobile, "^1\d{10}$")){
return $this->error(__('Mobile is incorrect'));
}
$username = $mobile;
unset($email);
captcha_verify('mobile','register',$mobile,false);
}
if ($type == 'username') {
if(!$username){
return $this->error(__('Username is incorrect'));
}
}
if (!$password) {
return $this->error(__('Invalid parameters'));
}
// if (!$trade_password) {
// return $this->error(__('Invalid trade password'));
// }else{
// $extends['trade_password'] = \plugin\admin\app\common\Util::passwordHash($trade_password);
// }
//邀请码
//$invite_code = 'TEAJXLEE';
$region = Input('region','+86');
$region = str_replace('+','',$region);
$extends = [
'role_id' => 1,
'group_id' => 0,
'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){
//系统生产的一次性推荐吗
$inviteModel = \app\model\Invitecode::where('code',$invite_code)->find();
if(!$inviteModel){
return $this->error(__('错误的邀请码'));
}
$extends['group_id'] = 2;
$extends['role_id'] = 1;
$extends['parent_id'] = 0;
}else{
$inviter_user = UserModel::where('invite_code',$invite_code)->field('group_id,id')->find();
if(!$inviter_user){
return $this->error(__('Invalid invite code'));
}
$extends['parent_id'] = $inviter_user['id'];
}
}else{
//return $this->error(__('Invalid invite code'));
}
// validate(\app\validate\User::class)
// ->scene('edit')
// ->check([
// 'name' => 'thinkphp',
// 'email' => 'thinkphp@qq.com',
// ]);
try {
$user = \support\Jwt::register($username, $password, $email, $mobile, $extends);
if($inviteModel){
$inviteModel->status = 1;
$inviteModel->save();
}
$data = ['userinfo' => $user];
// if ($type == 'email') {
// captcha_verify('email','register',$email,true);
// }else if ($type == 'mobile') {
// captcha_verify('mobile','register',$mobile,true);
// }else{
// captcha_verify('image','register',$mobile,true);
// }
return $this->success(__('Sign up successful'), $data);
} catch (\Exception $e) {
return $this->error($e->getMessage());
}
}
/**
* 登录
* @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){
$username = input('username');
$mobile = input('mobile');
$email = input('email');
$password = input('password');
$type = input('type');
if($type == 'mobile'){
if (!$mobile ) {
return $this->fail(__('Invalid username or password'));
}
$username = $mobile;
}else if($type == 'email'){
if (!$email ) {
return $this->fail(__('Invalid username or password'));
}
$username = $email;
}else{
if (!$username ) {
return $this->fail(__('Invalid username or password'));
}
}
try{
if ($password) {
//return $this->fail(__('Invalid username or password'));
$user = \support\Jwt::login($username, $password,$type);
}else{
$user = \support\Jwt::login($username, $password,$type,'code');
}
if($user === false){
return $this->fail(\support\Jwt::getError());
}
//登录成功的事件
$user = Hook("user.login_successed", $user);
return $this->success(__('successful'), $user[0]);
} catch (\Exception $e) {
return $this->error($e->getMessage());
}
}
/**
* 退出登录
* @Apidoc\Method("GET")
*/
public function logout(){
\support\Jwt::logout();
return $this->success(__('successful'));
}
/**
* 重置密码
*
* @Apidoc\Method ("POST")
* @Apidoc\Param("email", type="string",require=true, desc="邮箱")
* @Apidoc\Param("mobile", type="string",require=true, desc="手机号")
* @Apidoc\Param("newpassword", type="string",require=true, desc="新密码")
* @Apidoc\Param("code", type="string",require=true, desc="邮箱验证码,event=resetpwd")
*/
public function resetpwd()
{
$email = input("email");
$mobile = input("mobile");
$newpassword = input("newpassword");
if (!$newpassword) {
return $this->error(__('Invalid parameters'));
}
//验证Token
if (!Validate::check(['newpassword' => $newpassword], ['newpassword' => 'require|regex:\S{6,32}'])) {
return $this->error(__('Password must be 6 to 32 characters'));
}
if (!$mobile && !$email){
try{
$user = \support\Jwt::getUser();
}catch(\Exception $e){
$user = false;
}
if($user){
captcha_verify('mobile','reset_pwd',$user->mobile);
}
}else{
if ($mobile && Validate::regex($mobile, "^1\d{10}$")) {
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_verify('email','reset_pwd',$email);
$user = UserModel::getByEmail($email);
}
}
if (!$user) {
return $this->error(__('Invalid parameters'));
}
//模拟一次登录,需不需要充值登录信息?????
//\support\Jwt::direct($user->id);
try{
UserModel::where('id',$user->id)->save([
'loginfailure' => 0,
'password' => \plugin\admin\app\common\Util::passwordHash($newpassword)
]);
} catch (\Exception $e) {
return $this->error($e->getMessage());
}
return $this->success(__('Reset password successful'));
}
/**
* 重置交易密码
*
* @Apidoc\Method ("POST")
* @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'));
}
//验证Token
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){
$user = false;
}
if($user){
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_verify('mobile','reset_trade_pwd',$mobile);
$user = UserModel::getByMobile($mobile);
}elseif ($email && Validate::is($email, "email")) {
captcha_verify('email','reset_trade_pwd',$email);
$user = UserModel::getByEmail($email);
}
}
if (!$user) {
return $this->error(__('Invalid parameters'));
}
//模拟一次登录,需不需要充值登录信息?????
//\support\Jwt::direct($user->id);
try{
UserModel::where('id',$user->id)->save([
'trade_password' => \plugin\admin\app\common\Util::passwordHash($newpassword)
]);
} catch (\Exception $e) {
return $this->error($e->getMessage());
}
return $this->success(__('Reset Trade password successful'));
}
/**
* 验证码
* @Apidoc\Method ("POST")
* @Apidoc\Param("type", type="string",require=true, desc="GET参数,类型,email:邮箱验证码,image:图片验证码")
* @Apidoc\Param("event", type="string",require=true, desc="事件,regiser:注册,resetpwd:重置密码,withdrawl:提现")
* @Apidoc\Param("email", type="string",require=true, desc="邮箱,可选")
*/
public function captcha(Request $request){
$debug = false;
$request->input('type');
$type = $request->input('type');
$event = $request->input('event');
if($type == 'email'){
$email = $request->input('email');
if(!$email){
try {
$user = \support\Jwt::getUser();
$email = $user->email;
} catch (\Exception $th) {
return $this->error(__('Invalid parameter'));
}
}
$key = 'captcha_'.$event.'_'.$email;
$list = cache($key);
$list = $list ?:[];
$expris = 60;
if(cache('?exp_'.$key)){
if(cache('exp_'.$key)+$expris > time()){
return $this->fail(__('Only one verification code can be sent within %second% seconds',['%second%'=>$expris]));
}
}
$code =\support\Random::numeric(6);
$list[$code] = time();
cache($key,$list);
cache('exp_'.$key,time());
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 : ''
]);
}elseif($type == 'mobile'){
$mobile = $request->input('mobile');
if(!$mobile){
try {
$user = \support\Jwt::getUser();
$mobile = $user->mobile;
} catch (\Exception $th) {
return $this->error(__('Invalid parameter'));
}
}
if (!Validate::regex($mobile, "^1\d{10}$")) {
return $this->error(__('Mobile is incorrect'));
}
$key = 'captcha_'.$event.'_'.$mobile;
$list = cache($key);
$list = $list ?:[];
$expris = 300;
if(cache('?exp_'.$key)){
if(cache('exp_'.$key)+$expris > time()){
return $this->fail(__('Only one verification code can be sent within %second% seconds',['%second%'=>$expris]));
}
}
$code =\support\Random::numeric(6);
$list[$code] = time();
cache($key,$list);
cache('exp_'.$key,time());
addJob([
'mobile' => $mobile,
'event' => $event,
'code' => $code
],'Sms');
\support\Log::channel('mail')->alert("短信验证码:".$code.',手机号:'.$mobile);
return $this->success(__('SMS sent successfully'),[
'code'=> $debug ? $code : ''
]);
}else{
//TODO 图像验证码没有唯一的KEY
$key = 'captcha_'.$event.'_';
//abcdefghjkmnpqrstuvwxyzABCDEFGHJKMNPQRSTUVWXYZ
$builder = new PhraseBuilder(4, '0123456789');
$captcha = new CaptchaBuilder(null, $builder);
$captcha->build(120);
$code = strtolower($captcha->getPhrase());
$list[$code] = time();
cache($key,$list);
if($request->method() =='GET'){
$img_content = $captcha->get();
return response($img_content, 200, ['Content-Type' => 'image/jpeg']);
}else{
$img_content = $captcha->inline();
return json([
'code' => 0,
'msg' => __('successful'),
'data' => $img_content
]);
}
}
}
/**
* 校验验证码
* @Apidoc\Param("type", type="string",require=true, desc="GET参数,类型,email:邮箱验证码,image:图片验证码")
* @Apidoc\Param("event", type="string",require=true, desc="事件,register:注册,resetpwd:重置密码,withdrawl:提现")
* @Apidoc\Param("email", type="string",require=false, desc="邮箱,可选,仅type==email时必填")
* @Apidoc\Param("code", type="string",require=true, desc="验证码")
*/
public function verify_captcha(Request $request): Response
{
$type = $request->input('type');
$email = $request->post('email');
$mobile = $request->input('mobile');
$event = $request->post('event');
try {
if($type == 'email'){
$result = captcha_verify('email', $event , $email,false);
}elseif($type == 'mobile'){
$result = captcha_verify('mobile', $event , $mobile,false);
}else{
$result = captcha_verify('image', $event , '',false);
}
if(!$result){
return $this->fail(__('Captcha is incorrect'));
}
} catch (\Exception $e) {
return $this->fail($e->getMessage());
}
return $this->success(__('successful'));
}
}
-81
View File
@@ -1,81 +0,0 @@
<?php
namespace app\api\controller;
use app\model\User as UserModel;
use app\model\UserRemark as UserRemarkModel;
use app\model\GroupRemark as GroupRemarkModel;
use support\Request;
use support\Response;
use support\think\Db;
use hg\apidoc\annotation as Apidoc;
/**
* 通讯录
*/
class ContactController extends BaseController{
/**
* 不需要鉴权的方法
* @var array
*/
public $noNeedAuth = ['*'];
/**
* 无需登录及鉴权的方法
* @var array
*/
public $noNeedLogin = [];
/**
* @Apidoc\Title("获取用户好友列表")
* @Apidoc\Method("GET")
*/
function get_friend_list(Request $request): Response
{
$current_user = \support\Jwt::getUser();
$user_id = $current_user->id;
$userID = \support\Encrypt::userIDencode($user_id);
$res = $request->IM->friend()->getFriendList($userID,1,10000);
return $this->success('ok',$res['data']['friendsInfo']);
}
/**
* @Apidoc\Title("好友信息")
* @Apidoc\Method("GET")
* @Apidoc\Param("userID", type="string",require=true, desc="用户ID")
*/
function get_friend_info(Request $request): Response
{
$userID = Input('userID');
if(!$userID){
return $this->error('UserID is Empty');
}
$res = \app\model\User::where('userID',$userID)->find();
return $this->success('ok',$res);
}
/**
* @Apidoc\Title("批量查询好友信息")
* @Apidoc\Method("GET")
* @Apidoc\Param("userIDs", type="string",require=true, desc="用户ID列表,逗号分隔")
*/
function get_friends_info(Request $request): Response
{
$userIDs = Input('userIDs');
if(!$userIDs){
return $this->error('UserID is Empty');
}
$res = \app\model\User::whereIn('userID',$userIDs)->select();
return $this->success('ok',$res);
}
/**
* @Apidoc\Title("批量查询好友信息")
* @Apidoc\Method("GET")
* @Apidoc\Param("userIDs", type="string",require=true, desc="用户ID列表,逗号分隔")
*/
function get_friends_roles(Request $request): Response
{
$userIDs = Input('userIDs');
if(!$userIDs){
return $this->error('UserID is Empty');
}
$res = Db::name('user')->whereIn('userID',$userIDs)->column('role_id','userID');
return $this->success('ok',$res);
}
}
@@ -1,441 +0,0 @@
<?php
namespace app\api\controller;
use app\model\FriendCircle;
use Shopwwi\WebmanFilesystem\FilesystemFactory;
use Shopwwi\WebmanFilesystem\Facade\Storage;
use app\model\User as UserModel;
use app\model\Realname as RealnameModel;
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 FriendCircleController extends BaseController{
/**
* 不需要鉴权的方法
* @var array
*/
public $noNeedAuth = ['*'];
/**
* 无需登录及鉴权的方法
* @var array
*/
public $noNeedLogin = [];
/**
* 朋友圈设置
* @return void
*/
function info(Request $request): Response{
$user_id = Input('user_id');
if($user_id){
$user_id = \support\Encrypt::userIDDecode($user_id);
$json= [
'top_unread_items' =>[],
'unread_item_ids' =>[],
'unread_count' =>0,
'settings' => Db::name('user_extend')->where('user_id',$user_id)->findOrEmpty()
];
return $this->success('ok',$json);
}else{
$user = \support\Jwt::getUser();
if (!$user) {
return $this->fail('请先登录');
}
$user_id = $user->id;
$res = $this->newcount($request);
$response = $res->rawBody();
$json = json_decode($response,true);
$json['data']['settings'] = Db::name('user_extend')->where('user_id',$user_id)->findOrEmpty();
// [
// 'bg' => '',
// ];
$top_unread_items = FriendCircleModel::whereIn('id',$json['data']['unread_item_ids'])
->with(['user' => function($query) {
$query->field('id,nickname,avatar');
}])
->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("page", type="int", require=true, desc="页码",default=1)
* @Apidoc\Query("limit", type="int", require=true, desc="分页大小",default=10)
* @Apidoc\Query("user_id", type="int", require=false, desc="用户ID,不传则获取所有")
*/
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);
$user_id = Input('user_id', 0);
if($user_id){
$user_id = \support\Encrypt::userIDDecode($user_id);
}
$query = FriendCircleModel::where('status', 1)
->whereIn('user_id',$this->getFriendUserIds($current_user_id))
->with(['user' => function($query) {
$query->field('id,nickname,avatar');
}])
->order('created_at', 'desc');
// 如果指定了用户ID,只获取该用户的朋友圈
if ($user_id > 0) {
$query->where('user_id', $user_id);
}
$list = $query->paginate([
'list_rows' => $limit,
'page' => $page,
]);
if(!$user_id){
cache('circle_last_read_id_'.$current_user_id,$list[0]['id']);
}
// 处理每条朋友圈数据
$items = $list->items();
$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.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($item)use($current_user_id){
return $item['user_id'] == $current_user_id;
});
// FriendCircleLikeModel::where('circle_id', $item->id)
// ->where('user_id', $current_user_id)
// ->count() > 0;
}
// 获取评论列表(最新10条)
$comments = FriendCircleCommentModel::where('circle_id', $item->id)
->where('status', 1)
->with(['user' => function($query) {
$query->field('id,nickname,avatar');
}, 'replyUser' => function($query) {
$query->field('id,nickname,avatar');
}])
->order('created_at', 'asc')
->limit(10)
->select();
// 格式化数据
$item->is_liked = $is_liked;
$item->likes = $likes;
$item->comments = $comments;
// 处理图片URL
if (!empty($item->files)) {
$files = is_array($item->files) ? $item->files : json_decode($item->files, true);
if (is_array($files)) {
$item->files = array_map(function($file) {
return cdnurl($file);
}, $files);
} else {
$item->files = [];
}
} else {
$item->files = [];
}
// 处理用户头像
if ($item->user && $item->user->avatar) {
$item->user->avatar = cdnurl($item->user->avatar);
}
// 处理点赞用户头像
foreach ($item->likes as $like) {
if ($like->user) {
$like->avatar = cdnurl($like->avatar);
}
}
// 处理评论用户头像
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;
// 统计从上次查看时间到现在新增的朋友圈数量
$unread_item_ids = FriendCircleModel::where('status', 1)
->whereIn('user_id',$this->getFriendUserIds($user_id))
->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_user_id", type="int",require=false, desc="回复的用户ID(回复评论时使用)")
*/
function comment(Request $request): Response
{
$user = \support\Jwt::getUser();
if (!$user) {
return $this->fail('请先登录');
}
$body = $request->post('body', '');
$circle_id = (int)$request->post('id', 0);
$reply_user_id = (int)$request->post('reply_user_id', 0);
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('朋友圈动态不存在');
}
// 如果回复评论,检查被回复的用户是否存在
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')->where('id',$comment->user_id)->find();
$comment->replyUser=null;
if($comment->reply_user_id){
$comment->replyUser = Db::name('user')->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]);
} else {
// 添加点赞
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($user_id):array{
if (!$user_id) {
return [];
}
$cache_key = 'friend_id_list_'.$user_id;
$result = cache($cache_key) ?: [];
if(count($result) === 0){
$res = request()->IM->friend->getFriendList(\support\Encrypt::userIDencode($user_id));
$friendsInfo = $res['friendsInfo'];
foreach($friendsInfo as $k=>$v){
array_push($result,\support\Encrypt::userIDDecode($v['friendUser']['userID']));
}
cache($cache_key,$result,3600);
}
$result[] = $user_id;
return $result;
}
function delete(Request $request): Response{
$id = $request->post('id');
$user = \support\Jwt::getUser();
if (!$user) {
return $this->fail('请先登录');
}
if($id){
FriendCircleModel::where('id',$id)->where('user_id',$user->id)->delete();
}
return $this->success('删除成功');
}
function upload_bg(Request $request){
try {
$user = \support\Jwt::getUser();
if (!$user) {
return $this->fail('请先登录');
}
$res = $this->_upload($request);
if(is_string($res)){
return $this->fail( $res);
}
Db::name('user_extend')->where('user_id',$user->id)->save([
'moments_banner' => $res[0]['file_name']
]);
//$result->ss = cdnurl($result->url);
//P($result);
return $this->success(__('successful'),[
'url'=>$res[0]['file_name']
]);
}catch (\Exception $e){
return $this->error($e->getMessage());
}
}
}
-41
View File
@@ -1,41 +0,0 @@
<?php
namespace app\api\controller;
use app\model\User as UserModel;
use app\model\UserRemark as UserRemarkModel;
use app\model\GroupRemark as GroupRemarkModel;
use support\Request;
use support\Response;
use support\think\Db;
use hg\apidoc\annotation as Apidoc;
/**
* 好友相关
*/
class FriendController extends BaseController{
/**
* 不需要鉴权的方法
* @var array
*/
public $noNeedAuth = ['*'];
/**
* 无需登录及鉴权的方法
* @var array
*/
public $noNeedLogin = [];
/**
* @Apidoc\Title("搜索用户")
* @Apidoc\Method("GET")
* @Apidoc\Param("nickname", type="string",require=true, desc="昵称")
*/
function search(Request $request): Response
{
$keyword = Input('keyword');
$searchtype = Input('searchtype');
if($searchtype =='id'){
}else{
}
return $this->success('ok');
}
}
-120
View File
@@ -1,120 +0,0 @@
<?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;
}
}
-160
View File
@@ -1,160 +0,0 @@
<?php
namespace app\api\controller;
use app\model\Gift as GiftModel;
use app\model\GiftOrder as GiftOrderModel;
use app\model\User as UserModel;
use support\think\Db;
use hg\apidoc\annotation as Apidoc;
/**
* 礼品模块
* @Apidoc\NotParse()
* @Apidoc\NotDebug()
*/
class GiftController extends BaseController{
/**
* 不需要鉴权的方法
* @var array
*/
public $noNeedAuth = ['*'];
public $noNeedLogin = ['detail','list'];
/**
* 列表
* @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)
*/
public function list(){
$limit = (int)input('limit',10);
$model = GiftModel::where('status',1);
$list = $model->order('weight desc,id asc')->paginate($limit);
return $this->success(__('successful'),$list->toArray());
}
/**
* 详情
* @Apidoc\Query("id", type="int", require=true, desc="ID")
*/
public function detail(){
try{
$user = \support\Jwt::getUser();
}catch(\Exception $e){
$user = ['id'=>0,'role_id'=>0];
}
$appid = input('id');
if(!$appid){
return $this->error(__("Product does not exist"));
}
/** @var GiftModel $gift */
$gift = GiftModel::where('id',$appid)->find();
//->cache(true,86400,'product_detail')
if(!$gift) {
return $this->error(__("Product does not exist"));
}
if($user['id']){
/** @var GiftOrderModel $total_quantity_user */
$total_quantity_user = GiftOrderModel::where('gift_id',$gift->id)->where('user_id',$user['id'])->sum('quantity');
$total_quantity_system = $gift->user_quantity ?: 99999999;
$max_quantity = $total_quantity_system-$total_quantity_user;
$max_quantity= $max_quantity < 0 ? 0: $max_quantity;
$gift->max_quantity = $max_quantity;
$gift->total_quantity_user = $total_quantity_user;
}else{
$gift->total_quantity_user = 0;
$gift->max_quantity = 0;
}
return $this->success(__('successful'),$gift->toArray());
}
/**
* 列表
* @Apidoc\Query("status", type="int", require=false, desc="状态")
* @Apidoc\Query("page", type="int", require=true, desc="页码",default=1)
* @Apidoc\Query("limit", type="int", require=true, desc="分页大小",default=10)
*/
public function record(){
$limit = (int)input('limit',10);
$page = (int)input('page',1);
$status = input('status','all');
$user_id = \support\Jwt\JwtToken::getCurrentId();
$model = GiftOrderModel::with(['gift'])->where('user_id',$user_id);
if($status!='all'){
$model = $model->where('status',$status);
}
$list = $model->order('created_at desc')
->paginate($limit);
return $this->success(__('successful'),$list->toArray());
}
/**
* 购买产品
* @Apidoc\Method("POST")
* @Apidoc\Param("gift_id", type="string", require=true, desc="产品ID")
* @Apidoc\Param("quantity", type="int", require=true, desc="数量")
* @Apidoc\Param("trade_password", type="string", require=true, desc="交易密码")
*/
public function create()
{
$user = \support\Jwt::getUser();
$data = [
'user_id' => $user->id,
'gift_id'=> input('gift_id'),
'quantity' => (int)input('quantity'),
'denomination' => input('denomination',0),
'status' => 0
];
if(!$data['gift_id']){
return $this->error(__('Gift is incorrect'));
}
/** @var GiftModel $gift */
$gift = GiftModel::where('id',$data['gift_id'])->find();
if(!$gift){
return $this->error(__('Gift is incorrect'));
}
// if(!in_array($data['denomination'],$gift->amounts)){
// return $this->error(__('Denomination is incorrect'));
// }
if($data['quantity'] < 1){
return $this->error(__('Quantity is incorrect'));
}
if($gift->stock < $data['quantity']){
return $this->error(__('Gift stock is insufficient'));
}
//$data['amount'] = $data['denomination'] * $data['quantity'];
$data['denomination'] = $gift->price;
$data['amount'] = $gift->price * $data['quantity'];
$total_quantity_user = GiftOrderModel::where('gift_id',$gift->id)->where('user_id',$user['id'])->sum('quantity');
$total_quantity_system = $gift->user_quantity ?: 99999999;
$can_purchase = $total_quantity_system-$total_quantity_user;
$can_purchase= $can_purchase < 0 ? 0: $can_purchase;
if($can_purchase < $data['quantity']){
return $this->error(__('You can only purchase %max_quantity% copies',[
'%max_quantity%' => $can_purchase
]));
}
//验证交易密码
$trade_password = input('trade_password');
\support\Jwt::verify_trade_password($trade_password);
//var_dump($user);
//验证余额
if($data['amount'] > $user->score){
return $this->error(__('Insufficient balance'));
}
Db::startTrans();
try{
$data = GiftOrderModel::create($data);
$gift->stock = $gift->stock - $data['quantity'];
$gift->sales = $gift->sales + $data['quantity'];
$gift->save();
UserModel::score($data['user_id'],-$data['amount'],\app\enum\BalanceType::GIFT_BUY,$data['id']);
Hook('gift.buy',$data);
Db::commit();
}catch(\Exception $e){
Db::rollback();
return $this->error($e->getMessage());
}
return $this->success(__('successful'));
}
}
-127
View File
@@ -1,127 +0,0 @@
<?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 AlbumModel;
/**
* 群组管理
*/
class GroupController extends BaseController
{
public $noNeedAuth = ['*'];
public $noNeedLogin = [];
/**
* @Apidoc\Title("群相片列表")
* @Apidoc\Method("POST")
* @Apidoc\Param("group_id", type="string", require=true, desc="群ID")
* @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
{
$user = \support\Jwt::getUser();
$limit = $request->post('limit',10);
$offset = $request->post('offset',0);
$group_id = $request->post('groupID') ?:$request->post('group_id');
//$ls = $this->get_user_in_group($group_id);
$list = AlbumModel::where('group_id',$group_id)
->where('id','<',$offset)
->order('id','desc')
->limit(0,$limit)
->select();
return $this->success('ok',$list);
}
/**
* @Apidoc\Title("上传相片")
* @Apidoc\Method("POST")
* @Apidoc\Param("group_id", type="string", require=true, desc="群ID")
* @Apidoc\Param("title", type="string", require=true, desc="标题")
* @Apidoc\Param("url", type="string", require=true, desc="图片")
*/
function album_create(Request $request): Response
{
$user_id = \support\Jwt\JwtToken::getCurrentId();
$res = $this->_upload($request);
if(is_string($res)){
return $this->fail( $res);
}
$groupID = $request->post('groupID');
$insert_data = [];
foreach($res as $item){
$insert_data[] = [
'user_id' => $user_id,
'group_id' => $groupID,
'title' => $item['origin_name'],
'url' => $item['file_name'],
];
}
$result = AlbumModel::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 album_update(Request $request): Response
{
$id = $request->input('id');
$data = $request->input('data');
$album = AlbumModel::find($id);
$album->update($data);
return $this->success('ok',$album);
}
/**
* @Apidoc\Title("删除相片")
* @Apidoc\Method("POST")
* @Apidoc\Param("id", type="string", require=true, desc="ID")
*/
function album_delete(Request $request): Response
{
$ids = Input('ids');
//$album = AlbumModel::whereIn('id',condition: $ids)->select();
//$album->delete();
AlbumModel::whereIn('id',condition: $ids)->delete();
return $this->success('ok');
}
/**
* 获取再群里的角色
* @Apidoc\NotParse()
* @Apidoc\NotDebug()
*/
private function get_user_in_group($group_id='',$user_id='')
{
$list = request()->IM->group->getGroupMemberList($group_id,$user_id);
return $list;
}
/**
* 头像上传
* @Apidoc\Method("POST")
* @Apidoc\Param("file", type="File", require=true, desc="文件")
*/
public function avatar(Request $request)
{
//单文件上传
$groupID = $request->post('groupID');
if(!$groupID){
return $this->fail(__('Invalid parameter'));
}
$res = $this->_upload($request);
if(is_string($res)){
return $this->fail( $res);
}
$data = [
'groupID' => $groupID,
'faceURL' => $res[0]['file_name'],
];
$list = request()->IM->group->setGroupInfo($data);
return $this->success(__('successful'),$data);
}
}
-17
View File
@@ -1,17 +0,0 @@
<?php
namespace app\api\controller;
use Response;
use support\Request;
use hg\apidoc\annotation as Apidoc;
/**
* 主控制器
* @Apidoc\NotParse()
* @Apidoc\NotDebug()
*/
class IndexController extends BaseController{
public $noNeedLogin=['index','chart'];
public function index(){
return $this->success(__("Unverified address"));
}
}
-32
View File
@@ -1,32 +0,0 @@
<?php
namespace app\api\controller;
use app\model\User as UserModel;
use support\Request;
use support\Response;
use support\think\Db;
use hg\apidoc\annotation as Apidoc;
/**
* 消息控制器
* @Apidoc\NotParse()
* @Apidoc\NotDebug()
*/
class MessageController extends BaseController{
/**
* 购买产品
* @Apidoc\Method("POST")
* @Apidoc\Param("gift_id", type="string", require=true, desc="产品ID")
* @Apidoc\Param("quantity", type="int", require=true, desc="数量")
* @Apidoc\Param("trade_password", type="string", require=true, desc="交易密码")
*/
function delete(Request $request):Response{
$im = $request->IM;
$data = $im->message->sendBusinessNotification('system',\support\Encrypt::userIDencode(100007),[
'contentType' => 101,
'textElem' => [
'content' => '欢迎使用4'.Config('site.name')
]
],'group');
return $this->success('ok');
}
}
-473
View File
@@ -1,473 +0,0 @@
<?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());
}
}
}
-190
View File
@@ -1,190 +0,0 @@
<?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'));
}
}
-731
View File
@@ -1,731 +0,0 @@
<?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());
}
}
}
-70
View File
@@ -1,70 +0,0 @@
<?php
namespace app\api\controller;
use app\model\Product as ProductModel;
use app\model\ProductOrder as ProductOrderModel;
use support\think\Db;
use hg\apidoc\annotation as Apidoc;
/**
* 产品模块
* @Apidoc\NotParse()
* @Apidoc\NotDebug()
*/
class ProductController extends BaseController{
/**
* 不需要鉴权的方法
* @var array
*/
public $noNeedAuth = ['*'];
public $noNeedLogin = ['detail','list'];
/**
* 列表
* @Apidoc\Query("kw", type="string", require=false, desc="搜索关键字")
* @Apidoc\Query("step", type="string", require=false, desc="类型,progress,done")
* @Apidoc\Query("billing_cycle", type="int", require=true, desc="周期",default=1)
* @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);
$model = ProductModel::where('status',1);
$list = $model->order('weight desc,id asc')->paginate($limit);
return $this->success(__('successful'),$list->toArray());
}
/**
* 详情
* @Apidoc\Query("id", type="int", require=true, desc="ID")
*/
public function detail(){
try{
$user = \support\Jwt::getUser();
}catch(\Exception $e){
$user = ['id'=>0,'role_id'=>0];
}
$appid = input('id');
if(!$appid){
return $this->error(__("Product does not exist"));
}
/** @var ProductModel $product */
$product = ProductModel::where('id',$appid)->find();
//->cache(true,86400,'product_detail')
if(!$product) {
return $this->error(__("Product does not exist"));
}
if($user['id']){
$total_quantity_user = ProductOrderModel::where('product_id',$product->id)->where('user_id',$user['id'])->sum('quantity');
$total_quantity_system = $product->user_quantity ?: 99999999;
$max_quantity = $total_quantity_system-$total_quantity_user;
$max_quantity= $max_quantity < 0 ? 0: $max_quantity;
$product->max_quantity = $max_quantity;
$product->total_quantity_user = $total_quantity_user;
}else{
$product->total_quantity_user = 0;
$product->max_quantity = 0;
}
return $this->success(__('successful'),$product->toArray());
}
}
@@ -1,146 +0,0 @@
<?php
namespace app\api\controller;
use app\model\ProductOrder as ProductOrderModel;
use app\model\Product as ProductModel;
use app\model\User as UserModel;
use support\think\Db;
use hg\apidoc\annotation as Apidoc;
/**
* 我的产品
* @Apidoc\NotParse()
* @Apidoc\NotDebug()
*/
class ProductOrderController extends BaseController{
/**
* 不需要鉴权的方法
* @var array
*/
public $noNeedAuth = ['*'];
public $noNeedLogin = [];
/**
* 列表
* @Apidoc\Query("step", type="int", require=false, desc="工作状态")
* @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);
$page = (int)input('page',1);
$step = (int)input('step',0);
$user_id = \support\Jwt\JwtToken::getCurrentId();
$model = ProductOrderModel::with(['product'])->where('user_id',$user_id);
if($step){
$ids = \app\model\WorkRecord::where('user_id',$user_id)
->distinct(true)
->where('status',$step)
->column('order_id');
$model = $model->whereIn('id',$ids);
}
$list = $model->where('status',1)
->order('created_at desc')
->paginate($limit);
$list->each(function($item)use($step){
//$_step=1;
if($step){
$_step = $step;
}else{
$_step = \app\model\WorkRecord::where('order_id',$item->id)
->order('status','asc')
->limit(0,1)
->value('status');
}
$item->step = $_step;
return $item;
});
return $this->success(__('successful'),$list->toArray());
}
/**
* 购买产品
* @Apidoc\Method("POST")
* @Apidoc\Param("product_id", type="string", require=true, desc="产品ID")
* @Apidoc\Param("quantity", type="int", require=true, desc="数量")
* @Apidoc\Param("trade_password", type="string", require=true, desc="交易密码")
*/
public function create()
{
$user = \support\Jwt::getUser();
//验证交易密码
$trade_password = input('trade_password');
\support\Jwt::verify_trade_password($trade_password);
$data = [
'user_id' => $user->id,
'product_id'=> input('product_id'),
'quantity' => (int)input('quantity'),
'accelerate' => input('accelerate'),
'status' => 1
];
if(!$data['product_id']){
return $this->error(__('Product is incorrect'));
}
if($data['quantity'] < 1){
return $this->error(__('Quantity is incorrect'));
}
if($data['product_id'] == 12){
$exsit = ProductOrderModel::where('product_id',$data['product_id'])->where('user_id',$data['user_id'])->count('id');
if($exsit != 0){
return $this->error(__('微量包每个用户只能购买一个'));
}
if($data['quantity'] != 1){
return $this->error(__('微量包每个用户只能购买一个'));
}
}
/** @var ProductModel $product */
$product = ProductModel::where('id',$data['product_id'])->find();
if(!$product){
return $this->error(__('Product is incorrect'));
}
/*
if ($product->start_time > time() || $product->end_time < time()) {
return $this->error(__('Can\'t buy now'));
}
*/
//return $this->success(__('successful'),[$product->start_time]);
$data['price'] = $product['price'];
if($data['accelerate']){
$data['price'] += $product['accelerate_price'];
$data['accelerate_times'] = $product['accelerate_assign_times'];
$data['accelerate_used'] = 0;
}
$data['amount'] = $data['price'] * $data['quantity'];
$data['total'] = $product['total'] * $data['quantity'];
$data['assigned'] = 0;
/*
$total_quantity_user = ProductOrderModel::where('product_id',$product->id)->where('user_id',$user['id'])->sum('quantity');
$total_quantity_system = $product->user_quantity ?: 99999999;
$can_purchase = $total_quantity_system-$total_quantity_user;
$can_purchase= $can_purchase < 0 ? 0: $can_purchase;
if($can_purchase < $data['quantity']){
return $this->error(__('You can only purchase %max_quantity% copies',[
'%max_quantity%' => $can_purchase
]));
}
*/
//var_dump($user);
//验证余额
if($data['amount'] > $user->money){
return $this->error(__('Insufficient balance'));
}
Db::startTrans();
try{
$data = ProductOrderModel::create($data);
$product->sales = $product->sales + $data['quantity'];
$product->save();
UserModel::money($data['user_id'],-$data['amount'],\app\enum\BalanceType::PRODUCT_BUY,$data['id']);
Hook('product.buy',$data);
Db::commit();
}catch(\Exception $e){
Db::rollback();
return $this->error($e->getMessage());
}
return $this->success(__('successful'));
}
}
-482
View File
@@ -1,482 +0,0 @@
<?php
namespace app\api\controller;
use support\Request;
use support\think\Db;
use app\model\Recharge as RechargeModel;
use app\model\User as UserModel;
use hg\apidoc\annotation as Apidoc;
/**
* 充值模块
* @Apidoc\NotParse()
* @Apidoc\NotDebug()
*/
class RechargeController extends BaseController{
/**
* 不需要鉴权的方法
* @var array
*/
public $noNeedAuth = ['*'];
/**
* 无需登录及鉴权的方法
* @var array
*/
public $noNeedLogin = ['notify','watch_list','updateUserBalance'];
/**
* 列表
* @Apidoc\Method("POST")
* @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);
$status = Input('status',null);
if(!is_null($status)){
$list = RechargeModel::where('user_id',\support\Jwt\JwtToken::getCurrentId())->where('status',$status)->order('id desc')->paginate($limit);
}else{
$list = RechargeModel::where('user_id',\support\Jwt\JwtToken::getCurrentId())->order('id desc')->paginate($limit);
}
return $this->success(__('successful'),$list->toArray());
}
/**
* 创建
* @Apidoc\Method("POST")
* @Apidoc\Param("amount", type="string", require=true, desc="金额")
* @Apidoc\Param("network", type="string", require=true, desc="网络")
*/
function create(){
return $this->create1();
}
public function create2()
{
$user = \support\Jwt::getUser();
$data = [
'user_id' => $user->id,
'amount' => input('amount',0),
'network' => input('network',''),
'status' => \app\enum\WithdrawlStatus::CREATED->value
];
//验证最小金额
if($data['amount'] < Config('site.recharge_minimum')){
return $this->error(__('Minimum recharge of %num%',[
'%num%' => Config('site.recharge_minimum')
]));
}
if (!$data['network'] || !in_array($data['network'],['BEP-20','TRC-20','wxpay','alipay','qqpay'])) {
return $this->error(__('Network is incorrect'));
}
if(in_array($data['network'],['BEP-20','TRC-20'])){
$ing_count = RechargeModel::where('user_id',$data['user_id'])->where('status',\app\enum\RechargeStatus::CREATED->value)->count('id');
if($ing_count >= 2){
return $this->error(__('You have reached the limit of uncompleted orders ( %max% ). Please complete the existing orders before trying to create another one.',[
'%max%' => 2
]));
}
Db::startTrans();
try {
/** @var RechargeModel $order */
$order = RechargeModel::create($data);
//$order->notify_url = Request()->domain().'/api/recharge/notify';
$order->notify_url = config('pay.notify_server').'/api/recharge/notify';
$order->out_trade_no = $order->id;
$order->env = 'product';
$order->appid = Config('pay.appid');
$order->created_at = time();
$postdata = $order->toArray();
unset($postdata['id']);
//转换成USDT
$postdata['amount'] = bcdiv($order->amount,Config('site.money_to_usdt_rate'),4);
//折扣
$postdata['amount'] = bcdiv($postdata['amount'],Config('site.usdt_recharge_discount'),4);
$res = post(Config('pay.server').'/recharge/create',$postdata);
\support\Log::alert("create res:".$res);
//cp($res);
$res = json_decode($res,true);
if($res['code'] === 0){
$order->address = $res['data']['address'];
}else{
Db::rollback();
return $this->error(__('Failed to create recharge order, please try again later'));
}
$order->allowField(['address'])->save();
Db::commit();
return $this->success(__('successful'),[
'order' => $order
]);
} catch (\Exception $e) {
Db::rollback();
\support\Log::alert("create error".$e->getMessage());
return $this->error(__('Failed to create recharge order, please try again later'));
}
}else{
$sign = \support\Encrypt::aesencode(json_encode($data));
return $this->success(__('successful'),[
'order' => [
'id' => 0,
'network' => $data['network'],
'amount' => $data['amount'],
"url" => 'http://43.240.74.89:9595/pay/index?sign='.$sign
]
]);
}
}
public function create1()
{
$user = \support\Jwt::getUser();
$data = [
'user_id' => $user->id,
'amount' => input('amount',0),
'network' => input('network',''),
'status' => \app\enum\WithdrawlStatus::CREATED->value
];
//验证最小金额
if($data['amount'] < Config('site.recharge_minimum')){
return $this->error(__('Minimum recharge of %num%',[
'%num%' => Config('site.recharge_minimum')
]));
}
if (!$data['network'] || !in_array($data['network'],['BEP-20','TRC-20','wxpay','alipay','qqpay'])) {
return $this->error(__('Network is incorrect'));
}
if(in_array($data['network'],['BEP-20','TRC-20'])){
$ing_count = RechargeModel::where('user_id',$data['user_id'])->where('status',\app\enum\RechargeStatus::CREATED->value)->count('id');
if($ing_count >= 2){
return $this->error(__('You have reached the limit of uncompleted orders ( %max% ). Please complete the existing orders before trying to create another one.',[
'%max%' => 2
]));
}
}
Db::startTrans();
try {
/** @var RechargeModel $order */
$order = RechargeModel::create($data);
if(in_array($data['network'],['BEP-20','TRC-20'])){
//$order->notify_url = Request()->domain().'/api/recharge/notify';
$order->notify_url = config('pay.notify_server').'/api/recharge/notify';
$order->out_trade_no = $order->id;
$order->env = 'product';
$order->appid = Config('pay.appid');
$order->created_at = time();
$postdata = $order->toArray();
unset($postdata['id']);
//转换成USDT
$postdata['amount'] = bcdiv($order->amount,Config('site.money_to_usdt_rate'),4);
//折扣
$postdata['amount'] = bcmul($postdata['amount'],Config('site.usdt_recharge_discount'),4);
$res = post(Config('pay.server').'/recharge/create',$postdata);
\support\Log::alert("create res:".$res);
//cp($res);
$res = json_decode($res,true);
if($res['code'] === 0){
$order->address = $res['data']['address'];
}else{
Db::rollback();
return $this->error(__('Failed to create recharge order, please try again later'));
}
$order->allowField(['address'])->save();
}else{
$postdata = [
"pid" => '144604',
"type" => $order->network,
"out_trade_no" => $order->id,
"notify_url" => Config('pay.notify_server').'/api/recharge/notify_ok',
"return_url" => Config('pay.notify_server').'/api/recharge/notify_ok',
"name" => 'VIP会员',
"money" => $order->amount,
"device" => "mobile",
"clientip" => request()->getRealIp()
];
$postdata['money'] = bcdiv($postdata['money'] ,Config('site.rmb_recharge_discount'),2);
//\support\Log::alert("create postdata:".json_encode($postdata));
$this->getsign($postdata);
$res = post('https://pay.yf2.cn/mapi.php',$postdata);
\support\Log::alert("create res:".$res);
$res = json_decode($res,true);
if($res['code'] === 1){
if(isset($res['payurl'])){
$order->address = $res['qrcode'];
}elseif(isset($res['qrcode'])){
$order->address = $res['qrcode'];
}elseif(isset($res['urlscheme'])){
$order->address = $res['urlscheme'];
}
}else{
Db::rollback();
\support\Log::error(json_encode($res));
throw new \Exception($res['msg']);
}
$order->allowField(['address'])->save();
}
Db::commit();
return $this->success(__('successful'),[
'order' => $order
]);
} catch (\Exception $e) {
Db::rollback();
\support\Log::alert("create error".$e->getMessage());
return $this->error(__('Failed to create recharge order, please try again later'));
}
}
/**
* 更新
* @Apidoc\Method("POST")
* @Apidoc\Param("id", type="int", require=true, desc="ID")
* @Apidoc\Param("amount", type="string", require=true, desc="金额")
* @Apidoc\Param("network", type="string", require=true, desc="网络")
*/
function update(){
$id = input('id');
$amount = input('amount');
$network = input('network');
$data = [];
if(!$id){
return $this->error(__('Invalid parameters'));
}
/** @var RechargeModel $order */
$order = RechargeModel::where('id',$id)->find();
if(!$order){
return $this->error(__('Order not exsit'));
}
if($amount && $amount != $order->amount){
$data['amount'] = $amount;
//验证最小金额
if($data['amount'] < Config('site.recharge_minimum')){
return $this->error(__('Minimum recharge of %num%',[
'%num%' => Config('site.recharge_minimum')
]));
}
$data['amount'] = bcdiv($data['amount'],Config('site.money_to_usdt_rate'),4);
}
if($network && $network != $order->network){
$data['network'] = $network;
if (!$data['network'] || !in_array($data['network'],['BEP-20','TRC-20'])) {
return $this->error(__('Network is incorrect'));
}
}
if(empty($data)){
return $this->error(__('Invalid parameters'));
}
foreach($data as $field=>$value){
$order->$field = $value;
}
$data['out_trade_no'] = $order->id;
$data['action'] = 'update';
$data['appid'] = Config('pay.appid');
$res = post(Config('pay.server').'/recharge/create',$data);
//\support\Log::alert("update res:".$res);
$res = json_decode($res,true);
//cp($res);
if($res['code'] === 0){
if($order->address != $res['data']['address']){
$order->address = $res['data']['address'];
}
}else{
return $this->error(__('Failed to create recharge order, please try again later'));
}
$order->save();
return $this->success(__('successful'),[
'order' => $order
]);
}
/**
* 详情
* @Apidoc\Query("id", type="string", require=true, desc="ID")
*/
public function detail(){
$appid = input('id');
$vo = RechargeModel::where('id',$appid)->find();
if($vo) {
return $this->success(__('successful'),[
'order' => $vo
]);
}
return $this->error(__("Record does not exist"));
}
/**
* 转账成功异步通知
* @Apidoc\NotParse()
* @Apidoc\NotDebug()
*/
public function notify(){
$data = \support\Encrypt::aesdecode(input('data'));
$data = json_decode($data,true);
/** @var RechargeModel $vo */
$vo = RechargeModel::where('id',$data['out_trade_no'])->find();
if($vo){
if($data['result'] == 'SUCCESS'){
if($vo->status != \app\enum\RechargeStatus::COMPLETE->value){
try{
$vo->status = \app\enum\RechargeStatus::COMPLETE->value;
$vo->txid = $data['txid'];
$vo->real_amount = isset($data['real_amount']) ? $data['real_amount'] : 0;
$vo->transfer_at = $data['transfer_at'] ?: time();
$vo->save();
//消除USDT和人民币的误差,因为USDT支付可能不是一模一样的金额
$money = bcmul($vo->amount ,Config('site.rmb_to_money_rate'));
UserModel::money($vo->user_id,$money,\app\enum\BalanceType::RECHARGE,$vo->id);
Hook('recharge.success',$vo);
}catch(\Exception $e){
log_alert('充值回调失败:'.$e->getMessage());
log_alert($data);
}
}
}else{
$vo->status = \app\enum\RechargeStatus::FAIL->value;
$vo->txid = $data['txid'];
$vo->reason = $data['reason'];
$vo->save();
}
}
return response("SUCCESS");
}
/**
* 人民币转账成功异步通知
* @Apidoc\NotParse()
* @Apidoc\NotDebug()
*/
public function notify_ok(){
$data = Input('post.');
$sign = get_pay_sign($data);
if($sign != $data['sign']){
return response("FAIL");
}
/** @var RechargeModel $vo */
$vo = RechargeModel::where('id',$data['out_trade_no'])->find();
if($vo && isset($data['trade_status'])){
if($data['trade_status'] === 'TRADE_SUCCESS'){
if($vo->status != \app\enum\RechargeStatus::COMPLETE->value){
try{
$vo->status = \app\enum\RechargeStatus::COMPLETE->value;
$vo->txid = isset($data['trade_no']) ? $data['trade_no'] : '';
$vo->from='pay.yf2.cn';
$vo->real_amount = isset($data['money']) ? $data['money'] : 0;
$vo->save();
$money = bcmul($vo->real_amount ,Config('site.rmb_to_money_rate'));
UserModel::money($vo->user_id,$money,\app\enum\BalanceType::RECHARGE,$vo->id);
Hook('recharge.success',$vo);
}catch(\Exception $e){
log_alert('充值回调失败:'.$e->getMessage());
log_alert($data);
return response("FAIL");
}
}
}
}
return response("SUCCESS");
}
/**
* 根据TXID更新用户余额,补单的功能
* @Apidoc\NotParse()
* @Apidoc\NotDebug()
* @return \support\Response
*/
function update_balance_by_txid(){
$user_id = \support\Jwt\JwtToken::getCurrentId();
$data = [
'txid' => input('txid'),
'id' => input('id'),
'status' => \app\enum\WithdrawlStatus::CREATED->value
];
/** @var RechargeModel $vo */
$vo = RechargeModel::where('id',$data['id'])->find();
if($vo->user_id !=$user_id){
return $this->error(__('permission denied'));
}
//验证最小金额
if(!$data['txid']){
return $this->error(__('Invalid parameters'));
}
$res = get(Config('pay.server').'/Util/txid?txid='.$data['txid']);
$res = json_decode($res,true);
if($res['code'] !== 0){
return $this->error($res['msg']);
}
$vo->txid = $data['txid'];
$vo->result = $res['result'];
if($res['result'] == 'SUCCESS'){
$vo->from = $res['from'];
$vo->real_amount = $res['real_amount'];
$vo->pay_time = $res['timestamp'];
$vo->status = \app\enum\RechargeStatus::COMPLETE->value;
}else{
$vo->result = $res['from'];
$vo->reason = $res['real_amount'];
$vo->status = \app\enum\RechargeStatus::FAIL->value;
}
$vo->save();
return $this->success(__('successful'));
}
/**
* 根据监视上报数据更新用户余额
* @Apidoc\NotParse()
* @Apidoc\NotDebug()
*/
function updateUserBalance(Request $request){
$decimal_part = $request->post('decimal_part');
$data = [
'user_id' => 0 ,
'amount' => $request->post('amount'),
'network' => $request->post('chain'),
'address' => $request->post('to'),
'from' => $request->post('from'),
'txid' => $request->post('txid'),
'pay_time' => $request->post('pay_time'),
'created_at' => $request->post('pay_time'),
];
if($data['network'] == 'TRC-20'){
$data['user_id'] = UserModel::where('trc_recharge_address',$data['address'])->where('decimal_part',$decimal_part)->value('id');
}else{
$data['user_id'] = UserModel::where('bep_recharge_address',$data['address'])->where('decimal_part',$decimal_part)->value('id');
}
if(!$data['user_id']){
return $this->success(__('user not exist'));
}
$data['real_amount'] = $data['amount'];
//$data['pay_time'] = time();
$data['status'] = 2;
$vo = RechargeModel::where('txid',$data['txid'])->find();
if($vo){
return $this->success(__('exist'));
}
Db::startTrans();
try{
$idata = RechargeModel::create($data);
UserModel::score($data['user_id'],$data['amount'],\app\enum\BalanceType::RECHARGE,$idata['id'].'');
Db::commit();
Hook('recharge.success',$idata);
return $this->success(__('successful'));
}catch(\Exception $e){
Db::rollback();
return $this->error($e->getMessage());
}
}
/**
* 监视列表
* @Apidoc\NotParse()
* @Apidoc\NotDebug()
*/
function watch_list(){
//$bep = UserModel::distinct(true)->column('LOWER(bep_recharge_address)');
//$trc = UserModel::distinct(true)->column('LOWER(trc_recharge_address)');
$bep = UserModel::distinct(true)->column('bep_recharge_address');
$trc = UserModel::distinct(true)->column('trc_recharge_address');
return $this->success(__('successful'),[
'BEP-20'=>$bep,
'TRC-20'=>$trc,
]);
}
protected function getsign(&$data, $key = "3E7551E3707DFB6B6E8A6E83B01FF437") {
return get_pay_sign($data,$key);
}
}
-295
View File
@@ -1,295 +0,0 @@
<?php
namespace app\api\controller;
use support\Request;
use support\Response;
use app\model\UserSignin as UserSigninModel;
use app\model\UserXuanchuan as UserXuanchuanModel;
use support\Jwt\JwtToken;
use Shopwwi\WebmanFilesystem\FilesystemFactory;
use Shopwwi\WebmanFilesystem\Facade\Storage;
use hg\apidoc\annotation as Apidoc;
/**
* 签到模块
* @Apidoc\NotParse()
* @Apidoc\NotDebug()
*/
class SigninController extends BaseController
{
/**
* 不需要鉴权的方法
* @var array
*/
public $noNeedAuth = ['*'];
/**
* 无需登录及鉴权的方法
* @var array
*/
public $noNeedLogin = [];
/**
* 列表
* @Apidoc\Query("status", type="int", require=true, desc="状态")
* @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);
$status = (int)input('status');
$model = UserXuanchuanModel::where('id','>',0);
if($status && $status!='all'){
$model = $model->where('status',$status);
}
$list = $model->order('id','desc')->paginate($limit);
return $this->success(__('successful'),$list);
}
/**
* @Apidoc\Title("用户签到")
* @Apidoc\Method("GET")
*/
public function info(Request $request)
{
$user_id = JwtToken::getCurrentId();
if (!$user_id) {
return $this->error(__('Please login first'));
}
/** @var UserSigninModel $last */
$last = UserSigninModel::where('user_id', $user_id)->order('id','desc')->find();
return $this->success(__('successful'),[
'continuous_days' => $last->continuous_days,
'last_day' => $last->sign_date,
'signed' => $last->sign_date == date('Y-m-d'),
'invite_complete' => cache('invite_'.$user_id.'_'.date('Ymd')),
'pyq_complete' => UserXuanchuanModel::where('user_id',$user_id)->where('type','pyq')->whereTime('created_at','today')->count() > 0,
'group_complete' => UserXuanchuanModel::where('user_id',$user_id)->where('type','group_complete')->whereTime('created_at','today')->count() > 0,
'signinList' => [20,20,20,20,20,20,100]
]);
}
/**
* @Apidoc\Title("用户签到")
* @Apidoc\Method("GET")
*/
public function sign(Request $request)
{
$user = JwtToken::getUser();
if (!$user->realname_verify) {
return $this->error(__('Please complete real-name verification first'));
}
$user_id = $user->id;
$today = date('Y-m-d');
// 检查今天是否已签到
if (UserSigninModel::where('user_id', $user_id)->where('sign_date', $today)->find()) {
return $this->error(__('今日已签到'));
}
// 检查昨天是否签到
$continuous_days = UserSigninModel::where('user_id', $user_id)->count('id');
$continuous_days = $continuous_days ? ($continuous_days + 1) : 1;
// 奖励规则(可自定义/读取配置/数据库)
$reward = $this->getReward($continuous_days);
// 写入签到记录
UserSigninModel::create([
'user_id' => $user_id,
'sign_date' => $today,
'reward' => $reward,
'continuous_days' => $continuous_days,
]);
// 发放奖励(如积分、余额等)
\app\model\User::currency1($user_id, $reward, \app\enum\BalanceType::SIGNIN);
return $this->success(__('successful'),[
'reward' => $reward,
'continuous_days' => $continuous_days
]);
}
/**
* @Apidoc\Title("查询签到状态")
* @Apidoc\Method("GET")
*/
public function status(Request $request)
{
$user_id = JwtToken::getCurrentId();
if (!$user_id) {
return $this->error(__('Please login first'));
}
$today = date('Y-m-d');
$record = UserSigninModel::where('user_id', $user_id)->where('sign_date', $today)->find();
$continuous_days = UserSigninModel::where('user_id', $user_id)->order('id','desc')->value('continuous_days');
return $this->success(__('successful'),[
'signed' => !!$record,
'continuous_days' => $continuous_days ?: 0,
]);
}
/**
* @Apidoc\Title("查询签到记录")
* @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 records(Request $request)
{
$user_id = JwtToken::getCurrentId();
if (!$user_id) {
return $this->error(__('Please login first'));
}
$limit = (int)$request->get('limit', 30);
$list = UserSigninModel::where('user_id', $user_id)
->order('id','desc')
->limit($limit)
->select();
return $this->success(__('successful'),$list);
}
/**
* @Apidoc\Title("发布朋友圈")
* @Apidoc\Method("POST")
* @Apidoc\Param("files", type="string",require=true, desc="文件列表")
*/
public function pyq(Request $request)
{
$user = JwtToken::getUser();
if (!$user->realname_verify) {
return $this->error(__('Please complete real-name verification first'));
}
$user_id = $user->id;
$files = Input('files');
if(count($files) != 2) {
return $this->error(__('请上传2张图片'));
}
if(UserXuanchuanModel::where('user_id',$user_id)->where('type','pyq')->whereTime('created_at','today')->count() > 0) {
return $this->error(__('请明日再来'));
}
UserXuanchuanModel::create([
'user_id' => $user_id,
'files' => implode(',',$files),
'type' => 'pyq'
]);
return $this->success(__('successful'));
}
/**
* @Apidoc\Title("发布微信群")
* @Apidoc\Method("POST")
* @Apidoc\Param("files", type="string",require=true, desc="文件")
*/
public function wx(Request $request)
{
$user = JwtToken::getUser();
if (!$user->realname_verify) {
return $this->error(__('Please complete real-name verification first'));
}
$user_id = $user->id;
$files = Input('files');
if(count($files) != 1) {
return $this->error(__('请上传1张图片'));
}
if(UserXuanchuanModel::where('user_id',$user_id)->where('type','group')->whereTime('created_at','today')->count() > 0) {
return $this->error(__('请每日再来'));
}
UserXuanchuanModel::create([
'user_id' => $user_id,
'files' => implode(',',$files),
'type' => 'group'
]);
return $this->success(__('successful'));
}
/**
* @Apidoc\Title("补签")
* @Apidoc\Method("POST")
* @Apidoc\Param("date", type="string",require=true, desc="补签日期")
*/
public function makeUp(Request $request)
{
$user_id = JwtToken::getCurrentId();
if (!$user_id) {
return $this->error(__('Please login first'));
}
$date = $request->post('date');
$today = date('Y-m-d');
if (!$date || !preg_match('/^\d{4}-\d{2}-\d{2}$/', $date)) {
return $this->error(__('日期格式错误'));
}
// 补签日期不能大于今天
if ($date > $today) {
return $this->error(__('补签日期不能大于今天'));
}
// 检查是否已签到
if (UserSigninModel::where('user_id', $user_id)->where('sign_date', $date)->find()) {
return $this->error(__('该日已签到'));
}
// 补签消耗积分或奖励减半(此处以奖励减半为例)
$yesterday = date('Y-m-d', strtotime($date . ' -1 day'));
/** @var UserSigninModel $last */
$last = UserSigninModel::where('user_id', $user_id)->where('sign_date', $yesterday)->find();
$continuous_days = $last ? ($last->continuous_days + 1) : 1;
$reward = floor($this->getReward($continuous_days) / 2); // 奖励减半
UserSigninModel::create([
'user_id' => $user_id,
'sign_date' => $date,
'reward' => $reward,
'continuous_days' => $continuous_days,
]);
\app\model\User::currency1($user_id, $reward, \app\enum\BalanceType::SIGNIN);
return $this->success(__('successful'),['reward' => $reward, 'continuous_days' => $continuous_days]);
}
/**
* @Apidoc\Title("签到统计报表")
* @Apidoc\Method("GET")
*/
public function report(Request $request)
{
$user_id = JwtToken::getCurrentId();
if (!$user_id) {
return $this->error(__('Please login first'));
}
$total = UserSigninModel::where('user_id', $user_id)->count();
$max_continuous = UserSigninModel::where('user_id', $user_id)->max('continuous_days');
$month = date('Y-m');
$month_count = UserSigninModel::where('user_id', $user_id)
->whereLike('sign_date', "$month%")
->count();
return $this->success(__('successful'),[
'total' => $total,
'max_continuous' => $max_continuous,
'month_count' => $month_count,
]);
}
// 奖励规则,可自定义
protected function getReward($continuous_days)
{
$rewards = [20,20,20,20,20,20,100];
$continuous_days = $continuous_days - 1;
$continuous_days = $continuous_days % 7;
return $rewards[$continuous_days];
}
/**
* @Apidoc\Title("上传")
* @Apidoc\Method("POST")
* @Apidoc\Param("file", type="string",require=true, desc="文件")
*/
function upload(Request $request,$return = false)
{
//多文件上传
$files = $request->file();
try {
$result = Storage::adapter('public')
->path('upload/files')
->size(1024*1024*50)
->extYes(['image/jpeg','image/png'])
->uploads($files,0,1024*1024*20,false);
return $this->success(__('successful'),$result);
}catch (\Exception $e){
return $this->error($e->getMessage());
}
}
}
-192
View File
@@ -1,192 +0,0 @@
<?php
namespace app\api\controller;
use app\model\User as UserModel;
use app\model\UserTeam as UserTeamModel;
use app\model\WorkRecord as WorkRecordModel;
use support\Request;
use support\think\Db;
use hg\apidoc\annotation as Apidoc;
/**
* 用户团队
*/
class TeamController extends BaseController{
/**
* 不需要鉴权的方法
* @var array
*/
public $noNeedAuth = ['*'];
/**
* 无需登录及鉴权的方法
* @var array
*/
public $noNeedLogin = [];
/**
* @Apidoc\Title("团队概览")
* @Apidoc\Method("GET")
* @Apidoc\Param("username", type="string",require=true, desc="用户名")
* @Apidoc\Param("nickname", type="string",require=true, desc="密码")
*/
public function index(Request $request){
$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');
$result=[
'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);
}
/**
* @Apidoc\Title("团队列表")
* @Apidoc\Method("GET")
* @Apidoc\Param("page", type="int",require=false, desc="页码")
* @Apidoc\Param("limit", type="int",require=false, desc="分页大小")
*/
public function list(Request $request){
$user = \support\Jwt::getUser();
$limit = $request->get('limit',10);
$page = $request->get('page',1);
$kw = $request->get('kw');
// 假设 $user_id 是要查询的用户 ID
// $user = User::find($user['id']); // 查询用户对象
// // 获取该用户的下属团队
// $teamMembers = $user->team()
// ->where('status', 1) // 只查询有效的下属
// ->with('user') // 联合查询 User 模型,获取下属的详细信息
// ->order('depth') // 按照层级深度排序
// ->select();
// foreach ($teamMembers as $member) {
// echo '下属用户ID: ' . $member->descendant_id . ',层级深度: ' . $member->depth . ',状态: ' . $member->status . ',用户名: ' . $member->user->name . ',邮箱: ' . $member->user->email . "\n";
// }
// return $this->success(__('successful'),$result);
// $model = Db::name('user_team')
// ->alias('ut')
// ->join('user wu', 'ut.descendant_id = wu.id')
// ->where('ut.ancestor_id', $user['id'])
// ->where('ut.depth', '<=', 3) // 限制三级内
// ->field('wu.id, wu.username, wu.group, ut.depth')
// ->order('ut.depth ASC, wu.username ASC');
// if($limit == 'all' || $limit >= 999999){
// $result = $model->select();
// }else{
// // 分页处理
// $result = $model->page($page, $limit)->select();
// $total = $model->count(); // 获取总记录数
// $result->each(function ($item) {
// //cache_add('user_recharge_total_'.$item['id'],1);
// //cache_add('user_withdrawl_total_'.$item['id'],1);
// //cache_add('user_income_total_'.$item['id'],1);
// $item['avatar'] = cdnurl($item['avatar'] ?: '/storage/avatar/default.png');
// $item['recharge_total'] = cache('user_recharge_total_'.$item['id']);
// $item['withdrawl_total'] = cache('user_withdrawl_total_'.$item['id']);
// $item['income_total'] = cache('user_income_total_'.$item['id']);
// $item['created_at'] = date('Y-m-d H:i:s', $item['created_at']);
// return $item;
// });
// $result = [
// 'data' => $result,
// 'total' => $total,
// 'current_page' => $page,
// 'last_page' => ceil($total / $limit),
// 'per_page' => $limit,
// ];
// }
$user_id = \support\Jwt\JwtToken::getCurrentId();
$model = UserModel::alias('u')
->where('parent_id',$user_id)
->join('user_extend ue', 'u.id = ue.user_id')
->where('u.parent_id', $user['id'])
//->where('ue.active', 1)
->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.'%');
}
if($limit == 'all' || $limit >= 999999){
$result = $model->select();
// $result = [
// 'data' => $result,
// 'total' => count($result),
// 'current_page' => 1,
// 'last_page' => 1,
// 'per_page' => count($result),
// ];
$result= \think\Paginator::make($result, 99999999999, 1, count($result));
}else{
$result = $model->paginate($limit);
}
$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]['created_at'] = date('Y-m-d H:i:s', $item['created_at']);
//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")
* @Apidoc\Param("level", type="int",require=false, desc="等级")
*/
function changelevel(Request $request){
$user = \support\Jwt::getUser();
$id = $request->post('id');
$level = $request->post('level');
$id = \support\Encrypt::userIDDecode($id);
if(!$id || !$level){
return $this->error(__('Invalid parameters'));
}
$child_user = UserModel::find($id);
if(!$child_user){
return $this->error(__('Invalid user'));
}
if($child_user->parent_id!=$user->id){
return $this->error(__('Access denied'));
}
if($user->role_id <= $level){
return $this->error(__('It cannot be lower than the user\'s current level'));
}
if($child_user->role_id >= $level){
return $this->error(__('It cannot be lower than the user\'s current level'));
}
$child_user->role_id = $level;
$child_user->save();
return $this->success(__('successful'));
}
}
-164
View File
@@ -1,164 +0,0 @@
<?php
namespace app\api\controller;
use support\Request;
use support\Response;
use support\think\Db;
use hg\apidoc\annotation as Apidoc;
/**
* VIP
*/
class ThaliController extends BaseController{
/**
* 不需要鉴权的方法
* @var array
*/
public $noNeedAuth = ['*'];
/**
* 无需登录及鉴权的方法
* @var array
*/
public $noNeedLogin = ['recent','list'];
/**
* @Apidoc\Title("列表")
* @Apidoc\Method("GET")
* @Apidoc\Param("page", type="int",require=false, desc="页码")
* @Apidoc\Param("limit", type="int",require=false, desc="分页大小")
*/
public function list(Request $request){
$limit = $request->get('limit',10);
$kw = $request->get('kw');
$model = \app\model\Thali::with(['Role'])->where('status',1)->order('id asc');
if($limit == 'all' || $limit >= 999999){
$result = $model->select();
$result= \think\Paginator::make($result, 99999999999, 1, count($result));
}else{
$result = $model->paginate($limit);
}
return $this->success(__('successful'),$result);
}
/**
* @Apidoc\Title("当前角色信息")
* @Apidoc\Method("GET")
* @Apidoc\NotParse()
* @Apidoc\NotDebug()
*/
function detail(){
$user = \support\Jwt::getUser();
$data = \app\model\UserRole::where('id',$user->role_id)->field('name,id,price')->find();
return $this->success(__('successful'),$data);
}
/**
* @Apidoc\Title("最近购买列表")
* @Apidoc\Method("GET")
*/
public function recent(Request $request){
$list = (new \app\model\BalanceLog)->setSuffix('_score')
->where('type',\app\enum\BalanceType::PURCHASE_ROLE->value)
->order('created_at','desc')
->limit(0,10)
->field('user_id,created_at,memo')
->select();
$list = $list->toArray();
$data = [];
$role_list = \app\model\UserRole::where('id', '>',1)
->column('name','id');
foreach($list as $v){
$data[] = [
'username' => \app\model\User::where('id',$v['user_id'])->value('username'),
'created_at' => $v['created_at'],
'v' => $v,
//'role' => $role_list[str_replace('购买用户组:','',$v['memo'])]
'role' => 'K'.str_replace('购买用户组:','',$v['memo'])
];
}
return $this->success(__('successful'),$data);
}
/**
* @Apidoc\Title("购买")
* @Apidoc\Method("POST")
* @Apidoc\Param("id", type="string",require=true, desc="要购买的ID")
* @Apidoc\Param("quantity", type="string",require=true, desc="要购买的数量(单位月)")
* @Apidoc\Param("trade_password", type="string",require=true, desc="交易密码")
*/
function buy(Request $request): Response{
$user = \support\Jwt::getUser();
$id = (int)$request->post('id');
//数量
$quantity = (int)$request->post('quantity',1);
/**
* @var \app\model\Thali $thali
*/
$thali = \app\model\Thali::where('id',$id)->find();
if(!$thali){
return $this->fail(__('Role does not exist'));
}
$role_id = $thali->role_id;
if($user->role_id > $role_id){
return $this->fail(__('Your level is too high to purchase this character'));
}
$price = $thali->price;
if($quantity == 1){
$price = $thali->month_price;
}
if($quantity == 3){
$price = $thali->quarter_price;
}
if($quantity == 12){
$price = $thali->year_price;
}
//新开通
$isNew=false;
if(is_null($user->role_id)){
$isNew = true;
}
//升级
$isUpgrade=true;
//续费
if($user->role_id == $role_id){
$isUpgrade = false;
}
$amount = $price;
if($isUpgrade){
//按那个价格算,目前是按原价,剩余时间不做抵扣
}
//$amount = $price * $quantity;
if($amount <=0){
return $this->fail(__('This character group is not allowed to be sold'));
}
if($user->score < $amount){
return $this->fail(__('Insufficient balance'));
}
\support\Jwt::verify_trade_password($request->post('trade_password'));
$user = \support\Jwt::getUser();
$user->expire_at = ($user->expire_at>time() ? $user->expire_at : time())+86400* $quantity * 30;
if($isUpgrade){
$user->expire_at = (time())+86400* $quantity * 30;
$user->role_id = $role_id;
}
$user->save();
cache('user_role_'.$user->userID,[
'role_id'=>$role_id,'expire_at'=>$user->expire_at
],$user->expire_at-time());
\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);
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);
}
}
-293
View File
@@ -1,293 +0,0 @@
<?php
namespace app\api\controller;
use Shopwwi\WebmanFilesystem\FilesystemFactory;
use Shopwwi\WebmanFilesystem\Facade\Storage;
use app\model\User as UserModel;
use app\model\Realname as RealnameModel;
use support\Request;
use support\Response;
use support\think\Db;
use hg\apidoc\annotation as Apidoc;
/**
* 用户相关
*/
class UserController extends BaseController{
/**
* 不需要鉴权的方法
* @var array
*/
public $noNeedAuth = ['*'];
/**
* 无需登录及鉴权的方法
* @var array
*/
public $noNeedLogin = [];
/**
* @Apidoc\Title("个人资料")
* @Apidoc\Method("GET","POST")
* @Apidoc\Tag("常用")
* @Apidoc\Desc("GET为获取用户信息,POST为修改数据")
* @Apidoc\Param("nickname", type="string",require=true, desc="昵称")
*/
public function profile()
{
$data = \support\Jwt::getUser();
if(Request()->method() == 'POST'){
$nickname = input('nickname');
$gender = input('gender',null);
$faceURL = input('faceURL',null);
$birth = input('birth',null);
$bio = input('bio',null);
$save_data =[];
if($nickname){
$save_data['nickname'] = $nickname;
}
if($gender!=null){
$save_data['sex'] = $gender;
}
if($faceURL){
$save_data['faceURL'] = $faceURL;
}
if($bio){
$save_data['bio'] = $bio;
}
if($birth){
$save_data['birthday'] = datetime($birth/1000,'Y-m-d');
}
if(!empty($save_data)){
\support\Jwt::getUser()->save($save_data);
}
return $this->success(__('successful'));
}
$data = \support\Jwt::getUserInfo($data);
$data= Hook('user.profile',$data);
return $this->success(__('successful'),$data[0]);
}
/**
* @Apidoc\Title("修改密码")
* @Apidoc\Method("POST")
* @Apidoc\Param("password", type="string",require=true, desc="旧密码")
* @Apidoc\Param("newpassword", type="string",require=true, desc="新密码")
* @Apidoc\Param("renewpassword", type="string",require=true, desc="新密码")
*/
public function change_password(){
$password = input('password');
$newpassword = input('newpassword');
$renewpassword = input('renewpassword');
if (!$password || !$newpassword || !$renewpassword) {
return $this->error(__('Invalid parameters'));
}
if ($newpassword !== $renewpassword) {
return $this->error(__('Invalid parameters'));
}
try{
\support\Jwt::changepwd($newpassword,$password);
return $this->success(__('Reset password successful'));
} catch (\Throwable $e) {
return $this->error($e->getMessage());
}
}
/**
* 修改交易密码
* @Apidoc\Method("POST")
* @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'));
}
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());
}
}
}
/**
* 根据关键字查询用户列表
* @Apidoc\Method("POST")
* @Apidoc\Param("kw", type="string",require=true, desc="关键字")
*/
function getuserlist(){
$kw = Input('kw');
$user_id = \support\Jwt\JwtToken::getCurrentId();
$list = [];
if($kw){
//$list = User::where('id','<>',\support\Jwt\JwtToken::getCurrentId())->whereLike('nickname|username|email','%'.$kw.'%')->limit(0,10)->order('id asc')->field('id,username')->select();
//$list = User::where('id','<>',\support\Jwt\JwtToken::getCurrentId())->whereLike('username','%'.$kw.'%')->limit(0,10)->order('id asc')->field('id,username,username as name')->select();
$list = UserModel::whereLike('username','%'.$kw.'%')->where('id','<>',$user_id)->limit(0,10)->order('id asc')->field('id,username,username as name')->select();
// foreach($list as $k=>$v){
// }
}
return $this->success(__('successful'),$list);
}
/**
* 头像上传
* @Apidoc\Method("POST")
* @Apidoc\Param("file", type="File", require=true, desc="文件")
*/
public function avatar(Request $request)
{
//单文件上传
$res = $this->_upload($request);
if(is_string($res)){
return $this->fail( $res);
}
$data = [
'avatar' => $res[0]['file_name'],
];
\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
{
/**
* @var UserModel $user
*/
$user = \support\Jwt::getUser();
if($request->method() == 'POST'){
$data = [
'realname' => Input('realname'),
'idcard' => Input('idcard'),
'user_id' => $user->id,
];
if(!$data['realname'] || !$data['idcard']){
return $this->error(__('Invalid parameter'));
}
if($user->realname_verify == 1){
return $this->error(__('You have verified'));
}
if(RealnameModel::where('idcard',$data['idcard'])->where('user_id','<>',$user->id)->count()){
return $this->error(__('ID card already exists'));
}
Db::startTrans();
try {
RealnameModel::create($data);
$user->realname_verify = 1;
$user->save();
if($user->parent_id && cache('invite_'.$user->parent_id.'_'.date('Ymd')) < 1){
\app\model\User::currency1($user->parent_id,40,\app\enum\BalanceType::INVITE_NEW_USER);
cache('invite_'.$user->parent_id.'_'.date('Ymd'),1);
}
Db::commit();
return $this->success('ok',$user);
} catch (\Exception $e) {
Db::rollback();
return $this->error(__($e->getMessage()));
}
}else{
$user->realname = RealnameModel::where('user_id',$user->id)->find();
return $this->success('ok',$user);
}
}
/**
* find
* @Apidoc\Method("POST")
* @Apidoc\Param("userIDs", type="array", require=true, desc="userIDs")
*/
function find(Request $request): Response
{
$ids = Input('userIDs');
if(is_string($ids)){
$ids = explode(',',$ids);
}
//$userIDs = array_map('\support\Encrypt::userIDDecode',$ids);
//$res = $request->IM->user->getUsersInfo($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){
$data = \support\Jwt::getUserInfo($user);
$data= Hook('user.profile',$data);
return $data[0];
//$user->hidden(['password']);
});
return $this->success('ok',$list);
}
/**
* search
* @Apidoc\Method("POST")
* @Apidoc\Param("keyword", type="string", require=true, desc="关键字")
* @Apidoc\Param("searchtype", type="string", require=true, desc="搜索类型")
*/
function search(Request $request): Response
{
$keyword = Input('keyword');
$searchtype = Input('searchtype');
$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{
// $model = $model->whereLike('username|id','%'.$keyword.'%');
// }
$list = $model->paginate(Input('limit',10));
$list->each(function ($item){
$item['id'] = $item['userID'];
return $item;
});
return $this->success('ok',$list);
}
}
-163
View File
@@ -1,163 +0,0 @@
<?php
namespace app\api\controller;
use app\model\User as UserModel;
use hg\apidoc\annotation as Apidoc;
/**
* 验证接口
*/
class ValidateController extends BaseController
{
public $noNeedLogin = '*';
/**
* 检测邮箱是否可用
*
* @Apidoc\Method ("POST")
* @Apidoc\Param("email", type="string",require=true, desc="邮箱")
* @Apidoc\Param("id", type="string",require=true, desc="排除会员ID")
*/
public function check_email_available()
{
$email = input('email');
$id = (int)input('id');
$count = UserModel::where('email', '=', $email)->where('id', '<>', $id)->count();
if ($count > 0) {
return $this->error(__('The mailbox is already occupied'));
}
return $this->success(__('successful'));
}
/**
* 检测用户名
*
* @Apidoc\Method ("POST")
* @Apidoc\Param("username", type="string",require=true, desc="用户名")
* @Apidoc\Param("id", type="string",require=true, desc="排除会员ID")
*/
public function check_username_available()
{
$username = input('username');
$id = (int)input('id');
$count = UserModel::where('username', '=', $username)->where('id', '<>', $id)->count();
if ($count > 0) {
return $this->error(__('Username is already taken'));
}
return $this->success(__('successful'));
}
/**
* 检测昵称
*
* @Apidoc\Method ("POST")
* @Apidoc\Param("nickname", type="string",require=true, desc="昵称")
* @Apidoc\Param("id", type="string",require=true, desc="排除会员ID")
*/
public function check_nickname_available()
{
$nickname = input('nickname');
$id = (int)input('id');
$count = UserModel::where('nickname', '=', $nickname)->where('id', '<>', $id)->count();
if ($count > 0) {
return $this->error(__('Nickname is already taken'));
}
return $this->success(__('successful'));
}
/**
* 检测手机
*
* @Apidoc\Method ("POST")
* @Apidoc\Param("mobile", type="string",require=true, desc="手机号")
* @Apidoc\Param("id", type="string",require=true, desc="排除会员ID")
*/
public function check_mobile_available()
{
$mobile = input('mobile');
$id = (int)input('id');
$count = UserModel::where('mobile', '=', $mobile)->where('id', '<>', $id)->count();
if ($count > 0) {
return $this->error(__('Phone Number is already taken'));
}
return $this->success(__('successful'));
}
/**
* 检测手机是否存在
*
* @Apidoc\Method ("POST")
* @Apidoc\Param("mobile", type="string",require=true, desc="手机号")
*/
public function check_mobile_exist()
{
$mobile = input('mobile');
$count = UserModel::where('mobile', '=', $mobile)->count();
if (!$count) {
return $this->error(__('Mobile number does not exist'));
}
return $this->success(__('successful'));
}
/**
* 检测邮箱是否存在
*
* @Apidoc\Method ("POST")
* @Apidoc\Param("email", type="string",require=true, desc="邮箱")
*/
public function check_email_exist()
{
$email = input('email');
$count = UserModel::where('email', '=', $email)->count();
if (!$count) {
return $this->error(__('Email does not exist'));
}
return $this->success(__('successful'));
}
/**
* 检测手机验证码(弃用)
*
* @Apidoc\Method ("POST")
* @Apidoc\Param("mobile", type="string",require=true, desc="手机号")
* @Apidoc\Param("code", type="string",require=true, desc="验证码")
* @Apidoc\Param("event", type="string",require=true, desc="事件")
*/
protected function check_sms_correct()
{
$mobile = input('mobile');
$captcha = input('captcha');
$event = input('event');
// if (!\app\common\library\Sms::check($mobile, $captcha, $event)) {
// $this->error(__('Incorrect verification code'));
// }
return $this->success(__('successful'));
}
/**
* 检测邮箱验证码
*
* @Apidoc\Method ("POST")
* @Apidoc\Param("email", type="string",require=true, desc="邮箱")
* @Apidoc\Param("code", type="string",require=true, desc="验证码")
* @Apidoc\Param("event", type="string",require=true, desc="事件")
*/
public function check_ems_correct()
{
$email = input('email');
$captcha = input('code');
$event = input('event');
$cache_key = 'captcha_'.$event.'_'.$email;
$list = cache($cache_key);
$list = $list?:[];
if(!isset($list[$captcha])){
return $this->error(__('Incorrect verification code'));
}
if($list[$captcha]+5*60 >= time()){
unset($list[$captcha]);
cache($cache_key,$list);
return $this->error(__('Verification code has expired'));
}
return $this->success(__('successful'));
}
}
-226
View File
@@ -1,226 +0,0 @@
<?php
namespace app\api\controller;
use app\model\User as UserModel;
use support\Request;
use app\model\Cdkey as CdkeyModel;
use support\think\Db;
use hg\apidoc\annotation as Apidoc;
/**
* 钱包接口
*/
class WalletController extends BaseController{
/**
* 不需要鉴权的方法
* @var array
*/
public $noNeedAuth = ['*'];
/**
* 无需登录及鉴权的方法
* @var array
*/
public $noNeedLogin = [];
/**
* 用户货币互换
* @Apidoc\Method("POST")
* @Apidoc\Param("currency", type="string",require=true, desc="货币money_to_score")
* @Apidoc\Param("sendAmount", type="string",require=true, desc="money兑换数量")
* @Apidoc\Param("receiveAmount", type="string",require=true, desc="score兑换数量")
* @Apidoc\Param("trade_password", type="string",require=true, desc="交易密码")
* @Apidoc\Param("code", type="string",require=true, desc="图形验证码(event=exchange)")
*/
public function exchange(){
//return $this->error(__('The system is under maintenance, please wait...'));
$user = \support\Jwt\JwtToken::getUser();
// if(Config('site.trade_password_type') == 'email'){
// captcha_verify('email','exchange',$user['username']);
// }else{
// $trade_password = input('trade_password');
// \support\Jwt::verify_trade_password($trade_password);
// }
$currency_pair = input('currency');
$currencys = explode('_to_', $currency_pair);
$from_currency = $currencys[0];
$to_currency = $currencys[1];
if(!$from_currency || !$to_currency){
return $this->error(__('Invalid parameters'));
}
$sendAmount = (float)input('sendAmount');
$receiveAmount = (float)input('receiveAmount');
$rate = Config('site.'.$currency_pair.'_rate');
if(!$sendAmount || !$receiveAmount || !$rate){
return $this->error(__('Invalid parameters'));
}
$_receiveAmount = intval($sendAmount / $rate);
$_sendAmount = $_receiveAmount * $rate;
if($sendAmount > $user->$from_currency || $receiveAmount <= 0){
return $this->error(__('Invalid parameters').$sendAmount .'<' .$user->$from_currency .'||'. $receiveAmount);
}
Db::startTrans();
try{
UserModel::$from_currency($user->id,-$_sendAmount,\app\enum\BalanceType::EXCHANGE);
UserModel::$to_currency($user->id,$_receiveAmount,\app\enum\BalanceType::EXCHANGE);
Db::commit();
return $this->success(__('Exchange successful'));
}catch(\Exception $e){
Db::rollback();
return $this->error($e->getMessage());
}
}
/**
* 用户间score转账
* @Apidoc\Method("POST")
* @Apidoc\Param("username", type="string",require=true, desc="收款用户/用户ID")
* @Apidoc\Param("amount", type="string",require=true, desc="金额")
* @Apidoc\Param("trade_password", type="string",require=true, desc="交易密码")
* @Apidoc\Param("code", type="string",require=true, desc="图形验证码(event=transfer)")
*/
public function transfer(){
//return $this->error(__('The system is under maintenance, please wait...'));
$user = \support\Jwt::getUser();
$username = input('username');
if(!$username){
return $this->error(__('User is incorrect'));
}
/** @var UserModel $to_user */
if(str_contains($username,'@')){
$to_user = UserModel::where('username',$username)->find();
}else{
$to_user_id = \support\Encrypt::userIDDecode($username);
$to_user = UserModel::where('id',$to_user_id)->find();
}
if(!$to_user){
return $this->error(__('User is incorrect'));
}
if(Config('site.trade_password_type') == 'email'){
//captcha_verify('email','transfer',$to_user['username']);
}else{
$trade_password = input('trade_password');
\support\Jwt::verify_trade_password($trade_password);
}
$amount = (float)input('amount');
if($amount <= 0){
return $this->error(__('Invalid parameters'));
}
if($user->score < $amount){
return $this->error(__('Insufficient balance'));
}
Db::startTrans();
try{
UserModel::score($user->id,-$amount,\app\enum\BalanceType::TRANSFER,$to_user->id);
UserModel::score($to_user->id,$amount,\app\enum\BalanceType::TRANSFER,$user->id);
Db::commit();
return $this->success(__('Transfer successful'));
}catch(\Exception $e){
Db::rollback();
return $this->error($e->getMessage());
}
}
/**
* 根据关键字查询用户列表
* @Apidoc\Method("POST")
* @Apidoc\Param("kw", type="string",require=true, desc="关键字")
*/
function getuserlist(){
$kw = Input('kw');
$user_id = \support\Jwt\JwtToken::getCurrentId();
$list = [];
if($kw){
//$list = User::where('id','<>',\support\Jwt\JwtToken::getCurrentId())->whereLike('nickname|username|email','%'.$kw.'%')->limit(0,10)->order('id asc')->field('id,username')->select();
//$list = User::where('id','<>',\support\Jwt\JwtToken::getCurrentId())->whereLike('username','%'.$kw.'%')->limit(0,10)->order('id asc')->field('id,username,username as name')->select();
$list = UserModel::whereLike('username','%'.$kw.'%')->where('id','<>',$user_id)->limit(0,10)->order('id asc')->field('id,username,username as name')->select();
// foreach($list as $k=>$v){
// }
}
return $this->success(__('successful'),$list);
}
/**
* 本地cdkey兑换
* @Apidoc\Method("POST")
* @Apidoc\Param("cdkey", type="string",require=true, desc="cdkey")
* @Apidoc\Param("trade_password", type="string",require=true, desc="交易密码")
* @Apidoc\Param("code", type="string",require=true, desc="图形验证码(event=cdkeyExchange)")
*/
public function cdkeyExchange_local_cdkey(){
//return $this->error(__('The system is under maintenance, please wait...'));
$user = \support\Jwt\JwtToken::getUser();
// if(Config('site.trade_password_type') == 'email'){
// captcha_verify('email','exchange',$user['username']);
// }else{
// $trade_password = input('trade_password');
// \support\Jwt::verify_trade_password($trade_password);
// }
$cdkey = input('cdkey');
/** @var CdkeyModel $Cdkey */
$Cdkey = CdkeyModel::where('account',$cdkey)->lock(true)->where('is_used',0)->find();
if(!$Cdkey){
return $this->error(__('卡密不存在'));
}
if($Cdkey['type'] == 3){
//不能使用续费激活码
return $this->error(__('卡密不存在'));
}
Db::startTrans();
try{
CdkeyModel::where('id',$Cdkey->id)->save([
'record_id' => $user->id,
'is_used' => 1,
'use_time' => time(),
]);
UserModel::score($user->id,$Cdkey->days,\app\enum\BalanceType::RECHARGE_CARD);
Db::commit();
return $this->success(__('Exchange successful'));
}catch(\Exception $e){
Db::rollback();
return $this->error($e->getMessage());
}
}
/**
* cdkey兑换
* @Apidoc\Method("POST")
* @Apidoc\Param("card_number", type="string",require=true, desc="卡号")
* @Apidoc\Param("password", type="string",require=true, desc="密码")
*/
function cdkey_exchange(){
$user = \support\Jwt\JwtToken::getUser();
$domain = 'http://127.0.0.1:8383';
$data=[
'user_id' => \support\Jwt\JwtToken::getCurrentId(),
'card_number'=> input('card_number'),
'password'=> input('password'),
];
$activeData = [
'app_id' => 8,
'card_number' => $data['card_number'],
'password' => $data['password'],
'type' => 'recharge',
'record_id' => $user->id
];
$remoteResponse = post($domain.'/api/cdkey/redeem',$activeData);
\support\Log::info($remoteResponse);
try{
$remoteResponse = json_decode($remoteResponse,true);
}catch(\Exception $e){
return $this->error($e->getMessage());
}
if($remoteResponse['code'] !== 0){
\support\Log::info(json_encode($remoteResponse));
return $this->error($remoteResponse['msg']);
}
if($remoteResponse['data']['days']){
UserModel::money($user->id,$remoteResponse['data']['days'],\app\enum\BalanceType::RECHARGE_CARD);
return $this->success(__('Exchange successful'));
}
return $this->error($remoteResponse['msg'],$remoteResponse);
}
}
-187
View File
@@ -1,187 +0,0 @@
<?php
namespace app\api\controller;
use app\model\Address as AddressModel;
use app\model\User as UserModel;
use app\model\Withdrawl as WithdrawlModel;
use support\Request;
use support\think\Db;
use hg\apidoc\annotation as Apidoc;
/**
* 提现模块
* @Apidoc\NotParse()
* @Apidoc\NotDebug()
*/
class WithdrawlController extends BaseController{
/**
* 不需要鉴权的方法
* @var array
*/
public $noNeedAuth = ['*'];
/**
* 无需登录及鉴权的方法
* @var array
*/
public $noNeedLogin = ['notify','notify1','recent'];
/**
* 列表
* @Apidoc\Method("GET")
* @Apidoc\Query("status", type="int", require=false, desc="状态")
* @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);
$status = input('status','all');
$model = WithdrawlModel::where('user_id',\support\Jwt\JwtToken::getCurrentId())
->order('id desc');
if($status!='all'){
$model = $model->where('status',$status);
}
$list = $model->paginate($limit);
return $this->success(__('successful'),$list->toArray());
}
/**
* 最近提现
* @Apidoc\Method("GET")
*/
public function recent()
{
$list = WithdrawlModel::with(['user'])->
where('status',\app\enum\WithdrawlStatus::COMPLETE->value)->
order('id desc')->
limit(10)->select();
$list->each(function($item){
$item->user = UserModel::field('username,email')->where('id',$item->user_id)->find();
});
return $this->success(__('successful'),$list->toArray());
}
/**
* 创建
* @Apidoc\Method("POST")
* @Apidoc\Param("amount", type="string", require=true, desc="金额")
* @Apidoc\Param("address_id", type="string", require=true, desc="地址ID,列表选择")
* @Apidoc\Param("trade_password", type="string", require=true, desc="交易密码")
*/
public function create()
{
//return $this->error(__('The system is under maintenance, please wait...'));
//* @Apidoc\Param("code", type="string", require=true, desc="图形验证码(type=withdrawl)")
//captcha_verify('image','withdrawl');
$address_id = input('address_id');
if(!$address_id){
return $this->error(__('Address is incorrect'));
}
/** @var AddressModel $address */
$address = AddressModel::where('id',$address_id)->find();
if(!$address){
return $this->error(__('Address is incorrect'));
}
// if(!$address->status){
// return $this->error(__('Unverified address'));
// }
$user = \support\Jwt::getUser();
if(Config('site.trade_password_type') == 'email'){
captcha_verify('email','withdrawl',$user['username']);
}else{
//验证交易密码
$trade_password = input('trade_password');
\support\Jwt::verify_trade_password($trade_password);
}
$deduction_amount = input('amount',0);
$fee = config('site.withdrawl_fee')[$address['network']];
if($fee < 0.5){
$fee = bcmul( $fee , $deduction_amount,2);
}
$data = [
'user_id' => \support\Jwt\JwtToken::getCurrentId(),
'deduction_amount' => $deduction_amount,
'title' => $address['title'],
'network' => $address['network'],
'address' => $address['address'],
'fee' => $fee,
'type' => 0,
'status' => \app\enum\WithdrawlStatus::CREATED->value
];
//验证最小提现金额
$data['recive_amount'] = $data['deduction_amount'] - $data['fee'];
$withdrawl_minimum = Config('site.withdrawl_minimum')[$data['network']];
if($data['deduction_amount'] < $withdrawl_minimum){
return $this->error(__('Minimum withdrawal of %num%',[
'%num%' => $withdrawl_minimum
]));
}
//var_dump($user);
//验证余额
if($data['deduction_amount'] > $user->money){
return $this->error(__('The amount exceeds the available balance'));
}
//if(WithdrawlModel::whereTime('created_at','-24 hours')->count('id')){
if(WithdrawlModel::whereTime('created_at','today')->where('user_id',$data['user_id'])->count('id')){
return $this->error(__('You can only withdraw once a day.'));
}
if (!$data['network'] || !in_array($data['network'],['BEP-20','TRC-20','ALIPAY','WECHAT'])) {
return $this->error(__('Network is incorrect'));
}
if (!$data['address']) {
return $this->error(__('Address is incorrect'));
}
Db::startTrans();
try{
/** @var WithdrawlModel $data */
$data = WithdrawlModel::create($data);
UserModel::money($data->user_id,-$data->deduction_amount,\app\enum\BalanceType::WITHDRAWAL,$data->id);
Db::commit();
return $this->success(__('successful'),$data);
}catch(\Exception $e){
Db::rollback();
return $this->error($e->getMessage());
}
}
/**
* 详情
* @Apidoc\Query("id", type="string", require=true, desc="ID")
*/
public function detail(){
$appid = input('id');
$vo = WithdrawlModel::where('id',$appid)->find();
if($vo) {
return $this->success(__('successful'),$vo->toArray());
}else{
return $this->error(__("Record does not exist"));
}
}
/**
* 转账成功异步通知
* @Apidoc\NotParse()
* @Apidoc\NotDebug()
*/
public function notify(){
$data = \support\Encrypt::aesdecode(input('data',''));
$data = json_decode($data,true);
/** @var WithdrawlModel $vo */
$vo = WithdrawlModel::where('id',$data['out_trade_no'])->find();
if($vo){
if($data['result'] == 'SUCCESS'){
if($vo->status != \app\enum\WithdrawlStatus::COMPLETE->value){
$vo->status = \app\enum\WithdrawlStatus::COMPLETE->value;
$vo->txid = $data['txid'];
$vo->transfer_at = $data['transfer_at'] ?: time();
$vo->save();
Hook('withdrawl.success',$vo);
}
}else{
$vo->status = \app\enum\WithdrawlStatus::FAIL->value;
$vo->txid = $data['txid'];
$vo->memo = $data['reason'];
$vo->save();
}
}
return response("SUCCESS");
}
}
-149
View File
@@ -1,149 +0,0 @@
<?php
namespace app\api\middleware;
use ReflectionException;
use support\exception\BusinessException;
use Webman\Http\Request;
use Webman\Http\Response;
use Webman\MiddlewareInterface;
use support\Container;
class Auth implements MiddlewareInterface
{
/**
* @param Request $request
* @param callable $handler
* @return Response
* @throws ReflectionException|BusinessException
*/
public function process(Request $request, callable $next): Response
{
$headers = [
'Access-Control-Allow-Credentials' => 'true',
'Access-Control-Allow-Origin' => $request->header('origin', '*'),
'Access-Control-Allow-Methods' => $request->header('access-control-request-method', '*'),
'Access-Control-Allow-Headers' => $request->header('access-control-request-headers', '*'),
];
if($request->method() == 'OPTIONS'){
$response = response('',204,$headers);
return $response;
}
$lang = $request->header('lang','zh-Hans');
locale($lang);
if ($request->controller) {
$request->client = $request->header('client',"web");
// if($request->client=='win' && $request->header('version') < 2.06){
// abort('旧版本不能再使用,请更新到最新版本', 603);
// }
//跨域请求检测
//check_cors_request();
// 检测IP是否允许
//check_ip_allowed();
$request->start_time = microtime();
$controller = Container::get($request->controller);
// 检测是否需要验证登录
if (!\support\Jwt::match($controller->noNeedLogin)) {
//检测是否登录
try {
if (!\support\Jwt::isLogin()) {
return new Response(401,$headers,json_encode([
"code"=>401,
"data"=>[],
"msg"=>__('Please login first')
]));
}
} catch (\Exception $e) {
return new Response(401,$headers,json_encode([
"code"=>401,
"data"=>[],
"msg"=>__('Please login first')
]));
}
$user = \support\Jwt\JwtToken::getUser();
if(!$user['status']){
return new Response(403,$headers,json_encode([
"code"=>403,
"data"=>[],
"msg"=>__('Account is locked')
]));
}
// $key = "debounce_" . $request->path() . "_" . ($user->id ?? 'guest');
// $ttl = 1; // 防抖时间(秒)
// $redishandler = new \Redis;
// $redishandler->connect(
// \support\Env::get('host'),
// (int) \support\Env::get('port'),
// (int) \support\Env::get('timeout'));
// $redishandler->select(12);
// if ($redishandler->setnx($key, 1)) {
// $redishandler->expire($key, $ttl);
// }else{
// return new Response(429,[],__('Too frequent operation'));
// }
// 判断是否需要验证权限
if (!\support\Jwt::match($controller->noNeedAuth)) {
// 判断控制器和方法判断是否有对应权限
$controllername = get_controller_name();
$actionname = strtolower(get_action_name());
$path = str_replace('.', '/', $controllername) . '/' . $actionname;
if (!\support\Jwt::check($path)) {
return new Response(405,$headers,json_encode([
"code"=>405,
"data"=>[],
"msg"=>__('have no permission')
]));
}
}
}
// if($request->client!='web'){
// $data = $request->post('data');
// if($data){
// $data = str_replace('%3D','=',$data);
// $data = str_replace(' ','+',$data);
// //var_dump($data);
// $data = \support\Encrypt::aesdecode($data);
// $data = json_decode($data,true);
// //var_dump($data);
// $request->withBody($data);
// }
// }
$config = Config('site');
$config['debug'] = config('app.debug');
$config['controller'] = $request->controller_name;
$config['action'] = $request->action_name;
$request->_view_vars = array_merge((array) $request->_view_vars,[
'user' => session('admin'),
'config' => $config
]);
$IM = new \support\OpenImSdk\Client([
'host' => config('openim.server'), // OpenIM API地址
'secret' => config('openim.secret'), // OpenIM密钥
]);
$request->IM = $IM;
$response = $next($request);
//cp('auth');
//\support\Log::alert('auth');
$body = str_replace([
'__SELF__'
],[
request()->path()
],$response->rawBody());
// if($request->app=="api" && $request->client!='web'){
// $body = \support\Encrypt::aesencode($body);
// }
$response->withHeaders($headers)->withBody($body)->getStatusCode();
$time = microtime() - $request->start_time;
//echo("响应时间:".$request->uri().':'.$time.PHP_EOL);
//$response = $next($request);
//\support\Log::error($response->rawBody());
return $response;
}
return $next($request);
}
}
-51
View File
@@ -1,51 +0,0 @@
<?php
/**
* This file is part of webman.
*
* Licensed under The MIT License
* For full copyright and license information, please see the MIT-LICENSE.txt
* Redistributions of files must retain the above copyright notice.
*
* @author walkor<walkor@workerman.net>
* @copyright walkor<walkor@workerman.net>
* @link http://www.workerman.net/
* @license http://www.opensource.org/licenses/mit-license.php MIT License
*/
namespace app\bootstrap;
use Webman\Bootstrap;
//use support\Db;
use Symfony\Component\VarDumper\Cloner\VarCloner;
use Symfony\Component\VarDumper\Dumper\CliDumper;
use support\think\Db;
class SqlDebug implements Bootstrap
{
/**
* 自定义输出格式,否则输出前面会带有当前文件,无用信息
* @param $var
* @return void
*/
public static function dumpvar($var): void
{
$cloner = new VarCloner();
$dumper = new CliDumper();
$dumper->dump($cloner->cloneVar($var));
}
public static function start($worker)
{
// Is it console environment ?
$is_console = !$worker;
// if ($is_console) {
// return;
// }
if (!Config("app.debug")) return;
Db::listen(function($sql, $runtime, $master) {
if (!Config("app.debug")) return;
if($sql!='select 1' && $sql){
$sql= preg_replace('/db\.[db\.]+/', 'db.', $sql);
\support\Log::alert('['.$runtime.']'.$sql);
}
});
}
}
-807
View File
@@ -1,807 +0,0 @@
<?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}";
}
}
}
-47
View File
@@ -1,47 +0,0 @@
<?php
namespace app\command;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Attribute\AsCommand;
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 app\model\BalanceLog;
class BalanceLogTask extends Command
{
protected static $defaultName = 'balance:log-task';
protected static $defaultDescription = 'balance:log-task';
protected function configure()
{
}
protected function execute(InputInterface $input, OutputInterface $output):int
{
// 1. 确保索引存在
$output->writeln('Creating indexes...');
$indexResults = BalanceLog::createAllIndexes();
foreach ($indexResults as $currency => $messages) {
$output->writeln("[$currency]");
foreach ($messages as $message) {
$output->writeln(" - $message");
}
}
// 2. 执行数据归档
$output->writeln('Archiving old data...');
$archiveResults = BalanceLog::archiveData(3); // 归档3天前的数据
foreach ($archiveResults as $currency => $result) {
$output->writeln("[$currency]");
$output->writeln(" - Table: {$result['table']}");
$output->writeln(" - Archived: {$result['archived']} records");
foreach ($result['messages'] as $message) {
$output->writeln(" - $message");
}
}
$output->writeln('All tasks completed!');
return self::SUCCESS;
}
}
-60
View File
@@ -1,60 +0,0 @@
<?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;
}
}
-60
View File
@@ -1,60 +0,0 @@
<?php
namespace app\command;
use Exception;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use think\db\PDOConnection;
use support\think\Db;
class Clear extends Command
{
protected static $defaultName = 'clear';
protected static $defaultDescription = '数据库缓存';
protected function configure()
{
$this->setDescription('clear database.');
}
protected function execute(InputInterface $input, OutputInterface $output): int
{
$action = 'all';
if($action == 'all'){
Db::name('address')->where('id','>',0)->delete();
Db::name('recharge')->where('id','>',0)->delete();
Db::name('transfer')->where('id','>',0)->delete();
Db::name('user')->where('id','>',0)->delete();
Db::name('user_extend')->where('user_id','>',0)->delete();
Db::name('user_team')->where('descendant_id|ancestor_id','>',0)->delete();
Db::name('withdrawl')->where('id','>',0)->delete();
Db::name('work_record')->where('id','>',0)->delete();
}else{
$list = \app\model\User::order('id','asc')->select();
foreach($list as $k=>$user){
Db::name('address')->where('user_id',$user->id)->delete();
Db::name('transfer')->where('user_id',$user->id)->delete();
Db::name('recharge')->where('user_id',$user->id)->delete();
Db::name('record')->where('user_id',$user->id)->delete();
Db::name('withdrawl')->where('user_id',$user->id)->delete();
Db::name('user_extend')->where('user_id',$user->id)->delete();
Db::name('user_team')->where('descendant_id|ancestor_id','=',$user->id)->delete();
Db::name('withdrawl')->where('user_id',$user->id)->delete();
Db::name('work_record')->where('user_id',$user->id)->delete();
Db::name('user')->where('id',$user->id)->delete();
}
}
$output->writeln('<info>Succeed!</info>');
return self::SUCCESS;
}
protected function buildModelSchema(string $class): void
{
}
}
-261
View File
@@ -1,261 +0,0 @@
<?php
namespace app\command;
use Exception;
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;
use app\model\User as UserModel;
use \think\db\PDOConnection;
use think\db\exception\PDOException;
use think\db\exception\InvalidArgumentException;
class Database extends Command
{
protected static $defaultName = 'Db';
protected static $defaultDescription = 'Database 优化';
/**
* @return void
*/
protected function configure()
{
$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');
}
/**
* @param InputInterface $input
* @param OutputInterface $output
* @return int
*/
protected function execute(InputInterface $input, OutputInterface $output): int
{
$action = $input->getOption('action');
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');
// 获取表前缀并构建完整表名
$prefix = config('thinkorm.connections.mysql.prefix', '');
$fullTableName = '`' . $prefix . $table . '`';
// 查询表结构
$res = Db::query('SHOW FULL COLUMNS FROM ' . $fullTableName);
if (empty($res)) {
return "// 表 {$table} 不存在或没有字段";
}
$annotations = [];
$annotations[] = '/**';
foreach ($res as $row) {
$field = $row['Field'];
$type = $row['Type'];
$comment = $row['Comment'] ?: '无注释';
// 处理字段类型映射
$phpType = $this->mapMysqlTypeToPhp($type);
// 处理特殊字段
if ($field === 'id') {
$annotations[] = " * @property integer \${$field} 主键(ID) - {$comment}";
} else {
$annotations[] = " * @property {$phpType} \${$field} {$comment}";
}
}
$annotations[] = ' */';
cp( implode("\n", $annotations));
return self::SUCCESS;
}
/**
* 将MySQL字段类型映射到PHP类型
*
* @param string $mysqlType MySQL字段类型
* @return string PHP类型
*/
protected function mapMysqlTypeToPhp($mysqlType)
{
$mysqlType = strtolower($mysqlType);
// 整数类型
if (preg_match('/^(tinyint|smallint|mediumint|int|bigint)/', $mysqlType)) {
// 检查是否为无符号
if (strpos($mysqlType, 'unsigned') !== false) {
return 'integer'; // 无符号整数也返回integer
}
return 'integer';
}
// 浮点类型
if (preg_match('/^(float|double|decimal)/', $mysqlType)) {
return 'float';
}
// 字符串类型
if (preg_match('/^(varchar|char|text|tinytext|mediumtext|longtext|enum|set)/', $mysqlType)) {
return 'string';
}
// 日期时间类型
if (preg_match('/^(date|time|datetime|timestamp|year)/', $mysqlType)) {
return 'string'; // 或者可以返回 '\\DateTime' 如果需要更精确的类型
}
// 二进制类型
if (preg_match('/^(blob|tinyblob|mediumblob|longblob|binary|varbinary)/', $mysqlType)) {
return 'string'; // 或者根据需求返回其他类型
}
// JSON类型
if (strpos($mysqlType, 'json') !== false) {
return 'array'; // 或者 'mixed'
}
// 布尔类型(tinyint(1)通常用作布尔值)
if ($mysqlType === 'tinyint(1)' || $mysqlType === 'boolean' || $mysqlType === 'bool') {
return 'boolean';
}
// 默认返回混合类型
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;
}
}
-273
View File
@@ -1,273 +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\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("═══════════════════════════════════════════════════════════");
}
}
-101
View File
@@ -1,101 +0,0 @@
<?php
namespace app\command;
use Exception;
use plugin\admin\app\model\Config;
use support\Request;
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 Language extends Command
{
protected static $defaultName = 'Language:scan';
protected static $defaultDescription = '自动完成多语言的文件提取';
/**
* @return void
*/
protected function configure()
{
$this->addOption('file','f', InputArgument::OPTIONAL, '只是针对那个文件');
}
/**
* @param InputInterface $input
* @param OutputInterface $output
* @return int
*/
protected function execute(InputInterface $input, OutputInterface $output): int
{
$file = $input->getOption('file');
$file = base_path()."/app/api/controller/ServerController.php";
$dir = new \RecursiveDirectoryIterator(base_path().'/app');
$iterator = new \RecursiveIteratorIterator($dir);
$phpFiles = new \RegexIterator($iterator, '/^.+\.php$/i', \RecursiveRegexIterator::GET_MATCH);
$fnlist = [];
$result = [
'common.php'=>""
];
foreach ($phpFiles as $_file) {
$_file = $_file[0];
$fnlist[] = $_file;
$key = 'common.php';
if(false !==strpos($_file, base_path().'/app/api/controller')){
$key = 'api/'.pathinfo($_file,PATHINFO_BASENAME);
}
if(false !==strpos($_file, base_path().'/app/controller')){
$key = pathinfo($_file,PATHINFO_BASENAME);
}
$key = strtolower(str_replace('Controller','',$key));
//cp($key);
$res = $this->parseOneFile($_file);
$result[$key]=$res;
}
//$res = $this->parseOneFile($file);
//cp($result);
$this->write2file($result);
return 0;
}
function write2file($data=[]){
$langs = ['zh-Hans','en'];
foreach($data as $fn=>$arr){
foreach($langs as $lang){
$lang_path = base_path('/resource/translations/'.$lang.'/');
$_common_arr = require($lang_path.'common.php');
$_arr = [];
if(file_exists($lang_path.$fn)){
$_arr = require($lang_path.$fn);
}
foreach($arr as $ov){
if(!isset($_common_arr[$ov]) && !isset($_arr[$ov])){
$_arr[$ov]=$ov;
}
if(isset($_common_arr[$ov]) && isset($_arr[$ov])){
unset($_arr[$ov]);
}
}
file_put_contents($lang_path.$fn,'<?php'.PHP_EOL.'return '.var_export($_arr,true).';');
//cp('写入文件:'.$lang_path.$fn);
}
}
}
function parseOneFile($fn){
cp('解析文件:',$fn);
if(file_exists($fn)){
$content = file_get_contents($fn);
$matchs = [];
preg_match_all('/__\(([\'"])(.*?)\1\s*(?:,\s*\[.*?\])?\)/',$content,$matchs);
//cp($matchs[2]);
return $matchs[2];
}else{
cp('文件不存在:'.$fn);
}
}
}
-872
View File
@@ -1,872 +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\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;
}
}
}
-173
View File
@@ -1,173 +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 Min extends Command
{
protected static $defaultName = 'Min';
protected static $defaultDescription = '压缩静态文件';
/**
* 路径和文件名配置
*/
protected $options = [
'cssBaseUrl' => 'public/assets/css/',
'cssBaseName' => '{module}',
'jsBaseUrl' => 'public/assets/js/',
'jsBaseName' => 'require-{module}',
];
/**
* @return void
*/
protected function configure()
{
$this->addOption('module', 'm', InputArgument::REQUIRED, 'module name(frontend or backend),use \'all\' when build all modules', null)
->addOption('resource', 'r', InputArgument::REQUIRED, 'resource name(js or css),use \'all\' when build all resources', null)
->addOption('optimize', 'o', InputArgument::OPTIONAL, 'optimize type(uglify|closure|none)', 'none')
->setDescription('Compress js and css file');
}
/**
* @param InputInterface $input
* @param OutputInterface $output
* @return int
*/
protected function execute(InputInterface $input, OutputInterface $output): int
{
$module = $input->getOption('module') ?: '';
$resource = $input->getOption('resource') ?: '';
$optimize = $input->getOption('optimize') ?: 'none';
if (!$module || !in_array($module, ['frontend', 'backend', 'all'])) {
throw new \Exception('Please input correct module name');
}
if (!$resource || !in_array($resource, ['js', 'css', 'all'])) {
throw new \Exception('Please input correct resource name');
}
$moduleArr = $module == 'all' ? ['frontend', 'backend'] : [$module];
$resourceArr = $resource == 'all' ? ['js', 'css'] : [$resource];
$minPath = __DIR__ . DIRECTORY_SEPARATOR . 'Min' . DIRECTORY_SEPARATOR;
$publicPath = public_path() . DIRECTORY_SEPARATOR;
$tempFile = $minPath . 'temp.js';
$nodeExec = '';
if (!$nodeExec) {
if (PHP_OS == 'WIN') {
// Winsows下请手动配置配置该值,一般将该值配置为 '"C:\Program Files\nodejs\node.exe"',除非你的Node安装路径有变更
$nodeExec = 'C:\Program Files\nodejs\node.exe';
if (file_exists($nodeExec)) {
$nodeExec = '"' . $nodeExec . '"';
} else {
// 如果 '"C:\Program Files\nodejs\node.exe"' 不存在,可能是node安装路径有变更
// 但安装node会自动配置环境变量,直接执行 '"node.exe"' 提高第一次使用压缩打包的成功率
$nodeExec = '"node.exe"';
}
} else {
try {
$nodeExec = exec("which node");
if (!$nodeExec) {
throw new \Exception("node environment not found!please install node first!");
}
} catch (\Exception $e) {
throw new \Exception($e->getMessage());
}
}
}
foreach ($moduleArr as $mod) {
foreach ($resourceArr as $res) {
$data = [
'publicPath' => $publicPath,
'jsBaseName' => str_replace('{module}', $mod, $this->options['jsBaseName']),
'jsBaseUrl' => $this->options['jsBaseUrl'],
'cssBaseName' => str_replace('{module}', $mod, $this->options['cssBaseName']),
'cssBaseUrl' => $this->options['cssBaseUrl'],
'jsBasePath' => str_replace(DIRECTORY_SEPARATOR, '/', public_path() . $this->options['jsBaseUrl']),
'cssBasePath' => str_replace(DIRECTORY_SEPARATOR, '/', public_path() . $this->options['cssBaseUrl']),
'optimize' => $optimize,
'ds' => DIRECTORY_SEPARATOR,
];
//源文件
$from = $data["{$res}BasePath"] . $data["{$res}BaseName"] . '.' . $res;
if (!is_file($from)) {
$output->writeln("{$res} source file not found!file:{$from}");
continue;
}
if ($res == "js") {
$content = file_get_contents($from);
preg_match("/require\.config\(\{[\r\n]?[\n]?+(.*?)[\r\n]?[\n]?}\);/is", $content, $matches);
if (!isset($matches[1])) {
$output->writeln("js config not found!");
continue;
}
$config = preg_replace("/(urlArgs|baseUrl):(.*)\n/", '', $matches[1]);
$config = preg_replace("/('tableexport'):(.*)\,\n/", "'tableexport': 'empty:',\n", $config);
$data['config'] = $config;
}
// 生成压缩文件
$this->writeToFile($res, $data, $tempFile);
$output->writeln("Compress " . $data["{$res}BaseName"] . ".{$res}");
// 执行压缩
$command = "{$nodeExec} \"{$minPath}r.js\" -o \"{$tempFile}\" >> \"{$minPath}node.log\"";
if ($output->isDebug()) {
$output->writeln($command);
}
echo exec($command);
}
}
if (!$output->isDebug()) {
@unlink($tempFile);
}
$output->writeln("Build Successed!");
return self::SUCCESS;
}
/**
* 写入到文件
* @param string $name
* @param array $data
* @param string $pathname
* @return mixed
*/
protected function writeToFile($name, $data, $pathname)
{
$search = $replace = [];
foreach ($data as $k => $v) {
$search[] = "{%{$k}%}";
$replace[] = $v;
}
$stub = file_get_contents($this->getStub($name));
$content = str_replace($search, $replace, $stub);
if (!is_dir(dirname($pathname))) {
mkdir(strtolower(dirname($pathname)), 0755, true);
}
return file_put_contents($pathname, $content);
}
/**
* 获取基础模板
* @param string $name
* @return string
*/
protected function getStub($name)
{
return __DIR__ . DIRECTORY_SEPARATOR . 'Min' . DIRECTORY_SEPARATOR . 'stubs' . DIRECTORY_SEPARATOR . $name . '.stub';
}
}
-6
View File
@@ -1,6 +0,0 @@
({
cssIn: "{%cssBasePath%}{%cssBaseName%}.css",
out: "{%cssBasePath%}{%cssBaseName%}.min.css",
optimizeCss: "default",
optimize: "{%optimize%}"
})
-11
View File
@@ -1,11 +0,0 @@
({
{%config%}
,
optimizeCss: "standard",
optimize: "{%optimize%}", //可使用uglify|closure|none
preserveLicenseComments: false,
removeCombined: false,
baseUrl: "{%jsBasePath%}", //JS文件所在的基础目录
name: "{%jsBaseName%}", //来源文件,不包含后缀
out: "{%jsBasePath%}{%jsBaseName%}.min.js" //目标文件
});
File diff suppressed because it is too large Load Diff
-158
View File
@@ -1,158 +0,0 @@
<?php
namespace app\command;
use Exception;
use plugin\admin\app\model\Config;
use app\model\User as UserModel;
use support\Request;
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;
use support\Mattermost;
use app\model\User;
class Tongji extends Command
{
protected static $defaultName = 'Tongji';
protected static $defaultDescription = 'Tongji';
/**
* @return void
*/
protected function configure()
{
$this->addOption('action','a', InputArgument::OPTIONAL, '要做什么操作');
$this->addOption('user_id','uid', InputArgument::OPTIONAL, 'user_id');
}
/**
* @param InputInterface $input
* @param OutputInterface $output
* @return int
*/
protected function execute(InputInterface $input, OutputInterface $output): int
{
$action = $input->getOption('action');
if(method_exists($this, $action)){
return $this->$action($input, $output);
}
cp('操作不存在:'.$action);
return self::SUCCESS;
}
/**
* 修复充值统计
*/
function recharge(InputInterface $input, OutputInterface $output) {
//购买金额统计
$recharge = \app\model\Recharge::where('status',\app\enum\RechargeStatus::COMPLETE->value)->select();
$recharge_result = [];
$statistics_recharge_times_result = [];
$user_recharge_total_result = [];
$team_recharge_total_result = [];
/**
* @var \app\model\Recharge $vo
*/
foreach($recharge as $vo){
$date = explode(' ',$vo->created_at)[0];
$recharge_result[$date] += abs($vo->amount);
$statistics_recharge_times_result[$date] += 1;
$user_recharge_total_result[$vo->user_id.''] +=abs($vo->amount);
$parent_id = get_parent_id($vo->user_id);
if($parent_id){
//团队提现统计
$team_recharge_total_result[$parent_id.''] +=abs($vo->amount);
}
}
foreach($recharge_result as $date => $value){
cache('statistics_recharge_amount_'.$date,$value);
}
foreach($statistics_recharge_times_result as $date => $value){
cache('statistics_recharge_times_'.$date,$value);
}
foreach($user_recharge_total_result as $user_id => $value){
cache('user_recharge_total_'.$user_id,$value);
}
foreach($team_recharge_total_result as $user_id => $value){
cache('team_recharge_total_'.$user_id,$value);
}
cp($recharge_result);
}
/**
* 修复提现统计
*/
function withdrawl(InputInterface $input, OutputInterface $output) {
//购买金额统计
$withdrawl = \app\model\Withdrawl::where('status',\app\enum\WithdrawlStatus::COMPLETE->value)->select();
$withdrawl_result = [];
$statistics_withdrawl_times_result = [];
$user_withdrawl_total_result = [];
$team_withdrawl_total_result = [];
/**
* @var \app\model\Withdrawl $vo
*/
foreach($withdrawl as $vo){
$date = explode(' ',$vo->created_at)[0];
$withdrawl_result[$date] += abs($vo->recive_amount);
$statistics_withdrawl_times_result[$date] += 1;
$user_withdrawl_total_result[$vo->user_id.''] +=abs($vo->recive_amount);
$parent_id = get_parent_id($vo->user_id);
if($parent_id){
//团队提现统计
$team_withdrawl_total_result[$parent_id.''] +=abs($vo->recive_amount);
}
}
foreach($withdrawl_result as $date => $value){
cache('statistics_withdrawl_amount_'.$date,$value);
}
foreach($statistics_withdrawl_times_result as $date => $value){
cache('statistics_withdrawl_times_'.$date,$value);
}
foreach($user_withdrawl_total_result as $user_id => $value){
cache('user_withdrawl_total_'.$user_id,$value);
}
foreach($team_withdrawl_total_result as $user_id => $value){
cache('team_withdrawl_total_'.$user_id,$value);
}
cp($withdrawl_result);
}
/**
* 修复团队统计数据
* @param InputInterface $input
* @param OutputInterface $output
* @return int
*/
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.'完成');
}
return self::SUCCESS;
}
}
-137
View File
@@ -1,137 +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;
use isszz\hashids\facade\Hashids;
use function GuzzleHttp\json_encode;
class User extends Command
{
protected static $defaultName = 'User';
protected static $defaultDescription = '用户';
public $sdk= null;
/**
* @return void
*/
protected function configure()
{
$this->addOption('user_id','u', InputOption::VALUE_OPTIONAL, 'user_id');
$this->addOption('action','a', InputOption::VALUE_OPTIONAL, '操作类型','test');
}
/**
* @param InputInterface $input
* @param OutputInterface $output
* @return int
*/
protected function execute(InputInterface $input, OutputInterface $output): int
{
$action = $input->getOption('action');
if(method_exists($this, $action)){
return $this->$action($input, $output);
}
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
View File
@@ -1,37 +0,0 @@
<?php
namespace app\command;
use Monolog\Formatter\MongoDBFormatter;
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 createIndex extends Command
{
protected static $defaultName = 'createIndex';
protected static $defaultDescription = 'createIndex';
/**
* @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
{
\app\model\BalanceLog::createindex();
return 0;
}
}
-46
View File
@@ -1,46 +0,0 @@
<?php
/**
* This file is part of webman.
*
* Licensed under The MIT License
* For full copyright and license information, please see the MIT-LICENSE.txt
* Redistributions of files must retain the above copyright notice.
*
* @author walkor<walkor@workerman.net>
* @copyright walkor<walkor@workerman.net>
* @link http://www.workerman.net/
* @license http://www.opensource.org/licenses/mit-license.php MIT License
*/
namespace app\api\exception;
use Throwable;
use Webman\Exception\ExceptionHandler;
use Webman\Http\Request;
use Webman\Http\Response;
/**
* Class Handler
* @package sapp\api\exception
*/
class Handler extends ExceptionHandler
{
public $dontReport = [
\support\exception\BusinessException::class,
];
public function report(Throwable $exception)
{
parent::report($exception);
}
public function render(Request $request, Throwable $exception): Response
{
$code = $exception->getCode();
$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));
}
}
-67
View File
@@ -1,67 +0,0 @@
<?php
namespace app\controller;
use think\Model;
use support\Response;
/**
* 基础控制器
*/
class Base
{
/**
* @var Model
*/
protected $model = null;
/**
* 无需登录及鉴权的方法
* @var array
*/
protected $noNeedLogin = [];
/**
* 需要登录无需鉴权的方法
* @var array
*/
protected $noNeedAuth = [];
/**
* 数据限制
* null 不做限制,任何管理员都可以查看该表的所有数据
* auth 管理员能看到自己以及自己的子管理员插入的数据
* personal 管理员只能看到自己插入的数据
* @var string
*/
protected $dataLimit = null;
/**
* 数据限制字段
*/
protected $dataLimitField = 'admin_id';
/**
* 返回格式化json数据
*
* @param int $code
* @param string $msg
* @param array $data
* @return Response
*/
protected function json(int $code, string $msg = 'ok', array $data = []): Response
{
return json(['code' => $code, 'data' => $data, 'msg' => $msg]);
}
protected function success(string $msg = '成功', array $data = []): Response
{
return $this->json(0, $msg, $data);
}
protected function fail(string $msg = '失败', array $data = []): Response
{
return $this->json(1, $msg, $data);
}
}
-31
View File
@@ -1,31 +0,0 @@
<?php
namespace app\controller;
use Exception;
use support\exception\BusinessException;
use support\Request;
use support\Response;
class CommonController extends Crud
{
/**
* 后台主页
* @param Request $request
* @return Response
* @throws BusinessException|Exception
*/
public function register(Request $request,$code='')
{
if($code){
return view('common/register',[
'config' => Config('site'),
'invite_code' => $code
]);
}else{
return $this->fail('404');
}
}
}
-440
View File
@@ -1,440 +0,0 @@
<?php
namespace app\controller;
use Illuminate\Database\Eloquent\Builder as EloquentBuilder;
use Illuminate\Database\Query\Builder as QueryBuilder;
use support\exception\BusinessException;
use support\think\Model;
use support\Request;
use support\Response;
use support\think\Db;
class Crud extends Base
{
/**
* @var Model
*/
protected $model = null;
/**
* 查询
* @param Request $request
* @return Response
* @throws BusinessException
*/
public function select(Request $request): Response
{
[$where, $format, $limit, $field, $order] = $this->selectInput($request);
$query = $this->doSelect($where, $field, $order);
return $this->doFormat($query, $format, $limit);
}
/**
* 添加
* @param Request $request
* @return Response
* @throws BusinessException
*/
public function insert(Request $request): Response
{
$data = $this->insertInput($request);
$id = $this->doInsert($data);
return $this->success(__('successful'), ['id' => $id]);
}
/**
* 更新
* @param Request $request
* @return Response
* @throws BusinessException
*/
public function update(Request $request): Response
{
[$id, $data] = $this->updateInput($request);
$this->doUpdate($id, $data);
return $this->success(__('successful'));
}
/**
* 删除
* @param Request $request
* @return Response
* @throws BusinessException
*/
public function delete(Request $request): Response
{
$ids = $this->deleteInput($request);
$this->doDelete($ids);
return $this->success(__('successful'));
}
/**
* 查询前置
* @param Request $request
* @return array
* @throws BusinessException
*/
protected function selectInput(Request $request): array
{
$field = $request->get('sort');
$order = $request->get('sortOrder', 'asc');
$format = $request->get('format', 'normal');
$limit = (int)$request->get('limit', $format === 'tree' ? 1000 : 10);
$limit = $limit <= 0 ? 10 : $limit;
$order = $order === 'asc' ? 'asc' : 'desc';
$where = $request->get('filter',[]);
$page = (int)$request->get('page');
$page = $page > 0 ? $page : 1;
$allow_column = [];
//var_dump($this->model->getConnectionName());
//if ($this->model->getConnection()->getDriverName() == 'mongodb') {
if ($this->model->getConnection() != 'mysql') {
} else {
$table = $this->model->getTable();
$allow_column = Db::query("desc `$table`");
if (!$allow_column) {
throw new BusinessException('表不存在');
}
$allow_column = array_column($allow_column, 'Field', 'Field');
if (!in_array($field, $allow_column)) {
$field = null;
}
}
return [$where, $format, $limit, $field, $order, $page];
}
/**
* 指定查询where条件,并没有真正的查询数据库操作
* @param array $where
* @param string|null $field
* @param string $order
* @return Model
*/
protected function doSelect(array $where, string $field = null, string $order = 'desc')
{
$model = $this->model;
foreach ($where as $column => $value) {
$symbol = $value['symbol'];
$value1 = $value['value1'];
$value2 = $value['value2'];
if (is_array($value)) {
if ($symbol === 'like' || $symbol === 'not like') {
$model = $model->where($column, $symbol, "%$value1%");
} elseif (in_array($symbol, ['>', '=', '<', '<>','>=','<='])) {
$model = $model->where($column, $symbol, $value1);
} elseif (($symbol == 'in'|| $symbol == 'not in') && !empty($value1)) {
$valArr = $value1;
if (is_string($value1)) {
$valArr = explode(",", trim($value1));
}
if($symbol == 'in'){
$model = $model->whereIn($column, $valArr);
}else{
$model = $model->whereNotIn($column, $valArr);
}
} elseif ($symbol == 'null') {
$model = $model->whereNull($column);
} elseif ($symbol == 'not null') {
$model = $model->whereNotNull($column);
} elseif ($symbol == 'range' && $$value1 !== '' || $value2 !== '') {
$model = $model->whereBetween($column, [$value1, $value2]);
}
} else {
$model = $model->where($column, $value);
}
}
if ($field) {
$model = $model->order($field, $order);
}
return $model;
}
/**
* 执行真正查询,并返回格式化数据
* @param $query
* @param $format
* @param $limit
* @return Response
*/
protected function doFormat($query, $format, $limit,$fields="*"): Response
{
$methods = [
'select' => 'formatSelect',
'tree' => 'formatTree',
'table_tree' => 'formatTableTree',
'normal' => 'formatNormal',
];
if($limit == 'all'){
$paginator = $query->field($fields)->select();
$total = count($paginator);
$items = $paginator;
}else{
$paginator = $query->field($fields)->paginate($limit);
$total = $paginator->total();
$items = $paginator->items();
}
if (method_exists($this, "afterQuery")) {
$items = call_user_func([$this, "afterQuery"], $items);
}
$format_function = $methods[$format] ?? 'formatNormal';
return call_user_func([$this, $format_function], $items, $total);
}
/**
* 插入前置方法
* @param Request $request
* @return array
* @throws BusinessException
*/
protected function insertInput(Request $request): array
{
$data = $this->inputFilter($request->post());
$password_filed = 'password';
if (isset($data[$password_filed])) {
$data[$password_filed] = password_hash($data[$password_filed],PASSWORD_DEFAULT);
}
return $data;
}
/**
* 执行插入
* @param array $data
* @return mixed|null
*/
protected function doInsert(array $data)
{
$primary_key = $this->model->getPk();
$model_class = get_class($this->model);
$model = $model_class::create($data);
return $primary_key ? $model->$primary_key : null;
}
/**
* 更新前置方法
* @param Request $request
* @return array
* @throws BusinessException
*/
protected function updateInput(Request $request): array
{
$primary_key = $this->model->getPk();
$id = $request->post($primary_key);
$data = $this->inputFilter($request->post());
$model = $this->model->find($id);
if (!$model) {
throw new BusinessException('记录不存在', 2);
}
$password_filed = 'password';
if (isset($data[$password_filed])) {
// 密码为空,则不更新密码
if ($data[$password_filed] === '') {
unset($data[$password_filed]);
} else {
$data[$password_filed] = password_hash($data[$password_filed],PASSWORD_DEFAULT);
}
}
unset($data[$primary_key]);
return [$id, $data];
}
/**
* 执行更新
* @param $id
* @param $data
* @return void
*/
protected function doUpdate($id, $data)
{
$model = $this->model->find($id);
foreach ($data as $key => $val) {
$model->{$key} = $val;
}
$model->save();
}
/**
* 对用户输入表单过滤
* @param array $data
* @return array
* @throws BusinessException
*/
protected function inputFilter(array $data): array
{
$table = config('database.connections.mysql.prefix') . $this->model->getTable();
$allow_column = Db::getFields($this->model->getTable());
if (!$allow_column) {
throw new BusinessException('表不存在', 2);
}
//$columns = array_column($allow_column, 'Type', 'Field');
//echo json_encode($allow_column);
foreach ($data as $col => $item) {
if (!isset($allow_column[$col])) {
unset($data[$col]);
continue;
}
// 非字符串类型传空则为null
if ($item === '' && strpos(strtolower($allow_column[$col]['type']), 'varchar') === false && strpos(strtolower($allow_column[$col]['type']), 'text') === false) {
$data[$col] = null;
}
if (is_array($item)) {
$data[$col] = implode(',', $item);
}
}
if (empty($data['created_at'])) {
unset($data['created_at']);
}
if (empty($data['updated_at'])) {
unset($data['updated_at']);
}
return $data;
}
/**
* 删除前置方法
* @param Request $request
* @return array
* @throws BusinessException
*/
protected function deleteInput(Request $request): array
{
$primary_key = $this->model->getPk();
if (!$primary_key) {
throw new BusinessException('该表无主键,不支持删除');
}
$ids = $request->post('ids', '');
if(!is_array($ids)){
$ids = explode(',',$ids);
}
return $ids;
}
/**
* 执行删除
* @param array $ids
* @return void
*/
protected function doDelete(array $ids)
{
if (!$ids) {
return;
}
$primary_key = $this->model->getPk();
$this->model->whereIn($primary_key, $ids)->delete();
}
/**
* 格式化树
* @param $items
* @return Response
*/
protected function formatTree($items): Response
{
$format_items = [];
//$primary_key = $this->model->getPk();
$primary_key = $this->model->getPk();
foreach ($items as $item) {
$item->name = $this->guessName($item) ?: $item->$primary_key;
$item->value = (string)$item->$primary_key;
$item->id = $item->$primary_key;
//$item->pid = $item->pid;
$format_items[] = $item;
}
return $this->success(__('successful'), $format_items);
}
/**
* 格式化表格树
* @param $items
* @return Response
*/
protected function formatTableTree($items): Response
{
return $this->success(__('successful'), $items);
}
/**
* 格式化下拉列表
* @param $items
* @return Response
*/
protected function formatSelect($items): Response
{
$formatted_items = [];
$primary_key = $this->model->getPk();
foreach ($items as $item) {
$formatted_items[] = [
'name' => $this->guessName($item) ?: $item->$primary_key,
'value' => $item->$primary_key
];
}
return $this->success(__('successful'), $formatted_items);
}
/**
* 通用格式化
* @param $items
* @param $total
* @return Response
*/
protected function formatNormal($items, $total): Response
{
return json(['code' => 0, 'msg' => 'ok', 'count' => $total, 'data' => $items]);
}
/**
* 查询数据库后置方法,可用于修改数据
* @param mixed $items 原数据
* @return mixed 修改后数据
*/
protected function afterQuery($items)
{
return $items;
}
/**
* 猜测记录名称
* @param $item
* @return mixed
*/
protected function guessName($item)
{
return $item->title ?? $item->name ?? $item->nickname ?? $item->username ?? $item->id;
}
function multi(){
$ids = Request()->post('ids');
$params = Request()->post('params');
parse_str($params,$s);
$this->model->whereIn('id', [$ids])->update($s);
return $this->success(__('successful'));
}
/**
* 返回格式化json数据
*
* @param int $code
* @param string $msg
* @param array $data
* @return Response
*/
protected function json(int $code, string $msg = 'ok', array|object $data = []): Response
{
return json(['code' => $code, 'data' => $data, 'msg' => $msg]);
}
protected function success(string $msg = '成功', array|object $data = []): Response
{
return $this->json(0, $msg, $data);
}
protected function fail(string $msg = '失败', array|object $data = []): Response
{
return $this->json(1,$msg, $data);
}
protected function error(string $msg = '失败', array|object $data = []): Response
{
return $this->json(1,$msg, $data);
}
}
-13
View File
@@ -1,13 +0,0 @@
<?php
namespace app\controller;
use support\Request;
class DocController
{
public function index(Request $request)
{
return view("/public/doc/index");
}
}
-101
View File
@@ -1,101 +0,0 @@
<?php
namespace app\controller;
use support\Request;
use support\Log;
use Symfony\Component\Process\Process;
use support\Response;
class GitController
{
private string $secret = 'a66fb7936210d94960ac9b4e0c8bd3ef45f8f3e1';
public function test(Request $request): Response
{
$this->dispatchUpdate('bang_server.sh');
return response('Test webhook executed');
}
public function handle(Request $request): Response
{
// 1. IP白名单验证(仅接受GitHub请求)
$allowedIps = ['110.42.52.196'];
if($request->method() !== 'POST'){
return response('Method Not Allowed', 405);
}
$clientIp = $request->header('x-real-ip', $request->getRealIp());
$isValidIp = false;
foreach ($allowedIps as $range) {
if ($this->ipInRange($clientIp, $range)) {
$isValidIp = true;
break;
}
}
if (!$isValidIp) {
Log::warning("Unauthorized IP: {$clientIp}");
return response('IP not allowed', 403);
}
// 2. 签名验证
$signature = $request->header('x-hub-signature-256');
$payload = $request->rawBody();
$json = json_decode($payload, true);
$script_fn = "";
if($json['repository']['full_name'] == 'commie/wenjuanbang_server')
{
if($json['ref'] == 'refs/heads/main'){
$script_fn = 'bang_server.sh';
}
if($json['ref'] == 'refs/heads/xi'){
$script_fn = 'xi_server.sh';
}
}else if($json['repository']['full_name'] == 'commie/cdkey'){
if($json['ref'] == 'refs/heads/xi'){
$script_fn = 'wjx_cdkey.sh';
}
if($json['ref'] == 'refs/heads/wjb'){
$script_fn = 'wjb_cdkey.sh';
}
}
if(!$script_fn){
return response('Not main branch', 200);
}
if (!$this->verifySignature($payload, $signature)) {
Log::warning("Invalid signature from {$clientIp}");
return response('Invalid signature', 403);
}
// 3. 异步更新
$this->dispatchUpdate($script_fn);
return response('Webhook received successfully');
}
private function ipInRange(string $ip, string $range): bool
{
[$subnet, $bits] = explode('/', $range);
$ip = ip2long($ip);
$subnet = ip2long($subnet);
$mask = -1 << (32 - $bits);
return ($ip & $mask) === ($subnet & $mask);
}
private function verifySignature(string $payload, ?string $signature): bool
{
$computedSignature = 'sha256=' . hash_hmac('sha256', $payload, $this->secret);
return hash_equals($computedSignature, $signature ?? '');
}
private function dispatchUpdate($script_fn): void
{
$scriptPath = base_path('scripts/'.$script_fn);
$outputFile = runtime_path('logs').'/'.$script_fn.'.log';
// 使用su命令切换到您的用户
$command = "bash {$scriptPath} > {$outputFile} 2>&1";
// 后台执行
shell_exec("nohup {$command} &");
}
}
-370
View File
@@ -1,370 +0,0 @@
<?php
namespace app\controller;
use support\Request;
use support\Response;
use think\facade\Db;
use app\model\User as UserModel;
class HookController{
public $sdk = null;
function index(){
return 'ok';
}
function __call($method, $args):Response
{
//log_alert(Input());
return $this->success();
}
//用户注册完成后
function callbackAfterUserRegisterCommand(Request $request): Response
{
$userID= Input('userID');
$nickname= Input('nickname');
$users = Input('users');
foreach($users as $k=>$v){
$this->getSdk()->message->sendBusinessNotification('SystemOfficialTeam',$v['userID'],[
'contentType' => 101,
'textElem' => [
'content' => '欢迎使用'.Config('site.name')
]
]);
}
return $this->success();
}
//在发送单聊消息前的回调
public function callbackBeforeSendSingleMsgCommand(Request $request): Response
{
//log_alert(Input());
// $user_id = Input('sendID');
// $recv_user_id = Input( 'recvID');
// $status = Input('status',1);
// $sessionType = Input('sessionType',null);
// if($status == 1 && $sessionType != 4){
// //$max = 10000000000;//限制消息数量
// $user_rights = get_user_rights($user_id);
// $max = $user_rights['right']['max_send_msg_count'];
// $sended_msg_count = cache('single_msg_count_'.$user_id)??0 + cache('group_msg_count_'.$user_id)??0;
// if($sended_msg_count > $max){
// return $this->error(1002,'超出消息数量限制,请先开通或升级会员');
// }
// }
return $this->success();
}
//发送单聊消息后的回调
public function callbackAfterSendSingleMsgCommand(Request $request): Response
{
$user_id = Input('sendID');
$recv_user_id = Input('recvID');
$status = Input('status',1);
$sessionType = Input('sessionType',null);
if($status == 1 && $sessionType != 4){
$key = '_msg_count_'.$user_id;
if($sessionType == 1){
$key = 'single'.$key;
}
if($sessionType == 2){
$key = 'group'.$key;
}
cache_add($key,1);
}
return $this->success();
}
//发送群聊消息前的回调
public function callbackBeforeSendGroupleMsgCommand(Request $request): Response
{
return $this->success();
}
//发送群聊消息后的回调
public function callbackAfterSendGroupleMsgCommand(Request $request): Response
{
return $this->success();
}
//发送好友申请之前的回调
public function callbackBeforeAddFriendCommand(Request $request): Response{
// $from_user_id = Input('fromUserID');
// $to_user_id = Input('toUserID');
// $handleResult = Input('handleResult');
// $key = 'friend_count_'.$from_user_id;
// $user_rights = get_user_rights($from_user_id);
// $max = isset($user_rights['right']['max_friend_count']) ? $user_rights['right']['max_friend_count'] : -1;
// if(cache($key) > $max){
// return $this->error(1001,'超出好友数量限制,请先开通或升级会员');
// }
return $this->success();
}
//发送好友申请之后的回调
public function callbackAfterAddFriendCommand(Request $request): Response
{
$from_user_id = Input('fromUserID');
$to_user_id = Input('toUserID');
cache_add('friend_count_'.$to_user_id,1);
cache_add('friend_count_'.$from_user_id,1);
return $this->success();
}
//在添加好友对方同意之前的回调
public function callbackBeforeAddFriendAgreeCommand(Request $request): Response
{
// $from_user_id = Input('fromUserID');
// $to_user_id = Input('toUserID');
// $handleResult = Input('handleResult');
// if($handleResult == 1){
// $key = 'friend_count_'.$to_user_id;
// $user_rights = get_user_rights($to_user_id);
// $max = isset($user_rights['right']['max_friend_count']) ? $user_rights['right']['max_friend_count'] : -1;
// if(cache($key) > $max){
// return $this->error(1001,'超出好友数量限制,请先开通或升级会员');
// }
// }
return $this->success();
}
//在添加好友对方同意之后的回调
public function callbackAfterAddFriendAgreeCommand(Request $request):Response
{
return $this->success();
}
//用户在线状态回调
public function callbackAfterUserOnlineCommand(Request $request): Response
{
$user_id = Input('userID');
return $this->success();
}
//用户离线状态回调
public function callbackAfterUserOfflineCommand(Request $request): Response{
$user_id = Input('userID');
return $this->success();
}
//用户删除好友之后得回调
public function callbackAfterDeleteFriendCommand(Request $request): Response {
$friendUserID = Input('friendUserID');
$ownerUserID = Input('ownerUserID');
$sdk = $this->getSdk();
$relation = $sdk->friend->isFriend($friendUserID,$ownerUserID);
if($relation){
if($relation['inUser1Friends']){
$this->getSdk()->friend->deleteFriend($friendUserID,$ownerUserID);
}
if($relation['inUser2Friends']){
$this->getSdk()->friend->deleteFriend($ownerUserID,$friendUserID);
}
}
return $this->success();
}
//在创建群组之前的回调
//执行顺序,callbackBeforeCreateGroupCommand -> callbackBeforeMembersJoinGroupCommand -> callbackAfterCreateGroupCommand
public function callbackbeforeCreateGroupCommand(Request $request): Response
{
return $this->success();
$groupID = Input('groupID');
$creatorUserID = Input('creatorUserID');
$key = 'user_'.$creatorUserID.'_create_group_count';
$user_rights = get_user_rights($creatorUserID);
$max_group_create_count = isset($user_rights['right']['max_group_create_count']) ? $user_rights['right']['max_group_create_count'] : -1;
if(cache($key) > $max_group_create_count){
return $this->error(2001,'超出创建群组数量限制,请先开通或升级会员');
}
$max_group_user = $user_rights['right']['max_group_user_count'];
if(count(Input('initMemberList')) > $max_group_user){
return $this->error(2002,'超出群组成员数量限制,请先开通或升级会员');
}
return $this->success();
}
//在创建群组之后的回调
public function callbackafterCreateGroupCommand(Request $request):Response
{
$groupID = Input('groupID');
$creatorUserID = Input('creatorUserID');
cache_add('user_'.$creatorUserID.'_create_group_count',1);
//增加群组用户数量缓存
cache_add('group_'.$groupID.'_user_count',count(Input('initMemberList')));
return $this->success();
}
//转让群主之后的回调
public function callbackAfterTransferGroupOwnerCommand(Request $request):Response
{
$oldOwnerUserID = Input('oldOwnerUserID');
$newOwnerUserID = Input('newOwnerUserID');
$groupID = Input('groupID');
cache_add('user_'.$oldOwnerUserID.'_create_group_count',-1);
cache_add('user_'.$newOwnerUserID.'_create_group_count',1);
cache_add('group_owner_'.$groupID,$newOwnerUserID);
return $this->success();
}
//解散群组后回调
public function callbackAfterDisMissGroupCommand(Request $request):Response
{
$groupID = Input('groupID');
$ownerID = Input('ownerID');
//减少群主创建群组数量缓存
cache_add('user_'.$ownerID.'_create_group_count',-1);
//删除群组用户数量缓存
cache('group_'.$groupID.'_user_count',null);
//删除群组群主缓存
cache('group_owner_'.$groupID,null);
return $this->success();
}
//用户退出群组的回调
public function callbackAfterQuitGroupCommand(Request $request):Response
{
$groupID = Input('groupID');
$userID = Input('userID');
// //减少用户加入群组数量缓存
// cache_add('user_'.$userID.'_join_group_count',-1);
//减少群组用户数量缓存
cache_add('group_'.$groupID.'_user_count',-1);
return $this->success();
}
//群成员进群之前的回调
public function callbackBeforeMembersJoinGroupCommand(Request $request):Response
{
return $this->success();
$groupID = Input('groupID');
$memberList = Input('memberList');
$ownerID = $this->getGroupOwner($groupID);
if(!$ownerID){
return $this->success();
}
//获取群组当前用户数量
$group_user_count = cache('group_'.$groupID.'_user_count');
if($group_user_count === null){
$group_user_count = 0;
}
//获取群组最大用户数量
$max_group_user = get_user_rights($ownerID)['right']['max_group_user_count'];
if((count($memberList) + $group_user_count) > $max_group_user){
return $this->error(2003,'超出群组成员数量限制,请先开通或升级会员');
}
return $this->success();
}
//踢除群组成员的回调
public function callbackAfterKickGroupCommand(Request $request):Response
{
$groupID = Input('groupID');
$kickedUserIDs = Input('kickedUserIDs');
//减少群组用户数量缓存
cache_add('group_'.$groupID.'_user_count',-count($kickedUserIDs));
// foreach($kickedUserIDs as $kickedUserID){
// //减少用户加入群组数量缓存
// cache_add('user_'.$kickedUserID.'_join_group_count',-1);
// }
return $this->success();
}
//新成员加入群组之后的回调
public function callbackAfterJoinGroupCommand(Request $request):Response
{
$groupID = Input('groupID');
$userID = Input('userID');
// //增加用户加入群组数量缓存
// cache_add('user_'.$userID.'_join_group_count',1);
//增加群组用户数量缓存
cache_add('group_'.$groupID.'_user_count',1);
return $this->success();
}
//邀请新成员加入群组之前的回调
//执行顺序,callbackBeforeInviteJoinGroupCommand -> callbackBeforeMembersJoinGroupCommand
public function callbackBeforeInviteJoinGroupCommand(Request $request):Response
{
return $this->success();
$groupID = Input('groupID');
$invitedUserIDs = Input('invitedUserIDs');
//获取群组当前用户数量
$group_user_count = cache('group_'.$groupID.'_user_count');
if($group_user_count === null){
$group_user_count = 0;
}
//获取群组最大用户数量
$max_group_user = get_user_rights($this->getGroupOwner($groupID))['right']['max_group_user_count'];
if((count($invitedUserIDs) + $group_user_count) > $max_group_user){
return $this->error(2003,'超出群组成员数量限制,请先开通或升级会员');
}
return $this->success();
}
//申请加入群组之前的回调
public function callbackBeforeJoinGroupCommand(Request $request):Response
{
return $this->success();
$groupID = Input('groupID');
$applyID = Input('applyID');
//获取群组当前用户数量
$group_user_count = cache('group_'.$groupID.'_user_count')?:0;
//获取群组最大用户数量
$max_group_user = get_user_rights($this->getGroupOwner($groupID))['right']['max_group_user_count'];
if((1 + $group_user_count) > $max_group_user){
return $this->error(2003,'群组已经满员');
}
// //获取用户加入群组数量限制
// $max_group_join_count = get_user_rights($applyID)['right']['max_group_join_count'];
// //获取用户加入群组数量
// $user_join_group_count = cache('user_'.$applyID.'_join_group_count')?:0;
// if((1 + $user_join_group_count) > $max_group_join_count){
// return $this->error(2004,'超出加入群组数量限制,请先开通或升级会员');
// }
return $this->success();
}
function getGroupOwner($groupID=''){
$result = cache('group_owner_'.$groupID);
if($result){
return $result;
}
$groupsInfo = $this->getSdk()->group->getGroupsInfo([$groupID]);
foreach($groupsInfo['groupInfos'] as $groupInfo){
if($groupInfo['groupID'] == $groupID){
cache('group_owner_'.$groupID,$groupInfo['ownerUserID']);
return $groupInfo['ownerUserID'];
}
}
return '';
}
function result($code,$msg,$nextCode=0){
return json([
"actionCode" => 0,
"errCode" => $code,
"errMsg" => $msg,
"errDlt" => '',
"nextCode"=> $nextCode
]);
}
function success(){
return $this->result(0,"");
}
function error($errCode=0,$errMsg='',$nextCode=1){
return $this->result($errCode,$errMsg,$nextCode);
}
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;
}
}
-63
View File
@@ -1,63 +0,0 @@
<?php
namespace app\controller;
use support\exception\BusinessException;
use support\Request;
use support\Response;
use Exception;
use app\model\Archives as ArchivesModel;
class IndexController extends Crud
{
/**
* 后台主页
* @param Request $request
* @return Response
* @throws BusinessException|Exception
*/
public function index(Request $request)
{
return view('public/index.html');
}
public function user(Request $request,$code)
{
cp($code);
return 'user';
}
public function group(Request $request,$code)
{
cp($code);
return 'group';
}
public function privacy_policy(Request $request)
{
return $this->siglepage($request);
}
public function aboutus(Request $request)
{
return $this->siglepage($request);
}
function siglepage($request){
$name = $request->action;
if(!$name){
return $this->error(__("Article does not exist"));
}
/** @var ArchivesModel $vo */
$vo = ArchivesModel::where('name',$name)->find();
if(!$vo) {
return $this->error(__("Article does not exist"));
}
$addon = \app\model\Content::where('id', $vo->id)->find()->toArray();
if ($addon) {
$vo->setAddonData($addon);
}
return view('common/siglepage',[
'vo' => $vo
]);
}
}
-672
View File
@@ -1,672 +0,0 @@
<?php
namespace app\controller;
use support\Request;
use support\Response;
/**
* 指标
* 提供Prometheus格式的监控指标
*/
class MetricsController extends Base
{
/**
* 无需登录及鉴权的方法
* @var array
*/
protected $noNeedLogin = ['index'];
/**
* 指标数据存储
*/
protected static $metrics = [];
/**
* 指标类型定义
*/
const TYPE_COUNTER = 'counter';
const TYPE_GAUGE = 'gauge';
const TYPE_HISTOGRAM = 'histogram';
const TYPE_SUMMARY = 'summary';
/**
* 获取指标数据
*
* @param Request $request
* @return Response
*/
public function index(Request $request): Response
{
$metrics = [];
// 系统基础指标
$metrics[] = $this->getSystemMetrics();
// PHP运行指标
$metrics[] = $this->getPhpMetrics();
// 应用业务指标
$metrics[] = $this->getAppMetrics();
// 数据库指标
$metrics[] = $this->getDatabaseMetrics();
// Redis指标
$metrics[] = $this->getRedisMetrics();
// MongoDB指标
$metrics[] = $this->getMongoDBMetrics();
// HTTP请求指标
$metrics[] = $this->getHttpMetrics();
$content = implode("\n", array_filter($metrics));
return response($content, 200, [
'Content-Type' => 'text/plain; charset=utf-8'
]);
}
/**
* 获取系统指标
*/
protected function getSystemMetrics(): string
{
$metrics = [];
// 内存使用
$memoryUsage = memory_get_usage(true);
$memoryPeak = memory_get_peak_usage(true);
$memoryLimit = $this->getBytes(ini_get('memory_limit'));
$metrics[] = $this->formatGauge('webman_memory_usage_bytes', 'Current memory usage in bytes', [], $memoryUsage);
$metrics[] = $this->formatGauge('webman_memory_peak_bytes', 'Peak memory usage in bytes', [], $memoryPeak);
$metrics[] = $this->formatGauge('webman_memory_limit_bytes', 'Memory limit in bytes', [], $memoryLimit);
// CPU负载
if (function_exists('sys_getloadavg')) {
$load = sys_getloadavg();
$metrics[] = $this->formatGauge('webman_system_load1', 'System load average over 1 minute', [], $load[0]);
$metrics[] = $this->formatGauge('webman_system_load5', 'System load average over 5 minutes', [], $load[1]);
$metrics[] = $this->formatGauge('webman_system_load15', 'System load average over 15 minutes', [], $load[2]);
}
// 磁盘使用
$diskTotal = disk_total_space('/');
$diskFree = disk_free_space('/');
$diskUsed = $diskTotal - $diskFree;
$metrics[] = $this->formatGauge('webman_disk_total_bytes', 'Total disk space in bytes', [], $diskTotal);
$metrics[] = $this->formatGauge('webman_disk_free_bytes', 'Free disk space in bytes', [], $diskFree);
$metrics[] = $this->formatGauge('webman_disk_used_bytes', 'Used disk space in bytes', [], $diskUsed);
return implode("\n", $metrics);
}
/**
* 获取PHP指标
*/
protected function getPhpMetrics(): string
{
$metrics = [];
// PHP版本信息
$metrics[] = $this->formatGauge('webman_php_version_info', 'PHP version information', [
'version' => PHP_VERSION,
'sapi' => PHP_SAPI
], 1);
// OPcache指标
if (function_exists('opcache_get_status')) {
$opcache = opcache_get_status(false);
if ($opcache) {
$metrics[] = $this->formatGauge('webman_opcache_enabled', 'OPcache enabled status', [], $opcache['opcache_enabled'] ? 1 : 0);
$metrics[] = $this->formatGauge('webman_opcache_hit_rate', 'OPcache hit rate', [], $opcache['opcache_statistics']['opcache_hit_rate'] ?? 0);
$metrics[] = $this->formatCounter('webman_opcache_hits_total', 'Total OPcache hits', [], $opcache['opcache_statistics']['hits'] ?? 0);
$metrics[] = $this->formatCounter('webman_opcache_misses_total', 'Total OPcache misses', [], $opcache['opcache_statistics']['misses'] ?? 0);
}
}
// 运行时间
if (function_exists('posix_times')) {
$times = posix_times();
$metrics[] = $this->formatCounter('webman_cpu_user_seconds_total', 'Total user CPU time', [], $times['utime'] ?? 0);
$metrics[] = $this->formatCounter('webman_cpu_system_seconds_total', 'Total system CPU time', [], $times['stime'] ?? 0);
}
return implode("\n", $metrics);
}
/**
* 获取应用业务指标
*/
protected function getAppMetrics(): string
{
$metrics = [];
// 应用信息
$metrics[] = $this->formatGauge('webman_app_info', 'Application information', [
'name' => config('app.name', 'webman'),
'version' => config('app.version', '1.0.0'),
'env' => config('app.debug') ? 'development' : 'production'
], 1);
// 启动时间
$metrics[] = $this->formatGauge('webman_start_time_seconds', 'Application start time', [], defined('WEBMAN_START_TIME') ? WEBMAN_START_TIME : time());
// 运行时长
$startTime = defined('WEBMAN_START_TIME') ? WEBMAN_START_TIME : time();
$uptime = time() - $startTime;
$metrics[] = $this->formatCounter('webman_uptime_seconds_total', 'Total uptime in seconds', [], $uptime);
return implode("\n", $metrics);
}
/**
* 获取数据库指标
*/
protected function getDatabaseMetrics(): string
{
$metrics = [];
try {
// 数据库连接信息
$dbConfig = config('database.connections.mysql');
if ($dbConfig) {
$metrics[] = $this->formatGauge('webman_db_config_info', 'Database configuration', [
'host' => $dbConfig['hostname'] ?? 'unknown',
'database' => $dbConfig['database'] ?? 'unknown',
'charset' => $dbConfig['charset'] ?? 'utf8'
], 1);
}
// 数据库连接状态 - 连接成功
$metrics[] = $this->formatGauge('webman_db_up', 'Database connection status', [], 1);
// 尝试获取数据库状态
$db = \think\facade\Db::connect();
// 1. 全局状态指标
$status = $db->query('SHOW GLOBAL STATUS');
$statusMap = [];
foreach ($status as $item) {
$statusMap[$item['Variable_name']] = $item['Value'];
}
// 关键指标
if (isset($statusMap['Threads_connected'])) {
$metrics[] = $this->formatGauge('webman_db_threads_connected', 'Number of currently connected threads', [], (int)$statusMap['Threads_connected']);
}
if (isset($statusMap['Threads_running'])) {
$metrics[] = $this->formatGauge('webman_db_threads_running', 'Number of threads running', [], (int)$statusMap['Threads_running']);
}
if (isset($statusMap['Queries'])) {
$metrics[] = $this->formatCounter('webman_db_queries_total', 'Total number of queries', [], (int)$statusMap['Queries']);
}
if (isset($statusMap['Slow_queries'])) {
$metrics[] = $this->formatCounter('webman_db_slow_queries_total', 'Total number of slow queries', [], (int)$statusMap['Slow_queries']);
}
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) {
// 数据库连接失败,记录错误
$metrics[] = $this->formatGauge('webman_db_up', 'Database connection status', [], 0);
}
return implode("\n", $metrics);
}
/**
* 获取Redis指标
*/
protected function getRedisMetrics(): string
{
$metrics = [];
try {
$redis = \support\think\Cache::handler();
$info = $redis->info();
// Redis连接状态
$metrics[] = $this->formatGauge('webman_redis_up', 'Redis connection status', [], 1);
// Redis版本
if (isset($info['redis_version'])) {
$metrics[] = $this->formatGauge('webman_redis_version_info', 'Redis version information', [
'version' => $info['redis_version']
], 1);
}
// 内存使用
if (isset($info['used_memory'])) {
$metrics[] = $this->formatGauge('webman_redis_memory_used_bytes', 'Redis memory used in bytes', [], (int)$info['used_memory']);
}
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'])) {
preg_match('/keys=(\d+)/', $info['db0'], $matches);
if (isset($matches[1])) {
$metrics[] = $this->formatGauge('webman_redis_keys_total', 'Total number of keys', [], (int)$matches[1]);
}
}
// 运行时间
if (isset($info['uptime_in_seconds'])) {
$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);
}
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请求指标
*/
protected function getHttpMetrics(): string
{
$metrics = [];
// 请求计数器(这里需要从中间件或日志中统计,示例使用静态数据)
$metrics[] = $this->formatCounter('webman_http_requests_total', 'Total HTTP requests', [
'method' => 'GET',
'status' => '200'
], 0);
$metrics[] = $this->formatCounter('webman_http_requests_total', 'Total HTTP requests', [
'method' => 'POST',
'status' => '200'
], 0);
// 请求处理时间(使用Gauge类型)
$metrics[] = $this->formatGauge('webman_http_request_duration_seconds', 'HTTP request duration in seconds', [
'method' => 'GET',
'path' => '/metrics'
], 0);
// 响应大小
$metrics[] = $this->formatCounter('webman_http_response_size_bytes_total', 'Total HTTP response size in bytes', [], 0);
// 请求大小
$metrics[] = $this->formatCounter('webman_http_request_size_bytes_total', 'Total HTTP request size in bytes', [], 0);
return implode("\n", $metrics);
}
/**
* 格式化Counter类型指标
*/
protected function formatCounter(string $name, string $help, array $labels, float $value): string
{
$output = [];
$output[] = "# HELP {$name} {$help}";
$output[] = "# TYPE {$name} counter";
$labelStr = $this->formatLabels($labels);
$output[] = $name . $labelStr . ' ' . $value;
return implode("\n", $output);
}
/**
* 格式化Gauge类型指标
*/
protected function formatGauge(string $name, string $help, array $labels, float $value): string
{
$output = [];
$output[] = "# HELP {$name} {$help}";
$output[] = "# TYPE {$name} gauge";
$labelStr = $this->formatLabels($labels);
$output[] = $name . $labelStr . ' ' . $value;
return implode("\n", $output);
}
/**
* 格式化Histogram类型指标
*/
protected function formatHistogram(string $name, string $help, array $labels, array $buckets): string
{
$output = [];
$output[] = "# HELP {$name} {$help}";
$output[] = "# TYPE {$name} histogram";
// 如果没有提供桶数据,返回空直方图
if (empty($buckets)) {
$labelStr = $this->formatLabels($labels);
$output[] = $name . '_bucket' . $labelStr . ',le="+Inf" 0';
$output[] = $name . '_sum' . $labelStr . ' 0';
$output[] = $name . '_count' . $labelStr . ' 0';
}
return implode("\n", $output);
}
/**
* 格式化标签
*/
protected function formatLabels(array $labels): string
{
if (empty($labels)) {
return '';
}
$pairs = [];
foreach ($labels as $key => $value) {
$pairs[] = $key . '="' . $this->escapeLabel($value) . '"';
}
return '{' . implode(',', $pairs) . '}';
}
/**
* 转义标签值
*/
protected function escapeLabel(string $value): string
{
return str_replace(['\\', '"', "\n"], ['\\\\', '\\"', '\\n'], $value);
}
/**
* 将PHP内存限制字符串转换为字节数
*/
protected function getBytes(string $val): int
{
$val = trim($val);
$last = strtolower($val[strlen($val) - 1]);
$val = (int) $val;
switch ($last) {
case 'g':
$val *= 1024;
case 'm':
$val *= 1024;
case 'k':
$val *= 1024;
}
return $val;
}
/**
* 增加计数器值(供其他控制器调用)
*
* @param string $name
* @param array $labels
* @param float $value
*/
public static function incCounter(string $name, array $labels = [], float $value = 1): void
{
$key = $name . json_encode($labels);
if (!isset(self::$metrics[$key])) {
self::$metrics[$key] = [
'type' => 'counter',
'name' => $name,
'labels' => $labels,
'value' => 0
];
}
self::$metrics[$key]['value'] += $value;
}
/**
* 设置Gauge值(供其他控制器调用)
*
* @param string $name
* @param float $value
* @param array $labels
*/
public static function setGauge(string $name, float $value, array $labels = []): void
{
$key = $name . json_encode($labels);
self::$metrics[$key] = [
'type' => 'gauge',
'name' => $name,
'labels' => $labels,
'value' => $value
];
}
/**
* 记录HTTP请求指标(供中间件调用)
*
* @param string $method
* @param string $path
* @param int $status
* @param float $duration
* @param int $responseSize
*/
public static function recordHttpRequest(string $method, string $path, int $status, float $duration, int $responseSize = 0): void
{
$labels = [
'method' => $method,
'path' => $path,
'status' => (string)$status
];
// 请求总数
self::incCounter('webman_http_requests_total', $labels);
// 响应大小
self::incCounter('webman_http_response_size_bytes_total', $labels, $responseSize);
}
}
-42
View File
@@ -1,42 +0,0 @@
<?php
namespace app\controller;
use app\model\Order;
use app\model\Withdrawl as WithdrawlModel;
use app\model\Address as AddressModel;
use support\exception\BusinessException;
use support\Request;
use support\Response;
use Throwable;
use Web3\Contracts\Types\Address as TypesAddress;
use Workerman\Worker;
class UtilsController extends Crud
{
public function i18n(Request $request): Response
{
$locale = $_GET['locale'];
$key = $_GET['key'];
$langArr=[
'zh_CN',
'zh_TW',
'fi_FI',
'ja_JP',
'ko_KR',
'en_US',
];
foreach($langArr as $lang){
$fn = "public/h5/i18n/".$lang.'.json';
$json = json_decode(file_get_contents($fn), true);
echo $locale,$key;
if(!isset($json[$key])){
$json[$key] = $key;
file_put_contents($fn, json_encode($json, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT));
}
}
return $this->success(__('successful'));
}
}
-107
View File
@@ -1,107 +0,0 @@
<?php
namespace app\enum;
enum BalanceType: int
{
/**
* 充值
*/
case RECHARGE = 100;
/**
* 充值卡密
*/
case RECHARGE_CARD = 101;
/**
* 提现
*/
case WITHDRAWAL = 200;
/**
* 提现退回
*/
case WITHDRAWAL_REJECT = 201;
/**
* 购买卡密
*/
case CDKEY = 202;
/**
* 站内转账
*/
case TRANSFER = 300;
/**
* 兑换
*/
case EXCHANGE = 301;
/**
* 签到
*/
case SIGNIN = 302;
/**
* 邀请新用户注册
*/
case INVITE_NEW_USER = 305;
/**
* 购买产品
*/
case PRODUCT_BUY = 401;
/**
* 购买角色
*/
case PURCHASE_ROLE = 402;
/**
* 购买积分卡
*/
case GIFT_BUY = 407;
/**
* 购买积分卡
*/
case SEE_POINT_AWARD = 901;
/**
* 获取所有类型映射数组
*/
public static function toArray(): array
{
return [
self::RECHARGE->value => __('充值'),
self::RECHARGE_CARD->value => __('充值卡密'),
self::WITHDRAWAL->value => __('提现'),
self::WITHDRAWAL_REJECT->value => __('提现退回'),
self::CDKEY->value => __('购买卡密'),
self::TRANSFER->value => __('站内转账'),
self::EXCHANGE->value => __('兑换'),
self::SIGNIN->value => __('签到'),
self::INVITE_NEW_USER->value => __('邀请新用户注册'),
self::PRODUCT_BUY->value => __('购买产品'),
self::PURCHASE_ROLE->value => __('购买角色'),
self::GIFT_BUY->value => __('购买积分卡'),
self::SEE_POINT_AWARD->value => __('见点奖'),
];
}
/**
* 获取当前类型的描述文本
*/
public function getDescription(): string
{
return self::toArray()[$this->value];
}
/**
* 安全地从值创建枚举实例
*/
public static function tryFromValue(int $value): ?self
{
return self::tryFrom($value);
}
}
-24
View File
@@ -1,24 +0,0 @@
<?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);
}
}
-39
View File
@@ -1,39 +0,0 @@
<?php
namespace app\enum;
enum OrderStatus: int
{
case CLOSE = 1;
case PAID = 2;
case CLUBS = 3;
case SPADES = 4;
/**
* 获取所有状态映射数组
*/
public static function toArray(): array
{
return [
self::CLOSE->value => __('失败'),
self::PAID->value => __('取消'),
self::CLUBS->value => __('等待支付'),
self::SPADES->value => __('完成'),
];
}
/**
* 获取当前状态的描述文本
*/
public function getDescription(): string
{
return self::toArray()[$this->value];
}
/**
* 安全地从值创建枚举实例
*/
public static function tryFromValue(int $value): ?self
{
return self::tryFrom($value);
}
}
-32
View File
@@ -1,32 +0,0 @@
<?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 => __('支付宝')
];
}
}
-61
View File
@@ -1,61 +0,0 @@
<?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);
}
}
-42
View File
@@ -1,42 +0,0 @@
<?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 => __('其他')
];
}
}
-60
View File
@@ -1,60 +0,0 @@
<?php
namespace app\enum;
enum RechargeStatus: int
{
/**
* 失败
*/
case FAIL = -2;
/**
* 取消
*/
case CANCEL = -1;
/**
* 等待支付
*/
case CREATED = 0;
/**
* 支付完成
*/
case PAID = 1;
/**
* 完成
*/
case COMPLETE = 2;
/**
* 获取所有状态映射数组
*/
public static function toArray(): array
{
return [
self::FAIL->value => __('失败'),
self::CANCEL->value => __('取消'),
self::CREATED->value => __('等待支付'),
self::COMPLETE->value => __('完成'),
];
}
/**
* 获取当前状态的描述文本
*/
public function getDescription(): string
{
return self::toArray()[$this->value];
}
/**
* 安全地从值创建枚举实例
*/
public static function tryFromValue(int $value): ?self
{
return self::tryFrom($value);
}
}
-61
View File
@@ -1,61 +0,0 @@
<?php
namespace app\enum;
enum ServerStatus : int
{
/**
* 等待开始
*/
case WAITING = 0;
/**
* 进行中
*/
case WORKING = 1;
/**
* 审核中
*/
case AUDITING = 2;
/**
* 结算中
*/
case SETTLEMENT = 3;
/**
* 任务完成
*/
case COMPLETE = 4;
/**
* 任务失败
*/
case FAILED = -1;
/**
* 获取所有状态映射数组
*/
public static function toArray(): array
{
return [
self::WAITING->value => __('waiting'),
self::WORKING->value => __('working'),
self::AUDITING->value => __('auditing'),
self::SETTLEMENT->value => __('settlement'),
self::COMPLETE->value => __('complete'),
self::FAILED->value => __('failed'),
];
}
/**
* 获取当前状态的描述文本
*/
public function getDescription(): string
{
return self::toArray()[$this->value];
}
/**
* 安全地从值创建枚举实例
*/
public static function tryFromValue(int $value): ?self
{
return self::tryFrom($value);
}
}
-55
View File
@@ -1,55 +0,0 @@
<?php
namespace app\enum;
enum WithdrawlStatus: int
{
/**
* 失败
*/
case FAIL = -2;
/**
* 驳回
*/
case REJECT = -1;
/**
* 等待审核
*/
case CREATED = 0;
/**
* 转账中
*/
case TRANSFERRING = 1;
/**
* 完成
*/
case COMPLETE = 2;
// 获取所有状态描述映射
public static function toArray(): array
{
return [
self::FAIL->value => __('失败'),
self::REJECT->value => __('驳回'),
self::CREATED->value => __('等待审核'),
self::TRANSFERRING->value => __('转账中'),
self::COMPLETE->value => __('完成'),
];
}
// 获取当前状态的描述
public function getDescription(): string
{
return self::toArray()[$this->value];
}
// 从值创建枚举实例(带安全检测)
public static function tryFromValue(int $value): ?self
{
return self::tryFrom($value);
}
}
-29
View File
@@ -1,29 +0,0 @@
<?php
namespace app\event;
use app\model\User as UserModel;
use support\think\Db;
use Request;
class Card{
function create($row){
$cdkeys = [];
for ($i=0; $i < $row['total']; $i++) {
array_push($cdkeys,[
'type' => $row['type'],
'category_id' => $row['id'],
'account' => \support\Random::uuid(),
'password' => '123456',
'expires' => $row['expires'],
'days' => $row['days'],
'is_used' => 0,
'use_time' => 0,
'status' => 1,
'created_at' => time(),
'updated_at' => time(),
]);
}
$Cdkey = new \app\model\Cdkey;
$Cdkey->saveAll($cdkeys);
return $row;
}
}
-9
View File
@@ -1,9 +0,0 @@
<?php
namespace app\event;
use support\think\Db;
use Request;
class Main{
function index($data=[]){
return $data;
}
}
-151
View File
@@ -1,151 +0,0 @@
<?php
namespace app\event;
use support\think\Db;
use app\model\User as UserModel;
use Request;
/**
* 产品Hook
*/
class Product{
private $debug = false;
private $userinfo=[];
function buy($data=[]){
$questionnaire_count = $data->product->total * $data['quantity']; //问卷总数
UserModel::currency7($data['user_id'],$questionnaire_count,\app\enum\BalanceType::PRODUCT_BUY,'购买产品');
$user = UserModel::find($data["user_id"]);
//设置定时任务发放问卷,马上发放第一天的,然后每隔24小时发放一次,发放到第$data->product->days天
$assign_count = $data->product->assign_count;
addJob([
'action' => 'assign',
'user_id' => $data['user_id'],
'order_id' => $data['id'],
'amount' => $assign_count,
],'Questionnaire');
//$data =
//用户消费统计更新
cache_add('user_consume_total_'.$data['user_id'],$data['amount']);
$parent_id = $this->get_parent_id($data['user_id']);
if($parent_id){
// 销售奖励(直推)
// $reward = bcmul($data['amount'] ,2,0);
// UserModel::score($parent_id ,$reward,\app\enum\BalanceType::SALES_REWARD,$data['id']);
// cache_add('user_sales_reward_'.$parent_id,$reward); //销售奖励
$ancestorIds = Db::name('user_team')->where('descendant_id',$data['user_id'])
->column('ancestor_id');
if(!empty($ancestorIds)){
// 批量累加上级业绩
Db::name('user_extend')->whereIn('user_id',$ancestorIds)->where('user_id','<>',$data['user_id'])->update([
'sales' => Db::raw('sales+'.$data['amount'])
]);
$users = Db::name('user')->whereIn('id',$ancestorIds)->where('group',2)->column('id');
// 销售奖励(渠道)
foreach($users as $uid){
$reward = bcmul($data['amount'] ,8,0);
UserModel::score($uid ,$reward,\app\enum\BalanceType::SALES_REWARD,$data['id']);
cache_add('user_sales_reward_'.$uid,$reward); //销售奖励
}
}
return $data;
// 业绩与等级批量更新(事务内:所有上级的 sales 与 role_id
$this->updateAncestorsSalesAndLevel($data['user_id'],$data['amount']);
//我的用户表有role_id:角色ID,id:用户ID,详细可以查看\app\model\User的属性
//$data['user_id'] //购买人ID
//$data['amount'] //交易金额
//$data['role_id'] //用户角色
//上级user_id查询用$this->get_parent_id($user_id)
//$parent_user_role_id = \app\model\User::where('id',$this->get_parent_id($data['user_id']))->value('role_id');
//用户余额增加使用 User::money(用户ID,增加的金额,\app\enum\BalanceType::SALES_REWARD,$data['id']);
// 分佣规则
//从当前用户
// 代理佣金总和是交易金额的10%
// 极差收益,type=\app\enum\BalanceType::SALES_REWARD
// 极差收益总和是交易金额的20%
// 最多只能10个人分,如果上级用户级别小于上一个分润的人的级别,就跳过他,继续找下一个,始终补满10个人,直到级别等于10或者上级为空的时候才停止
// 每个人分润的比例是(极差收益比例-已经分出去的比例)*极差收益总和
//代码写在这里,不能去掉我的注释
$distributed_users = jicha($data['user_id'],$data['amount'],[0,0.02,0.04,0.06,0.08,0.1]);
foreach($distributed_users as $k=>$v){
UserModel::money($v['user_id'],$v['amount'],\app\enum\BalanceType::SALES_REWARD,$data['id']);
cache_add('user_income_total_'.$v['user_id'],$v['amount']); //收入统计
cache_add('user_sales_reward_'.$v['user_id'],$v['amount']); //销售奖励
}
}
return $data;
}
// 批量更新所有上级的业绩并根据阈值升级角色(单事务)
private function updateAncestorsSalesAndLevel($user_id,$delta_sales){
Db::startTrans();
try{
// 取出所有上级ID
$ancestorIds = Db::name('user_team')->where('descendant_id',$user_id)->column('ancestor_id');
if(empty($ancestorIds)){
Db::commit();
return;
}
// 批量累加上级业绩
Db::name('user_extend')->whereIn('user_id',$ancestorIds)->update([
'sales' => Db::raw('sales+'.$delta_sales)
]);
// 读取更新后的 sales 和当前 role_id
$extends = Db::name('user_extend')->whereIn('user_id',$ancestorIds)->column('sales','user_id');
$roles = Db::name('user')->whereIn('id',$ancestorIds)->column('role_id','id');
$levelArr = [0,5000,10000,50000,100000,200000];
$maxIdx = count($levelArr)-1;
$upgradeMap = [];
foreach($extends as $uid=>$sales){
cache_add('user_consume_reward_'.$uid,$sales);//个人消费统计
cache_add('team_consume_total_'.$uid,$sales); //团队总业绩
// 计算应达的最高等级
$newLevel = 0;
for($i=$maxIdx;$i>=0;$i--){
if($sales >= $levelArr[$i]){ $newLevel = $i; break; }
}
$current = isset($roles[$uid]) ? (int)$roles[$uid] : 0;
if($newLevel > $current){
$upgradeMap[$uid] = $newLevel;
}
}
// 批量升级(按新等级分组,可减少语句数)
if(!empty($upgradeMap)){
$levelToUsers = [];
foreach($upgradeMap as $uid=>$lvl){ $levelToUsers[$lvl][] = $uid; }
foreach($levelToUsers as $lvl=>$uids){
Db::name('user')->whereIn('id',$uids)->where('group',2)->where('role_id','<',$lvl)->update(['role_id'=>$lvl]);
}
}
Db::commit();
}catch(\Throwable $e){
Db::rollback();
throw $e;
}
}
function get_parent_id($user_id){
if($this->debug){
return $this->userinfo[''.$user_id]['parent_id'];
}
return get_parent_id($user_id);
}
function log($str){
$args = func_get_args();
if(is_string($args[0])){
$str = call_user_func_array('sprintf',$args);
if($this->debug){
return print_r($str);
}
log_alert($str);
}else{
$str = json_encode($args);
if($this->debug){
return print_r($str);
}
log_alert($str);
}
}
}
-23
View File
@@ -1,23 +0,0 @@
<?php
namespace app\event;
use support\think\Db;
use Request;
class Recharge{
function success($row=[]){
$data = $row;
if(!is_array($row)){
$data = $data->toArray();
}
cache_add('user_recharge_total_'.$data['user_id'],$data['amount']);
$parent_id = get_parent_id($data['user_id']);
if($parent_id){
//团队提现统计
cache_add('team_recharge_total_'.$parent_id,$data['amount']);
}
//系统每日提现统计
$date = date('Y-m-d');
cache_add('statistics_recharge_times_'.$date,1);
cache_add('statistics_recharge_amount_'.$date,$data['amount']);
return $row;
}
}
-198
View File
@@ -1,198 +0,0 @@
<?php
namespace app\event;
use support\think\Db;
use Request;
use Symfony\Component\Console\Input\Input;
class 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']),
'invite_code' => \support\Encrypt::userIDencode($_user['id']),
'userID' => \support\Encrypt::userIDencode($_user['id'])
];
\app\model\User::where('id',$_user['id'])->update($saveData);
//创建扩展数据
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);
//管理团队人数
$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);
/**
* @var \support\OpenImSdk\Client $IM
*/
$IM = request()->IM;
$imToken = $IM->auth->getUserToken($data['userID'],Input('platform'));
$data['imToken'] = $imToken['token'];
return $data;
}
function profile($user=[]){
$data = $user;
if(!is_array($data)){
$data = $data->toArray();
}
$last_see = $last_see ?? cache('last_see_'.$data['id']);
$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=[]){
return $data;
}
function change_trade_pwd_successed($data=[]){
return $data;
}
function logout_successed($data=[]){
return $data;
}
function delete_successed($data=[]){
return $data;
}
//用户角色组变化
function role_up($user=[]){
$data = $user;
if(!is_array($data)){
$data = $data->toArray();
}
//旗下会员总数
$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
// ];
//addJob($data,'Settlement');
}
/**
* 分润逻辑
*
* @param int $userId 用户ID(充值用户)
* @param float $amount 充值金额
* @param int $orderId 订单ID
* @return bool
*/
function distributeProfit($user_id, $amount, $order_id) {
// 定义分润比例
$commissionRates = Config('site.indirect_referral_award');
// 启动事务
Db::startTrans();
try {
// 查询上三级用户
$ancestors = Db::name('user_team')
->alias('ut')
->join('user wu', 'ut.ancestor_id = wu.id') // 获取上级用户信息
->where('ut.descendant_id', $user_id)
->whereBetween('ut.depth', [1, 3]) // 限制深度为 1 到 3 级
->field('ut.ancestor_id, ut.depth')
->order('ut.depth ASC')
->select();
// 遍历上级用户,计算并记录分润
/** @var \app\model\UserTeam $ancestor */
foreach ($ancestors as $ancestor) {
$depth = $ancestor['depth'];
if (isset($commissionRates[$depth])) {
$commission = $amount * $commissionRates[$depth]; // 计算分润金额
// 插入分润记录
Db::table('z_commission_logs')->insert([
'user_id' => $ancestor['ancestor_id'],
'order_id' => $order_id,
'amount' => $commission,
'created_at' => date('Y-m-d H:i:s'),
]);
}
}
// 提交事务
Db::commit();
return true;
} catch (\Exception $e) {
// 回滚事务
Db::rollback();
throw $e; // 或记录日志以便调试
}
}
}
-75
View File
@@ -1,75 +0,0 @@
<?php
namespace app\event;
use app\model\User as UserModel;
use support\think\Db;
use Request;
class Withdrawl{
function success($row=[]){
$data = $row;
if(!is_array($row)){
$data = $data->toArray();
}
//用户提现统计
cache_add('user_withdrawl_total_'.$data['user_id'],$data['deduction_amount']);
// $parent_id = get_parent_id($data['user_id']);
// if($parent_id){
// //团队提现统计
// cache_add('team_withdrawl_total_'.$parent_id,$data['deduction_amount']);
// //提现奖励
// $distributed_users = jicha($data['user_id'],$data['deduction_amount'],[0,0.01,0.02,0.03,0.05,0.05]);
// foreach($distributed_users as $k=>$v){
// UserModel::money($v['user_id'],$v['amount'],\app\enum\BalanceType::OUTPUT_REWARD,$data['id']);
// cache_add('user_income_total_'.$v['user_id'],$v['amount']);
// cache_add('user_withdrawl_reward_'.$v['user_id'],$v['amount']);
// }
// }
//系统每日提现统计
$date = date('Y-m-d');
cache_add('statistics_withdrawl_times_'.$date,1);
cache_add('statistics_withdrawl_amount_'.$date,$data['deduction_amount']);
//cache_add('withdrawl_pass_total',$data['deduction_amount']);
//cache_add('withdrawl_pass_times',1);
return $row;
}
function reject($row=[]){
$data = $row;
if(!is_array($row)){
$data = $data->toArray();
}
// cache_add('withdrawl_pass_total',-$data['deduction_amount']);
// cache_add('withdrawl_pass_times',-1);
return $row;
}
function created($row=[]){
$data = $row;
if(!is_array($row)){
$data = $data->toArray();
}
return $row;
}
function transfering($row=[]){
$data = $row;
if(!is_array($row)){
$data = $data->toArray();
}
// cache_add('user_withdrawl_total_'.$data['user_id'],$data['deduction_amount']);
// $parent_id = get_parent_id($data['user_id']);
// if($parent_id){
// cache_add('team_withdrawl_total_'.$parent_id,$data['deduction_amount']);
// }
post(Config('pay.server').'/index/withdrawl',[
'appid' => config('pay.appid'),
'amount' => $data['recive_amount'],
'network' => $data['network'],
'out_trade_no' => $data['id'],
'address' => $data['address'],
'notify_url' => config('pay.notify_server').'/api/withdrawl/notify',
//'from_address' => $config['from_address'],
//'private_key' => $config['private_key'],
'env' => 'product'
]);
return $row;
}
}
-601
View File
@@ -1,601 +0,0 @@
<?php
use support\Env;
if (!function_exists('admin_path')) {
function admin_path(){
return '/app/admin';
}
}
if (!function_exists('cache')) {
/**
* 缓存管理
* @param string $name 缓存名称
* @param mixed $value 缓存值
* @param mixed $options 缓存参数
* @param string $tag 缓存标签
* @return mixed
*/
function cache(string $name = null, $value = '', $options = null, $tag = null)
{
if (is_null($name)) {
return '';
}
if ('' === $value) {
// 获取缓存
return str_starts_with($name, '?') ? \support\think\Cache::has(substr($name, 1)) : \support\think\Cache::get($name);
} elseif (is_null($value)) {
// 删除缓存
return \support\think\Cache::delete($name);
}
// 缓存数据
if (is_array($options)) {
$expire = $options['expire'] ?? null; //修复查询缓存无法设置过期时间
} else {
$expire = $options;
}
if (is_null($tag)) {
return \support\think\Cache::set($name, $value, $expire);
} else {
return \support\think\Cache::tag($tag)->set($name, $value, $expire);
}
}
}
if (!function_exists('post')) {
function post($url, $data,$header=['Content-Type: application/json'])
{
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_POST, 1);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($data));
if($header){
curl_setopt($ch, CURLOPT_HTTPHEADER, $header); // 设置请求头
}
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
//curl_setopt($ch, CURLOPT_HTTPHEADER, array('Content-Type: application/json'));
$response = curl_exec($ch);
curl_close($ch);
return $response;
}
}
if (!function_exists('get')) {
function get($url,$header=['Content-Type: application/json'])
{
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
if($header){
curl_setopt($ch, CURLOPT_HTTPHEADER, $header); // 设置请求头
}
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$response = curl_exec($ch);
curl_close($ch);
return $response;
}
}
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);
}
}
/**
* 跨域检测
*/
if (!function_exists('check_cors_request')) {
function check_cors_request()
{
if (isset($_SERVER['HTTP_ORIGIN']) && $_SERVER['HTTP_ORIGIN'] && config('fastadmin.cors_request_domain')) {
$info = parse_url($_SERVER['HTTP_ORIGIN']);
$domainArr = explode(',', config('fastadmin.cors_request_domain'));
$domainArr[] = request()->host(true);
if (in_array("*", $domainArr) || in_array($_SERVER['HTTP_ORIGIN'], $domainArr) || (isset($info['host']) && in_array($info['host'], $domainArr))) {
header("Access-Control-Allow-Origin: " . $_SERVER['HTTP_ORIGIN']);
} else {
abort('跨域检测无效', 403);
}
header('Access-Control-Allow-Credentials: true');
header('Access-Control-Max-Age: 86400');
if ($_SERVER['REQUEST_METHOD'] == 'OPTIONS') {
if (isset($_SERVER['HTTP_ACCESS_CONTROL_REQUEST_METHOD'])) {
header("Access-Control-Allow-Methods: GET, POST, PUT, DELETE, OPTIONS");
}
if (isset($_SERVER['HTTP_ACCESS_CONTROL_REQUEST_HEADERS'])) {
header("Access-Control-Allow-Headers: {$_SERVER['HTTP_ACCESS_CONTROL_REQUEST_HEADERS']}");
}
abort('', 200);
}
}
}
}
if (!function_exists('check_ip_allowed')) {
/**
* 检测IP是否允许
* @param string $ip IP地址
*/
function check_ip_allowed($ip = null)
{
$ip = is_null($ip) ? getRealIp() : $ip;
$forbiddenipArr = config('site.forbiddenip');
$forbiddenipArr = !$forbiddenipArr ? [] : $forbiddenipArr;
$forbiddenipArr = is_array($forbiddenipArr) ? $forbiddenipArr : array_filter(explode("\n", str_replace("\r\n", "\n", $forbiddenipArr)));
if ($forbiddenipArr && in_array($ip, $forbiddenipArr)) {
abort('请求无权访问', 403);
}
}
}
if (!function_exists('Hook')) {
function Hook(?string $key = null, mixed $default = null)
{
//return \Webman\Event\Event::dispatch($key, $default);//不会自动处理错误
return \Webman\Event\Event::emit($key, $default);//会自动处理错误
}
}
if (!function_exists('addJob')) {
function addJob($data, $queue = 'Default', $delay = 0)
{
//$queue = 'Default';
if ($delay) {
// 投递延迟消息,消息会在60秒后处理
\Webman\RedisQueue\Redis::send($queue, $data, $delay);
} else {
// 投递消息
\Webman\RedisQueue\Redis::send($queue, $data);
}
}
}
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;
$expires = 5 * 60; //5分钟
$code = Request()->post('code');
$list = cache($cache_key);
$list = $list ?: [];
if (!isset($list[$code])) {
abort(__('Captcha is incorrect'));
}
if ($list[$code] + $expires < time()) {
unset($list[$code]);
cache($cache_key, $list);
abort(__('Captcha has expired'));
}
if($clear){
unset($list[$code]);
if ($event && $email) {
cache($cache_key, null);
} else {
cache($cache_key, $list);
}
}
return true;
}
}
if (!function_exists('cdnurl')) {
function cdnurl($path = '')
{
if(!$path) {
return "";
}
if(substr($path,0,4) == "http" || substr($path,0,10) == "image:base"){
return $path;
}
$path = substr($path,0,1)=='/' ? $path : '/'.$path;
return Config('site.cdnurl') . $path;
//return $path ? domain() . $path : $path;
}
}
if (!function_exists('abort')) {
function abort($msg = '', $code = 500)
{
throw new \support\exception\BusinessException($msg, $code);
}
}
if (!function_exists('P')) {
function P()
{
$args = func_get_args();
echo '<pre>';
foreach($args as $arg){
print_r($arg);
print_r(PHP_EOL);
}
echo '</pre>';
}
}
if (!function_exists('cp')) {
function cp()
{
$args = func_get_args();
foreach($args as $arg){
print_r($arg);
print("\t");
}
echo "\n";
}
}
if (!function_exists('formatAmount')) {
function formatAmount($amount, $wei = 4)
{
if (!$amount) {
return 0;
}
return round($amount, $wei);
}
}
if (!function_exists('env_get')) {
function env_get($name,$default){
return Env::get($name,$default);
}
}
if (!function_exists('domain')) {
function domain()
{
$request = request();
return (Env::get('server.https')?'https':'http').'://'.($request ? $request->host() : Env::get('server.domain',''));
}
}
if (!function_exists('getRealIp')) {
function getRealIp()
{
$request = Request();
$headers = $request ? $request->header() : [];
$ip = $request ? $request->getRealIp() : '';
if (isset($headers['cf-connecting-ip'])) {
$ip = $headers['cf-connecting-ip'];
}
return $ip;
}
}
if (!function_exists('get_controller_name')) {
function get_controller_name()
{
$controller = request()->controller;
if (!$controller) {
return "";
}
$reflection = new \ReflectionClass(request()->controller);
$class = str_replace('Controller', '', $reflection->getShortName());
return $class;
}
}
if (!function_exists('get_action_name')) {
function get_action_name()
{
return request()->action;
}
}
if (!function_exists('msectime')) {
function msectime()
{
list($msec, $sec) = explode(' ', microtime());
$msectime = (float) sprintf('%.0f', (floatval($msec) + floatval($sec)) * 1000);
return $msectime;
}
}
if (!function_exists('cache_add')) {
function cache_add(string $key, int $value = 1, ?string $tag = null): void
{
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;
}
}
$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 = '')
{
if (empty($id)) {
return '';
}
// 使用一个固定的种子值来增加随机性
$seed = 0x7F4A8C3B;
// 将用户ID转换为数字并加入种子
$num = intval($id) + $seed;
// 使用一个固定的字符集(去掉容易混淆的字符)
$chars = 'ABCDEFGHJKLMNPQRSTUVWXYZ';
$chars_len = strlen($chars);
$code = '';
// 生成8位邀请码
for ($i = 0; $i < 8; $i++) {
// 使用不同的数学运算来打乱数字
$num = ($num * 31 + $seed) % 0x7FFFFFFF;
// 确保每次取模的结果在字符集范围内
$index = ($num % $chars_len + $chars_len) % $chars_len;
$code .= $chars[$index];
}
return $code;
}
}
if (!function_exists('get_parent_id')) {
function get_parent_id($user_id)
{
if (!$user_id) {
return "";
}
$info = parent_info( $user_id);
return $info['id'];
}
}
if (!function_exists('parent_info')) {
function parent_info($user_id,$value=[])
{
if (!$user_id) {
return "";
}
if($value){
cache('user_parent_info_' . $user_id, $value);
return $value;
}
$info = cache('user_parent_info_' . $user_id);
if (!$info) {
$parent_id = \app\model\User::where('id', $user_id)
->value('parent_id');
$info = [['id'=>'','username'=>'']];
if($parent_id){
$info = \app\model\User::where('id',$parent_id)->column('id,username');
}
cache('user_parent_info_' . $user_id, $info[0]);
}
return $info;
}
}
if(!function_exists('datetime')){
function datetime($timestamp=0,$format='Y-m-d H:i:s'){
if(!$timestamp){return '';}
if(strpos($timestamp,'-')===false){
if(!$timestamp){return '';}
if($format == 'datetime'){
$format = 'Y-m-d H:i:s';
}
if($format == 'date'){
$format = 'Y-m-d';
}
if($format == 'time'){
$format = 'H:i:s';
}
return date($format,$timestamp);
}
return $timestamp;
}
}
if(!function_exists('log_alert')){
function log_alert($data='',$channel='default'){
if(!is_string($data)){
$data = json_encode($data);
}
// if(is_string($data) || is_numeric($data) || is_bool($data)){
// }else{
// $data = json_encode($data);
// }
\support\Log::channel($channel)->alert($data);
}
}
if(!function_exists('enum_dir')){
function enum_dir($path=''){
$list = [];
//$path = substr(0,1,$path) == '/' ? $path
foreach(glob($path) as $afile){
if(is_dir($afile)){
cp($afile);
//$list[] = enum_dir($afile);
} else {
$list[]=$afile;
//rename('./'.$afile,'./'.$name);
echo $afile,"\n";
}
}
return $list ;
}
}
if(!function_exists('get_user_rights')){
function get_user_rights($user_id):array{
$user_id = idDecode($user_id);
$key = 'user_rights_'.$user_id;
$result = cache($key);
if(!$result){
$result = \think\facade\Db::name('user_role')->alias('ur')
->join('user u','ur.id = u.role_id')
->where('u.id',$user_id)
->field('ur.name,ur.right')
->find();
$result['right'] = json_decode($result['right'],true);
cache($key,$result,86400);
}
return $result;
}
}
if(!function_exists('array_find')){
function array_find(array $array,callable $callbcak):mixed{
foreach ($array as $key => $value) {
if ($callbcak($value, $key)) {
return $value;
}
}
return null;
}
}
if(!function_exists('__my__template_inputs')){
function __my__template_inputs(&$template, &$vars, &$app, &$plugin){
// cp('__after__template_inputs:');
// cp('template:'.$template);
// cp('app:'.$app);
// cp('plugin:'.$plugin);
$request = request();
if(!$template){
$baseViewPath = $plugin ? base_path() . "/plugin/$plugin/app" : app_path();
$viewPath = $app === '' ? "$baseViewPath/view/" : "$baseViewPath/$app/view/";
$template = strtolower($request->controller_name."/".$request->action_name);
}
if(count(explode('/',$template)) == 1){
$template = strtolower($request->controller_name."/".$template);
}
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());
}
}
}
File diff suppressed because it is too large Load Diff
-60
View File
@@ -1,60 +0,0 @@
<?php
/**
* This file is part of webman.
*
* Licensed under The MIT License
* For full copyright and license information, please see the MIT-LICENSE.txt
* Redistributions of files must retain the above copyright notice.
*
* @author walkor<walkor@workerman.net>
* @copyright walkor<walkor@workerman.net>
* @link http://www.workerman.net/
* @license http://www.opensource.org/licenses/mit-license.php MIT License
*/
namespace app\mcp;
use app\mcp\McpService;
use support\Log;
class process
{
public function __construct()
{
}
public function onWorkerStart()
{
try {
$config = config('mcp');
$transport = $config['transport'] ?? 'sse';
$host = $config['host'] ?? '127.0.0.1';
$port = (int)($config['port'] ?? 8080);
$path = $config['path'] ?? 'mcp';
$service = new McpService();
switch ($transport) {
case 'stdio':
Log::channel('mcp')->info('Starting MCP with STDIO transport');
$service->startWithStdio();
break;
case 'http':
Log::channel('mcp')->info("Starting MCP with HTTP transport at http://{$host}:{$port}/{$path}");
$service->startWithHttp($host, $port, $path);
break;
case 'sse':
default:
Log::channel('mcp')->info("Starting MCP with SSE transport at http://{$host}:{$port}/{$path}");
$service->startWithSse($host, $port, $path);
break;
}
} catch (\Throwable $e) {
Log::channel('mcp')->error('MCP process start failed: ' . $e->getMessage(), [
'file' => $e->getFile(),
'line' => $e->getLine(),
]);
}
}
}
-20
View File
@@ -1,20 +0,0 @@
<?php
namespace plugin\admin\app\controller;
use think\facade\Db;
/**
* @ControllerAnnotation('{$description}')
* Class {$controllerClass}
* @package plugin\admin\app\controller
*/
class {$controllerClass} extends Crud
{
protected array \$noNeedLogin = [];
protected array \$noNeedRight = [];
function __construct()
{
$this->model = new \app\model\{:(str_replace('','',$controllerClass)};
}
}
-128
View File
@@ -1,128 +0,0 @@
<?php
namespace app\api\controller;
use think\facade\Db;
use support\Request;
use Tinywan\Validate\Facade\Validate;
use support\Jwt;
use hg\apidoc\annotation as Apidoc;
/**
* @ControllerAnnotation('{$description}')
* Class {$controllerClass}
* @package app\api\controller
*/
class {$controllerClass} extends BaseController
{
/**
*
* @var array
*/
protected array \$noNeedRight = [];
/**
*
* @var array
*/
protected array \$noNeedLogin = [];
function __construct()
{
$this->model = new \app\model\{:(str_replace('','',$controllerClass)};
}
/**
*
* @Apidoc\Method("POST")
* @Apidoc\Query("network", type="string", require=true, desc="网络")
* @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);
$page = (int)input('page',1);
$status = (int)input('status',-1);
$network = input('network');
$type = input('type');
$model = $this->model->where('user_id',\support\Jwt\JwtToken::getCurrentId());
//->where('network','BEP-20');
if($type){
$model = $model->where('status',1);
}
if($network){
$model = $model->where('network',$network);
}
$list = $model->paginate($limit);
return $this->success(__('successful'),$list->toArray());
}
/**
*
* @Apidoc\Method("POST")
* @Apidoc\Param("network", type="string", require=true, desc="网络,BEP-20,TRC-20",default="BEP-20")
* @Apidoc\Param("address", type="string", require=true, desc="地址")
* @Apidoc\Param("title", type="string", require=true, desc="名称")
* @Apidoc\Param("status", type="string", require=true, desc="状态,可选,1,0,默认1")
*/
public function create()
{
//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);
//* @Apidoc\Param("trade_password", type="string", require=true, desc="交易密码")
$data = [
'title' => input('title',''),
'network' => input('network','BEP-20'),
'address' => input('address'),
'status' => input('status',0),
'user_id' => \support\Jwt\JwtToken::getCurrentId()
];
if(!$data['title']){
return $this->error(__('Invalid title'));
}
// || substr($data['address'],0,2)!='0x'
if(!$data['address']){
return $this->error(__('Invalid address'));
}
$this->model->create($data);
return $this->success(__('successful'));
}
/**
*
* @Apidoc\Method("POST")
* @Apidoc\Param("id", type="string", require=true, desc="id")
* @Apidoc\Param("title", type="string", require=true, desc="名称")
* @Apidoc\Param("status", type="string", require=true, desc="状态,可选,1,0,默认1")
*/
public function update()
{
//captcha_verify('image','update_address');
//$trade_password = input('trade_password');
//\support\Jwt::verify_trade_password($trade_password);
$data = [
'id' => input('id',''),
'title' => input('title',''),
'status' => input('status',1)
];
if(!$data['id']){
return $this->error(__('Invalid parameters'));
}
if(!$data['title']){
return $this->error(__('Invalid title'));
}
$this->model->where('id',$data['id'])->save($data);
return $this->success(__('successful'));
}
/**
*
* @Apidoc\Query("id", type="int", require=true, desc="id")
*/
public function detail(){
$id = input('id');
$vo = $this->model->where('id',$id)->find();
if($vo) {
return $this->success(__('successful'),$vo->toArray());
}else{
return $this->error(__("Record is not exist"));
}
}
}
-43
View File
@@ -1,43 +0,0 @@
<?php
namespace app\controller;
use think\facade\Db;
/**
* @ControllerAnnotation('{$description}')
* Class {$controllerClass}
* @package app\controller
*/
class {$controllerClass} extends Base
{
protected array \$noNeedLogin = [];
protected array \$noNeedRight = [];
function __construct()
{
}
/**
* @NodeAnnotation(title='列表')
* @return mixed
*/
public function index()
{
return view();
}
/**
* @NodeAnnotation(title='添加')
* @return mixed
*/
public function add()
{
}
/**
* @NodeAnnotation(title='编辑')
* @return \\support\\response
*/
public function edit()
{
}
}
-113
View File
@@ -1,113 +0,0 @@
define(['table', 'upload','form'], function (Table,Upload,Form) {
var {$controllerClass} = {
index: function () {
var admin_path = Config.admin_path;
Table.api.init({
extend: {
index_url: '{:admin_path()}/{$controllerLower}/select',
add_url: '{:admin_path()}/{$controllerLower}/insert',
edit_url: '{:admin_path()}/{$controllerLower}/update',
del_url: '{:admin_path()}/{$controllerLower}/delete',
multi_url: '{:admin_path()}/{$controllerLower}/multi',
dragsort_url: '{:admin_path()}/{$controllerLower}/weigh',
table: 'admin',
}
});
var table = $("#table");
var tableOptions = {
url: $.fn.bootstrapTable.defaults.extend.index_url,
pk: 'id',
sortName: 'id',
pagination: false,
commonSearch: false,
search: false,
columns: [
[
{checkbox: true},
{
field: 'id',
title: 'ID',
filter: "number",
sortable: true //
},
{
field: 'username',
title: '用户名',
filter: "string",
},
{
field: 'role_name',
title: '角色',
filter: "string",
},
{
field: 'mobile',
title: '手机',
filter: "string",
},
{
field: 'email',
title: '邮箱',
filter: "string"
},
{
field: 'login_at',
title: '最后登录',
filter: "date",
visible:false
},
{
field: 'created_at',
title: '注册时间',
filter: "date",
visible:false
},
{
field: 'status',
title: '状态',
formatter:Table.api.formatter.switch
},
{field: 'operate', title: '操作', table: table, events: Table.api.events.operate, formatter: Table.api.formatter.operate}
]
]
};
//
table.bootstrapTable(tableOptions);
//
Table.api.bindevent(table);
},
update:function(){
Config['upload_url'] = '{:admin_path()}/file/avatar';
var form = $('form');
Form.api.bindevent(form)
this.getRole();
},
insert:function(){
Config['upload_url'] = '{:admin_path()}/file/avatar';
var form = $('form');
Form.api.bindevent(form)
this.getRole();
},
getRole:function(){
Fast.api.ajax({
url: "{:admin_path()}/adminrole/select?format=select",
dataType: "json",
success: function (res) {
var html = "";
var selected=$('#roles').data('value');
for (let index = 0; index < res.data.length; index++) {
const element = res.data[index];
if(selected == element.value){
html+='<option value="'+element.value+'" selected>'+element.name+'</option>';
}else{
html+='<option value="'+element.value+'">'+element.name+'</option>';
}
}
$('#roles').append(html);
}
});
}
};
return {$controllerClass}
});
-21
View File
@@ -1,21 +0,0 @@
{layout name="layout"}
<div class="toolbar" class="toolbar-btn-action">
<a id="btn_add" class="btn btn-primary m-r-5 btn-add" data-url="{:url('insert')}" data-title="新增">
<span class="mdi mdi-plus" aria-hidden="true"></span>新增
</a>
<a id="btn_edit" class="btn btn-success m-r-5 btn-disabled disabled btn-multi" data-params="status=1">
<span class="mdi mdi-check" aria-hidden="true"></span>启用
</a>
<a id="btn_edit" class="btn btn-warning m-r-5 btn-disabled disabled btn-multi" data-params="status=0">
<span class="mdi mdi-block-helper" aria-hidden="true"></span>禁用
</a>
<a id="btn_delete" class="btn btn-danger btn-del btn-disabled disabled">
<span class="mdi mdi-window-close" aria-hidden="true"></span>删除
</a>
</div>
<!-- 数据表格 -->
<div class="card">
<div class="card-body">
<table id="table"></table>
</div>
</div>
-99
View File
@@ -1,99 +0,0 @@
{layout name="layout"}
<div class="card">
<div class="card-body">
<form class="form-horizontal" action="__SELF__" method="post">
<input type="hidden" name="id" value="{$row.id|null}" />
{volist name="fields" id="vo"}
<?php
$fieldName = $field['name'];
$fieldComment = $field['comment'] ?? $fieldName;
$fieldType = $field['type'] ?? 'varchar';
?>
{switch $fieldType}
{case value='select'}
<div class="form-group">
<label class="control-label col-xs-12 col-sm-2">{$fieldComment}</label>
<div class="col-xs-12 col-sm-8 col-md-6">
<select name="{$fieldName}" class="form-control selectpicker">
{\\volist name="$vo.selectOptions" id="cvo"}
<option value="{\\$cvo.id}" {\\if $row['{$fieldName}']== $cvo.id}selected{\\/if}>{\\$cvo.title}</option>
{\\/volist}
</select>
</div>
</div>
{/case}
{case value='radio'}
<div class="form-group">
<label class="control-label col-xs-12 col-sm-2">{$fieldComment}</label>
<div class="col-xs-12 col-sm-8 col-md-6">
{\\volist name="$vo.selectOptions" id="cvo"}
<label class="lyear-radio radio-primary radio-inline">
<input type="radio" name="{$fieldName}" {\\if $row.{$fieldName} == $key} checked{\\/if} value="{\\$key}">
<span>{\\$rvo}</span>
</label>
{\\/volist}
</div>
</div>
{/case}
{case value='time'}
<div class="form-group">
<label for="type" class="control-label col-xs-12 col-sm-2">{$fieldComment}</label>
<div class="col-xs-12 col-sm-8 col-md-6">
<input type="password" name="{$fieldName}" placeholder="请输入{$fieldComment}" value="{\$row.{$fieldName}|null}" class="form-control" />
</div>
</div>
{/case}
{case value='image'}
<div class="form-group">
<label for="type" class="control-label col-xs-12 col-sm-2">{$fieldComment}</label>
<div class="col-xs-12 col-sm-8 col-md-6">
<input id="c-{$fieldName}" class="form-control" size="50" name="{$fieldName}" type="hidden" value="{\$row.{$fieldName}|default=''}" data-tip="image">
<ul class="list-inline clearfix lyear-uploads-pic" data-template="preview" id="p-{$fieldName}">
<li nodelete class="col-xs-4 col-sm-3 col-md-2">
<a class="pic-add faupload" style="height: auto;border: 0;" permission="app.admin.files.upload" id="add-pic-btn" href="#!" title="点击上传" data-input-id="c-{$fieldName}" data-mimetype="image/*" data-multiple="false" data-preview-id="p-{$fieldName}"></a>
<a class="pic-add fachoose" style="height: auto;border: 0;" permission="app.admin.files.list" id="choose-pic-btn" href="#!" title="选择文件" data-input-id="c-{$fieldName}" data-mimetype="image/*" data-multiple="false" data-preview-id="p-{$fieldName}"></a>
</li>
</ul>
</div>
</div>
{/case}
{case value='file'}
<div class="form-group">
<label for="type" class="control-label col-xs-12 col-sm-2">{$fieldComment}</label>
<div class="col-xs-12 col-sm-8 col-md-6">
<input id="c-{$fieldName}" class="form-control" size="50" name="{$fieldName}" type="hidden" value="{\$row.{$fieldName}|default=''}" data-tip="image">
<ul class="list-inline clearfix lyear-uploads-pic" data-template="preview" id="p-{$fieldName}">
<li nodelete class="col-xs-4 col-sm-3 col-md-2">
<a class="pic-add faupload" style="height: auto;border: 0;" permission="app.admin.files.upload" id="add-pic-btn" href="#!" title="点击上传" data-input-id="c-{$fieldName}" data-multiple="false" data-preview-id="p-{$fieldName}"></a>
<a class="pic-add fachoose" style="height: auto;border: 0;" permission="app.admin.files.list" id="choose-pic-btn" href="#!" title="选择文件" data-input-id="c-{$fieldName}" data-multiple="false" data-preview-id="p-{$fieldName}"></a>
</li>
</ul>
</div>
</div>
{/case}
{case value='password'}
<div class="form-group">
<label for="type" class="control-label col-xs-12 col-sm-2">{$fieldComment}</label>
<div class="col-xs-12 col-sm-8 col-md-6">
<input type="password" name="{$fieldName}" placeholder="请输入{$fieldComment}" value="{\$row.{$fieldName}|null}" class="form-control" />
</div>
</div>
{/case}
{default /}
<div class="form-group">
<label for="type" class="control-label col-xs-12 col-sm-2">{$fieldComment}</label>
<div class="col-xs-12 col-sm-8 col-md-6">
<input type="text" name="{$fieldName}" placeholder="请输入{$fieldComment}" value="{\$row.{$fieldName}|null}" class="form-control" />
</div>
</div>
{/switch}
<div class="form-group">
<label class="control-label col-xs-12 col-sm-2"></label>
<div class="col-xs-12 col-sm-8 col-md-6 layer-footer">
<button type="submit" class="btn btn-primary m-r-5">提交</button>
<button type="reset" class="btn btn-warning m-r-5">重置</button>
</div>
</div>
</form>
</div>
</div>
-57
View File
@@ -1,57 +0,0 @@
<?php
namespace app\middleware;
use Override;
use support\Container;
use Webman\MiddlewareInterface;
use Webman\Http\Response;
use Webman\Http\Request;
use Webman\Route;
class ActionHook implements MiddlewareInterface
{
public function process(Request $request, callable $next) : Response
{
if ($request->controller) {
$headers = [
'Access-Control-Allow-Credentials' => 'true',
'Access-Control-Allow-Origin' => $request->header('origin', '*'),
'Access-Control-Allow-Methods' => $request->header('access-control-request-method', '*'),
'Access-Control-Allow-Headers' => $request->header('access-control-request-headers', '*'),
];
if($request->method() == 'OPTIONS'){
$response = response('',204,$headers);
return $response;
}
// 禁止直接访问beforeAction afterAction
if (substr($request->action,0,9) === '__before_' || substr($request->action,0,8) === '__after_') {
$callback = Route::getFallback() ?? function () {
return new Response(404, [], \file_get_contents(public_path() . '/404.html'));
};
$reponse = $callback($request);
return $reponse instanceof Response ? $reponse : \response($reponse);
}
$controller = Container::get($request->controller);
$beforeAction = '__before_'.$request->action.'__';
if (method_exists($controller, $beforeAction)) {
$before_response = call_user_func([$controller, $beforeAction], $request);
if ($before_response instanceof Response) {
return $before_response;
}
}
$response = $next($request);
$afterAction = '__after_'.$request->action.'__';
if (method_exists($controller, $afterAction)) {
$after_response = call_user_func([$controller, $afterAction], $request, $response);
if ($after_response instanceof Response) {
return $after_response;
}
}
if($request->controller == '\\hg\\apidoc\\Controller' && !$response->getHeader('Access-Control-Allow-Methods')){
$response->withHeaders($headers);
}
return $response;
}
return $next($request);
}
}
-76
View File
@@ -1,76 +0,0 @@
<?php
namespace app\middleware;
use app\controller\MetricsController;
use support\Request;
use Webman\Http\Response;
/**
* 指标收集中间件
* 自动记录HTTP请求指标
*/
class MetricsMiddleware
{
/**
* 请求开始时间
*/
protected $startTime;
/**
* 处理请求
*
* @param Request $request
* @param callable $handler
* @return Response
*/
public function process(Request $request, callable $handler): Response
{
// 记录请求开始时间
$this->startTime = microtime(true);
// 处理请求
$response = $handler($request);
// 计算请求处理时间
$duration = microtime(true) - $this->startTime;
// 记录指标
$this->recordMetrics($request, $response, $duration);
return $response;
}
/**
* 记录指标
*
* @param Request $request
* @param Response $response
* @param float $duration
*/
protected function recordMetrics(Request $request, Response $response, float $duration): void
{
try {
$method = $request->method();
$path = $request->path();
$status = $response->getStatusCode();
// 获取响应大小
$responseSize = strlen($response->rawBody());
// 记录HTTP请求指标
MetricsController::recordHttpRequest($method, $path, $status, $duration, $responseSize);
// 记录请求处理时间
MetricsController::setGauge('webman_http_request_duration_seconds', $duration, [
'method' => $method,
'path' => $path,
'status' => (string)$status
]);
} catch (\Exception $e) {
// 记录指标失败不应影响正常请求
error_log('Metrics recording failed: ' . $e->getMessage());
}
}
}
-42
View File
@@ -1,42 +0,0 @@
<?php
/**
* This file is part of webman.
*
* Licensed under The MIT License
* For full copyright and license information, please see the MIT-LICENSE.txt
* Redistributions of files must retain the above copyright notice.
*
* @author walkor<walkor@workerman.net>
* @copyright walkor<walkor@workerman.net>
* @link http://www.workerman.net/
* @license http://www.opensource.org/licenses/mit-license.php MIT License
*/
namespace app\middleware;
use Webman\MiddlewareInterface;
use Webman\Http\Response;
use Webman\Http\Request;
/**
* Class StaticFile
* @package app\middleware
*/
class StaticFile implements MiddlewareInterface
{
public function process(Request $request, callable $next): Response
{
// Access to files beginning with. Is prohibited
if (strpos($request->path(), '/.') !== false) {
return response('<h1>403 forbidden</h1>', 403);
}
/** @var Response $response */
$response = $next($request);
// Add cross domain HTTP header
/*$response->withHeaders([
'Access-Control-Allow-Origin' => '*',
'Access-Control-Allow-Credentials' => 'true',
]);*/
return $response;
}
}
-44
View File
@@ -1,44 +0,0 @@
<?php
namespace app\model;
use app\model\Base;
/**
* @property integer $id 主键(ID) - 无注释
* @property integer $user_id 用户ID
* @property string $title 名字
* @property string $network 网络
* @property string $address 账号
* @property string $img 图片
* @property integer $is_default 是否默认
* @property integer $created_at 创建时间
* @property integer $updated_at 更新时间
* @property integer $status 状态
*/
class Address extends Base
{
//protected $name = 'address';
function getNetworkList(){
return [
"BEP-20"=>"BEP-20",
"TRC-20"=>"TRC-20",
"WECHAT"=>"微信",
"ALIPAY"=>"支付宝"
];
}
function getStatusList(){
return [
'0' => '禁用',
'1' => '启用',
];
}
public function user()
{
return $this->belongsTo('User', 'user_id', 'id');//->setEagerlyType(0);
}
}
-70
View File
@@ -1,70 +0,0 @@
<?php
namespace app\model;
/**
* 相册模型
* @property integer $id 主键(ID)
* @property integer $user_id 用户ID
* @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:正常)
*/
class Album extends Base
{
protected $name = 'album';
protected function getOptions(): array
{
return array_merge(parent::getOptions(), [
'insert' => [
'status' => 1,
],
'append'=>[
'userID',
'groupID'
]
]);
}
public static function onAfterInsert($row){
$changeData = $row->getChangedData();
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['image']) && $OrgData['image']) {
\support\Log::info('OrgData string');
Files::where('path',$OrgData['image'])->dec('use_count');
};
if(isset($changeData['image']) && $changeData['image']) {
\support\Log::info('changeData string');
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->image)->dec('use_count');
}
function getGroupIDAttr($v,$row){
return $v?:$row['group_id'];
}
function getUserIDAttr($v,$row){
return $v?:$row['user_id'];
}
}
-107
View File
@@ -1,107 +0,0 @@
<?php
namespace app\model;
use support\think\Db;
use traits\model\SoftDelete;
class Archives extends Base
{
//use SoftDelete;// 表名
protected function getOptions(): array{
return array_merge(parent::getOptions(),[
'append' => [
'status_text'
],
]);
}
public static function onAfterInsert(Archives $row)
{
\support\Log::error(''. json_encode($row));
$pk = $row->getPk();
self::where($pk, $row->$pk)->update(['weigh' => $row[$pk]]);
$changedData = $row->getData();
if (isset($changedData['content'])) {
//在更新成功后刷新副表
$values = array_intersect_key($changedData, array_flip(['content']));
$values['id'] = $row['id'];
//更新副表
Db::name('content')->insert($values, true);
}
}
public static function onAfterUpdate($row)
{
\support\Log::info('onAfterUpdate'.$row->id. json_encode($row->getChangedData()));
$changedData = $row->getChangedData();
if (isset($changedData['content'])) {
//在更新成功后刷新副表
$values = array_intersect_key($row->getData(), array_flip(['content']));
//更新副表
Db::name('content')->where('id',$row->id)->update($values);
}
}
public static function onAfterDelete($row){
//删除副表
Db::name('content')->where("id", $row['id'])->delete();
}
/**
* 批量设置数据
* @param $data
* @return $this
*/
public function setAddonData(string|array|object $data)
{
if (is_object($data)) {
$data = get_object_vars($data);
}
foreach($data as $k=>$v){
$this->$k=$v;
}
return $this;
}
public function getStatusList()
{
return ['1' => '正常', '0' => '隐藏'];
}
public function getStatusTextAttr($value, $data)
{
$value = $value ? $value : (isset($data['status']) ? $data['status'] : '');
$list = $this->getStatusList();
return isset($list[$value]) ? $list[$value] : '';
}
public function getCategoryOptions($type='default'){
return Category::where('status','1')->where('type',$type)->column('id,title');
}
function setCreatedAtAttr($v){
if($v && strpos($v,'-')){
return strtotime($v);
}
return $v;
}
function setUpdatedAtAttr($v){
if($v && strpos($v,'-')){
return strtotime($v);
}
return $v;
}
public function getFlagList()
{
return Config('site.flagtype');
}
public function content()
{
return $this->hasOne('content', 'id', 'id');
// ->bind(['content']);//->setEagerlyType(0);
}
public function category()
{
return $this->belongsTo('Category', 'category_id', 'id');//->setEagerlyType(0);
}
}
-226
View File
@@ -1,226 +0,0 @@
<?php
namespace app\model;
use Symfony\Component\Console\Input\Input;
use think\Model;
use think\facade\Db;
class BalanceLog extends Base
{
// 表结构定义(使用时间戳)
const TABLE_SCHEMA = [
'id' => 'int(11) NOT NULL AUTO_INCREMENT',
'user_id' => 'int(11) NOT NULL',
'currency' => 'varchar(20) NOT NULL',
'amount' => 'decimal(15,2) NOT NULL',
'before' => 'decimal(15,2) NOT NULL',
'after' => 'decimal(15,2) NOT NULL',
'type' => 'varchar(50) NOT NULL',
'created_at' => 'int(11) NOT NULL COMMENT \'UNIX timestamp\'', // 改为整型时间戳
'memo' => 'varchar(255) DEFAULT NULL',
'PRIMARY KEY (`id`)'
];
function getCreatedAtAttr($v){
return $v ? explode('.',$v)[0] : '';
}
protected function getOptions(): array{
return array_merge(parent::getOptions(),[
'connection' => 'mongodb',
// 'append' => [
// 'from_user',
// 'to_user'
// ],
]);
}
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'])){
if(in_array($data['currency'],Config('site.allow_currency_logs'))){
$data['status']=isset($data['status']) ? $data['status']:1;
$data['user_id'] = intval($data['user_id']);
$data['amount'] = floatval($data['amount']);
$data['before'] = floatval($data['before']);
$data['after'] = floatval($data['after']);
$data['type'] = $data['type'] instanceof \app\enum\BalanceType ? $data['type']->value : floatval($data['type']);
$model->setSuffix('_'.strtolower($data['currency']))->allowField($allowField)
->replace($replace)
->save($data, true);
}
}
return $model->fetchModel($model);
}
// 创建所有需要的表索引
public static function createAllIndexes(): array
{
$results = [];
$allow_currency_logs = Config('site.allow_currency_logs');
foreach ($allow_currency_logs as $currency) {
$results[$currency] = self::createTableIndexes($currency);
}
return $results;
}
// 创建索引(适配时间戳查询)
public static function createTableIndexes(string $currency): array
{
$table = self::getTableName($currency);
$results = [];
try {
// 确保归档表存在
if (!self::tableExists($table)) {
self::createTableStructure($table);
}
// 主复合索引(使用时间戳)
if (!self::indexExists($table, 'idx_user_currency_type_created')) {
Db::execute("ALTER TABLE `{$table}` ADD INDEX `idx_user_currency_type_created` (`user_id`, `currency`, `type`, `created_at`)");
$results[] = "Created idx_user_currency_type_created on {$table}";
}
// 时间索引(降序优化)
if (!self::indexExists($table, 'idx_created_at')) {
Db::execute("ALTER TABLE `{$table}` ADD INDEX `idx_created_at` (`created_at` DESC)");
$results[] = "Created idx_created_at on {$table}";
}
} catch (\Throwable $e) {
$results['error'] = "Error on {$table}: " . $e->getMessage();
}
return $results;
}
// 检查索引是否存在
protected static function indexExists(string $table, string $indexName): bool
{
$indexes = Db::query("SHOW INDEX FROM `{$table}` WHERE Key_name = ?", [$indexName]);
return !empty($indexes);
}
// 检查表是否存在
protected static function tableExists(string $table): bool
{
try {
Db::query("SELECT 1 FROM `{$table}` LIMIT 1");
return true;
} catch (\Throwable $e) {
return false;
}
}
// 数据归档方法(可在定时任务中调用)
public static function archiveData(int $days = 3): array
{
$results = [];
$allow_currency_logs = Config('site.allow_currency_logs');
foreach ($allow_currency_logs as $currency) {
$results[$currency] = self::archiveCurrencyData($currency, $days);
}
return $results;
}
// 归档指定货币的数据
protected static function archiveCurrencyData(string $currency, int $days): array
{
$table = self::getTableName($currency);
$archiveTable = $table . '_archive';
$cutoffTimestamp = time() - ($days * 86400); // 转为时间戳计算
$result = [
'table' => $table,
'archived' => 0,
'messages' => []
];
try {
// 确保归档表存在
if (!self::tableExists($archiveTable)) {
self::createTableStructure($archiveTable);
$result['messages'][] = "Created archive table: {$archiveTable}";
}
// 分批归档数据
$totalArchived = 0;
Db::table($table)
->where('created_at', '<=', $cutoffTimestamp)
->chunk(1000, function($logs) use ($archiveTable, $table, &$totalArchived) {
Db::table($archiveTable)->insertAll($logs);
$count = count($logs);
Db::table($table)->whereIn('id', array_column($logs, 'id'))->delete();
$totalArchived += $count;
});
$result['archived'] = $totalArchived;
$result['messages'][] = "Archived {$totalArchived} records from {$table}";
// 优化表
Db::execute("OPTIMIZE TABLE `{$table}`");
$result['messages'][] = "Optimized table: {$table}";
} catch (\Throwable $e) {
$result['error'] = $e->getMessage();
}
return $result;
}
// 查询方法(示例)
public static function queryLogs($userId, $currency, $type = null, $startTime = null, $endTime = null)
{
$model = new static;
$query = $model->setSuffix('_'.strtolower($currency))
//->where('currency', $currency)
->where('user_id', intval($userId))
->order('created_at', 'desc');
if ($type) {
$temp_arr = explode(',', $type); // 得到 ["1", "2", "3", "4"]
$arr = array_map('intval', $temp_arr); // 得到 [1, 2, 3, 4]
$query->whereIn('type', $arr);
}
if ($startTime) {
// 支持传入时间戳或日期字符串
//$startTimestamp = is_numeric($startTime) ? intval($startTime) : strtotime($startTime);
$query->where('created_at', '>=', $startTime);
}
if ($endTime) {
// 支持传入时间戳或日期字符串
//$endTimestamp = is_numeric($endTime) ? intval($endTime) : strtotime($endTime);
$query->where('created_at', '<=', $endTime);
}
$limit = 10;
if(request()){
$limit = input('limit',10);
}
return $query->paginate($limit);
}
// 创建表结构
protected static function createTableStructure(string $table): bool
{
if (self::tableExists($table)) {
return false;
}
$columns = [];
foreach (self::TABLE_SCHEMA as $column => $definition) {
if (strpos($definition, 'PRIMARY KEY') === false) {
$columns[] = "`{$column}` {$definition}";
}
}
$primaryKey = self::TABLE_SCHEMA['PRIMARY KEY'] ?? 'PRIMARY KEY (`id`)';
$sql = "CREATE TABLE `{$table}` (" .
implode(', ', $columns) . ", " .
$primaryKey .
") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci";
Db::execute($sql);
return true;
}
}
-51
View File
@@ -1,51 +0,0 @@
<?php
namespace app\model;
use DateTimeInterface;
use support\think\Model;
class Base extends Model
{
protected function getOptions(): array{
return [
'connection' => 'mysql',
'createTime' => 'created_at',
'updateTime' => 'updated_at',
'deleteTime' => 'deleted_at',
'autoWriteTimestamp' => 'int',
//'dateFormat' => false
// query 自定义数据库查询对象类名(默认为空)
// type 需要自动转换的字段及类型(数组,默认为空)
// autoValidate 是否自动验证(开启后会自动进行数据验证)
// validate 对应验证类名或验证规则(字符串或数组,autoValidate参数开启后有效)
// strict 是否严格区分字段大小写(默认为true)
// disuse 废弃字段(数组,默认为空)
// readonly 只读字段(数组,默认为空)
// hidden 输出隐藏字段(数组,默认为空)
// visible 输出显示字段(数组,默认为空)
// append 输出追加字段(数组,默认为空)
// mapping 字段映射(数组,默认为空)
// autoRelation 自动with关联(数组,默认为空)
// insert 自动新增写入(数组,默认为空)
// update 自动更新写入(数组,默认为空)
// dateFormat 时间输出格式化设置
];
}
/**
* 格式化日期
*
* @param DateTimeInterface $date
* @return string
*/
protected function serializeDate(DateTimeInterface $date)
{
return $date->format('Y-m-d H:i:s');
}
function getStatusList(){
return [
'0' => '隐藏',
'1' => '正常',
];
}
}
-21
View File
@@ -1,21 +0,0 @@
<?php
namespace app\model;
/**
* @property integer $id 主键(ID) - 无注释
* @property integer $user_id 用户ID
* @property float $amount 总价
* @property integer $type 类型
* @property string $title 标题
* @property integer $total 总数量
* @property integer $used 已使用的数量
* @property integer $expires 过期时间
* @property integer $days 量
* @property integer $created_at 创建时间
* @property integer $updated_at 更新时间
* @property integer $status 状态
*/
class Card extends Base
{
protected $name = 'card';
}

Some files were not shown because too many files have changed in this diff Show More