Compare commits

..

16 Commits

Author SHA1 Message Date
commie 0a45a8fbb9 15 2026-03-01 00:24:34 +08:00
commie 873c7cf9c2 14 2026-02-28 19:24:05 +08:00
commie 73f67b4143 13 2026-02-28 16:18:52 +08:00
commie d75fea32f7 12 2026-02-27 13:53:53 +08:00
commie c9c8a120ab 11 2026-02-24 21:02:17 +08:00
commie 6586f27c9e 10 2026-02-21 08:21:05 +08:00
commie 1a7f4bc98a 9 2026-02-15 19:41:56 +08:00
commie 61c5192018 8 2026-01-12 12:42:08 +08:00
commie c153975eed 7 2026-01-08 05:42:44 +08:00
commie 7439a4a794 6 2025-12-25 23:30:14 +08:00
commie 7c1d6d447e 5 2025-12-25 06:02:38 +08:00
commie 20d230f6c8 database 2025-12-24 17:03:10 +08:00
commie b68946fe79 4 2025-12-24 16:59:05 +08:00
commie b52a51c09b 3 2025-11-22 15:31:01 +08:00
commie 9f25a85d07 2 2025-11-21 01:45:26 +08:00
commie f89196c73c 1 2025-11-21 01:42:54 +08:00
3201 changed files with 601181 additions and 173920 deletions
Executable
+38
View File
@@ -0,0 +1,38 @@
[app]
debug = true
[server]
port=8585
domain=www.wenjb.com
https=false
[mysql]
host =127.0.0.1
port = 3307
database = questionnaire
username = questionnaire
password = ba9b492bda93ad4d
charset = utf8mb4
collation = utf8mb4_general_ci
prefix = wa_
strict = true
engine =
[mongodb]
host = 127.0.0.1
port = 27017
database = questionnaire_m
username = commie
password = n1e5a6s6m7
[redis]
host = 127.0.0.1
port = 6379
database = 10
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
@@ -0,0 +1,2 @@
* text=auto eol=lf
*.txt -text
Regular → Executable
+9 -46
View File
@@ -1,48 +1,11 @@
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
# dependencies
/node_modules
/.pnp
.pnp.js
node_modules
*.lock
unpackage
package-lock.json
.hbuilderx
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
config/site.php
app/command/Test.php
.env
runtime
vendor
public/shunliao.apk
+1
View File
@@ -0,0 +1 @@
-2
View File
@@ -1,2 +0,0 @@
/node_modules/
/nativeplugins/
+1
View File
@@ -0,0 +1 @@
open_basedir=/www/wwwroot/admin/:/tmp/
Vendored Executable
+3
View File
@@ -0,0 +1,3 @@
{
"php.version": "8.2"
}
-624
View File
@@ -1,624 +0,0 @@
<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
@@ -1,123 +0,0 @@
```
安卓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
@@ -1,187 +0,0 @@
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);
})
})
}
+126
View File
@@ -0,0 +1,126 @@
<?php
namespace app\api\controller;
use support\Request;
use taoser\facade\Validate;
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_verfiy('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_verfiy('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"));
}
}
}
+144
View File
@@ -0,0 +1,144 @@
<?php
namespace app\api\controller;
use app\model\Archives as ArchivesModel;
use support\Request;
use taoser\facade\Validate;
use hg\apidoc\annotation as Apidoc;
/**
* 文章模块
*/
class ArticleController extends BaseController{
public $noNeedLogin = ['*'];
/**
* 列表
* @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','1')->where('type','article');
if($category_id){
$model = $model->where('category_id',$category_id);
}
$list = $model->order('id','desc')->paginate($limit);
$user_id=0;
try {
$user_id = \support\Jwt\JwtToken::getCurrentId();
} catch (\Throwable $th) {
}
$list->each(function($item)use($user_id){
if(!$user_id){
$item->is_read = 0;
}
$item->is_read = cache('article_read_'.$item->id.'_'.$user_id)?:0;
return $item;
});
return $this->success(__('successful'),$list->toArray());
}
/**
* faq
* @Apidoc\Query("page", type="int", require=true, desc="页码",default=1)
* @Apidoc\Query("limit", type="int", require=true, desc="分页大小",default=10)
*/
public function faq(){
$limit = (int)input('limit',10);
$model = ArchivesModel::alias('a')
->join('content c', 'a.id = c.id')
->where('a.status','1')
->where('a.type','article')
->where('a.category_id',9);
$list = $model->Field('a.title,a.id,c.content')->order('a.id','desc')->paginate($limit);
return $this->success(__('successful'),$list->toArray());
}
/**
* 详情
* @Apidoc\Query("id", type="int", require=true, desc="ID")
*/
public function detail(){
$appid = input('id');
/** @var ArchivesModel $vo */
$vo = ArchivesModel::where('id',$appid)->find();
if($vo) {
$addon = \app\model\Content::where('id', $vo->id)->find()->toArray();
if ($addon) {
$vo->setAddonData($addon);
}
$user_id=0;
try {
$user_id = \support\Jwt\JwtToken::getCurrentId();
} catch (\Throwable $th) {
}
if($user_id){
cache('article_read_'.$vo->id.'_'.$user_id,1);
}
return $this->success(__('successful'),$vo->toArray());
}else{
return $this->error(__("Article does not exist"));
}
}
/**
* 单页详情
* @Apidoc\Query("id", type="int", require=true, desc="ID")
* @Apidoc\Query("name", type="string", require=true, desc="二选1")
*/
public function singpage(){
$appid = input('id');
$name = input('name');
/** @var ArchivesModel $vo */
if($name){
$vo = ArchivesModel::where('name',$name)->find();
}else{
if($appid){
$vo = ArchivesModel::where('id',$appid)->find();
}
}
if($vo) {
$addon = \app\model\Content::where('id', $vo->id)->find()->toArray();
if ($addon) {
$vo->setAddonData($addon);
}
return $this->success(__('successful'),$vo->toArray());
}else{
return $this->error(__("Article does not exist"));
}
}
/**
* 幻灯片
* @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,多个逗号隔开")
*/
function mask_as_read(){
$ids = input('id');
$user_id = \support\Jwt\JwtToken::getCurrentId();
if(!$user_id){
return $this->success(__('successful'));
}
$ids = explode(',',$ids);
foreach ($ids as $id) {
$key = 'article_read_'.$id.'_'.$user_id;
cache($key,1);
}
return $this->success(__('successful'));
}
}
+50
View File
@@ -0,0 +1,50 @@
<?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 = idEncode($item->memo);
}
$item->_type= $item->type;
$item->type= $BalanceTypeList[$item->type];
return $item;
});
return $this->success(__('successful'),$list);
}
}
+121
View File
@@ -0,0 +1,121 @@
<?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 taoser\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")
*/
function upload(Request $request,$return = false)
{
$res = $this->_upload($request);
if(is_string($res)){
return $this->fail( $res);
}
return $this->success(__('successful'),$res);
}
}
+125
View File
@@ -0,0 +1,125 @@
<?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 taoser\facade\Validate;
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'));
}
}
+505
View File
@@ -0,0 +1,505 @@
<?php
namespace app\api\controller;
use taoser\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 = input('lang','en-US');
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['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("email", 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_verfiy('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_verfiy('mobile','register',$mobile,false);
}
if ($type == 'username') {
if(!$email || !Validate::is($email, "email")){
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';
$extends = [
'role_id' => 1,
'group_id' => 0,
'region' => '86',
'nickname' => input('nickname'),
'avatar' => '/static/avatar/'.rand(0,17).'.png',
];
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_verfiy('email','register',$email,true);
// }else if ($type == 'mobile') {
// captcha_verfiy('mobile','register',$mobile,true);
// }else{
// captcha_verfiy('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("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("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){
//log_alert($e->getMessage());
$user = false;
}
if($user){
captcha_verfiy('mobile','reset_pwd',$user->mobile);
}
}else{
if ($mobile && Validate::regex($mobile, "^1\d{10}$")) {
captcha_verfiy('mobile','reset_pwd',$mobile);
$region = Input('region');
$region = str_replace('+','',$region);
$user = UserModel::where('region',$region)->where('mobile',$mobile)->find();
}else if ($email && Validate::is($email, "email")) {
captcha_verfiy('email','reset_pwd',$email);
$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")
*/
public function reset_trade_pwd()
{
$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(__('Trade password must be 6-32 characters'));
}
if (!$mobile && !$email){
try{
$user = \support\Jwt::getUser();
}catch(\Exception $e){
log_alert($e->getMessage());
$user = false;
}
if($user){
captcha_verfiy('mobile','reset_trade_pwd',$user->mobile);
}
}else{
if ($mobile && Validate::regex($mobile, "^1\d{10}$")) {
captcha_verfiy('mobile','reset_trade_pwd',$mobile);
$user = UserModel::getByMobile($mobile);
}elseif ($email && Validate::is($email, "email")) {
captcha_verfiy('email','reset_trade_pwd',$email);
$user = UserModel::getByEmail($email);
}
}
if (!$user) {
return $this->error(__('Invalid parameters'));
}
//模拟一次登录,需不需要充值登录信息?????
//\support\Jwt::direct($user->id);
try{
log_alert($user->id.' 重置交易密码'.$newpassword);
UserModel::where('id',$user->id)->save([
'trade_password' => \plugin\admin\app\common\Util::passwordHash($newpassword)
]);
} 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(__('Incoret param'));
}
}
$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' => __("Mt email code"),
// 'event' => $event,
// 'code' => $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(__('Incoret param'));
}
}
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');
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_verfiy('email', $event , $email,false);
}elseif($type == 'mobile'){
$result = captcha_verfiy('mobile', $event , $mobile,false);
}else{
$result = captcha_verfiy('image', $event , '',false);
}
if(!$result){
return $this->fail(__('Captcha is incorrect'));
}
} catch (\Exception $e) {
return $this->fail($e->getMessage());
}
return $this->success(__('successful'));
}
}
+88
View File
@@ -0,0 +1,88 @@
<?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 taoser\facade\Validate;
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 = idEncode($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');
}
$userID = idDecode($userID);
$res = \app\model\User::where('id',$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');
}
//$userIDs = explode(',',$userIDs);
//$userIDs = idDecode($userIDs);
//$current_user = \support\Jwt::getUser();
//$user_id = $current_user->id;
//$userID = idEncode($user_id);
$res = \app\model\User::whereIn('userID',$userIDs)->select();
return $this->success('ok',$res);
}
/**
* @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);
}
}
+445
View File
@@ -0,0 +1,445 @@
<?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 taoser\facade\Validate;
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 = idDecode($user_id);
$json= [
'top_unread_items' =>[],
'unread_item_ids' =>[],
'unread_count' =>0,
'settings' => Db::name('friend_circle_setting')->where('user_id',$user_id)->order('id','desc')->findOrEmpty()
];
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('friend_circle_setting')->where('user_id',$user_id)->order('id','desc')->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 = idDecode($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(idEncode($user_id));
$friendsInfo = $res['friendsInfo'];
foreach($friendsInfo as $k=>$v){
array_push($result,$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('friend_circle_setting')->replace()->insert([
'user_id' => $user->id,
'bg' => $res[0]['file_name'],
'allow_days'=>0,
'created_at'=>0
]);
//$result->ss = cdnurl($result->url);
//P($result);
return $this->success(__('successful'),[
'url'=>$res[0]['file_name']
]);
}catch (\Exception $e){
return $this->error($e->getMessage());
}
}
}
+42
View File
@@ -0,0 +1,42 @@
<?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 taoser\facade\Validate;
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');
}
}
+161
View File
@@ -0,0 +1,161 @@
<?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 taoser\facade\Validate;
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'));
}
}
+129
View File
@@ -0,0 +1,129 @@
<?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 GroupController extends BaseController
{
public $noNeedAuth = ['*'];
public $noNeedLogin = [];
/**
* @Apidoc\Title("群相片列表")
* @Apidoc\Method("POST")
* @Apidoc\Param("group_id", type="string", require=true, desc="群ID")
* @Apidoc\Param("page", type="int", require=true, desc="页码",default=1)
* @Apidoc\Param("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);
//log_alert($ls);
log_alert([$offset,$group_id,$limit]);
$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(__('参数错误'));
}
$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
@@ -0,0 +1,17 @@
<?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"));
}
}
+33
View File
@@ -0,0 +1,33 @@
<?php
namespace app\api\controller;
use app\model\User as UserModel;
use support\Request;
use support\Response;
use taoser\facade\Validate;
use support\think\Db;
use hg\apidoc\annotation as Apidoc;
/**
* 消息控制器
* @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',idEncode(100007),[
'contentType' => 101,
'textElem' => [
'content' => '欢迎使用4'.Config('site.name')
]
],'group');
return $this->success('ok');
}
}
+71
View File
@@ -0,0 +1,71 @@
<?php
namespace app\api\controller;
use app\model\Product as ProductModel;
use app\model\ProductOrder as ProductOrderModel;
use support\think\Db;
use taoser\facade\Validate;
use hg\apidoc\annotation as Apidoc;
/**
* 产品模块
* @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());
}
}
+147
View File
@@ -0,0 +1,147 @@
<?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 taoser\facade\Validate;
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'));
}
}
+483
View File
@@ -0,0 +1,483 @@
<?php
namespace app\api\controller;
use support\Request;
use support\think\Db;
use taoser\facade\Validate;
use app\model\Recharge as RechargeModel;
use app\model\User as UserModel;
use hg\apidoc\annotation as Apidoc;
/**
* 充值模块
* @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 = 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 = 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
@@ -0,0 +1,295 @@
<?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());
}
}
}
+203
View File
@@ -0,0 +1,203 @@
<?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=[
'total_count' => count($team_ids),//团队总人数
'direct_total' => cache('team_direct_total_'.$user_id)??0,//直属团队人数
'recharge_total' => cache('team_recharge_total_'.$user_id)??0,
'withdrawl_total' => cache('team_withdrawl_total_'.$user_id)??0,
'income_total' => cache('team_income_total_'.$user_id)??0,
'today_income_total' => cache('user_today_income_total_'.$user_id)??0,
'promotion_income_total' => cache('user_promotion_income_total_'.$user_id)??0,
'consume_total' => Db::name('user_extend')->where('user_id',$user_id)->value('sales'),//cache('team_consume_total_'.$user_id)??0,//团队总业绩
'user_sales_reward' => cache('user_sales_reward_'.$user_id)??0,//销售奖
'user_output_reward' => cache('user_output_reward_'.$user_id)??0,//产值奖
'user_withdrawl_reward' => cache('user_withdrawl_reward'.$user_id)??0,//提现奖
'user' => $user[0],
];
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.username,u.money,u.score,u.role_id, u.group,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);
}
$role_arr = [
'0' => __('普通用户'),
'1' => __('V1'),
'2' => __('V2'),
'3' => __('V3'),
'4' => __('V4'),
'5' => __('V5'),
];
$result = $result->toArray();
foreach($result['data'] as $k=>$item){
$result['data'][$k]['avatar'] = cdnurl($item['avatar'] ?: '/storage/avatar/default.png');
$result['data'][$k]['recharge_total'] = cache('user_recharge_total_'.$item['id'])??0;
$result['data'][$k]['withdrawl_total'] = cache('user_withdrawl_total_'.$item['id'])??0;
$result['data'][$k]['withdrawl_reward'] = cache('user_withdrawl_reward_'.$item['id'])??0;
$result['data'][$k]['income_total'] = cache('user_income_total_'.$item['id'])??0;
$result['data'][$k]['consume_total'] = cache('user_consume_total_'.$item['id'])??0;
$result['data'][$k]['play_count'] = cache('user_play_count_'.$item['id'])??0;
//$result['data'][$k]['created_at'] = date('Y-m-d H:i:s', $item['created_at']);
$result['data'][$k]['total_count'] = UserTeamModel::where('ancestor_id',$item['id'])->where('status',1)->where('depth','>',0)->count('descendant_id');
$result['data'][$k]['direct_total'] = cache('team_direct_total_'.$item['id'])??0;
$result['data'][$k]['role'] = isset($role_arr[$item['role_id']]) ? $role_arr[$item['role_id']] : __('普通用户');
//$result['data'][$k]['questionnaire_count'] = WorkRecordModel::where('user_id',$item['id'])->count('id');
$result['data'][$k]['id'] = idEncode($item['id']);
//return $item;
}
return $this->success(__('successful'),$result);
}
/**
* @Apidoc\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 = idDecode($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'));
}
}
+158
View File
@@ -0,0 +1,158 @@
<?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;
}
//升级
$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);
//Hook('user.roleup', $user);
// $data = [
// 'role_id' => $role_id,
// 'user_id' => $user->id,
// 'parent_id' => $user->parent_id,
// 'amount' => $amount,
// ];
// Hook('role.buy', $data);
return $this->success(__('successful'),$user);
}
}
+260
View File
@@ -0,0 +1,260 @@
<?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 taoser\facade\Validate;
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= 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="新密码")
*/
public function change_trade_password(){
$password = input('password');
$newpassword = input('newpassword');
$renewpassword = input('renewpassword');
if (!$newpassword || !$renewpassword || $newpassword !== $renewpassword) {
return $this->error(__('Invalid parameters'));
}
try{
\support\Jwt::change_trade_pwd($newpassword,$password);
return $this->success(__('Reset trade password successful'));
} catch (\Throwable $e) {
return $this->error($e->getMessage());
}
}
/**
* 根据关键字查询用户列表
* @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);
}
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,
];
log_alert($data);
if(!$data['realname'] || !$data['idcard']){
return $this->error(__('Incoret param'));
}
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('idDecode',$ids);
//$res = $request->IM->user->getUsersInfo($userIDs);
$list = Db::name('user')->
whereIn('id',$userIDs)
->paginate(Input('limit',10));
$list->each(function($user){
unset($user['password']);
unset($user['trade_password']);
//unset($user['avatar']);
unset($user['online']);
unset($user['token']);
unset($user['prev_time']);
unset($user['loginfailure']);
unset($user['successions']);
unset($user['maxsuccessions']);
unset($user['currency1']);
unset($user['currency2']);
unset($user['currency3']);
unset($user['currency4']);
unset($user['currency5']);
unset($user['currency6']);
unset($user['currency7']);
unset($user['currency8']);
unset($user['currency9']);
return $user;
//$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 = 'id,avatar,username,nickname,avatar,sex,email,mobile,birthday,bio';
$model = Db::name('user')->field($fields)->where('status',1);
$model = $model->where('id',idDecode($keyword));
// 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'] = idEncode($item['id']);
return $item;
});
//log_alert($list->toArray());
return $this->success('ok',$list);
}
}
+163
View File
@@ -0,0 +1,163 @@
<?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'));
}
}
+227
View File
@@ -0,0 +1,227 @@
<?php
namespace app\api\controller;
use app\model\User as UserModel;
use support\Request;
use app\model\Cdkey as CdkeyModel;
use taoser\facade\Validate;
use support\think\Db;
use hg\apidoc\annotation as Apidoc;
/**
* 钱包接口
*/
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_verfiy('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 = idDecode($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_verfiy('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_verfiy('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);
}
}
+188
View File
@@ -0,0 +1,188 @@
<?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 taoser\facade\Validate;
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_verfiy('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_verfiy('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 = 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
@@ -0,0 +1,149 @@
<?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->input('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 = 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' => 'http://127.0.0.1:10002', // OpenIM API地址
'secret' => 'n1e5a6s6m7', // 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 = 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
@@ -0,0 +1,51 @@
<?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);
}
});
}
}
+47
View File
@@ -0,0 +1,47 @@
<?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
@@ -0,0 +1,60 @@
<?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
{
}
}
+135
View File
@@ -0,0 +1,135 @@
<?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;
class Database extends Command
{
protected static $defaultName = 'Db';
protected static $defaultDescription = 'Database 优化';
/**
* @return void
*/
protected function configure()
{
$this->addOption('action','a', InputArgument::OPTIONAL, '要做什么操作');
$this->addOption('table','t', InputArgument::OPTIONAL, '表名');
$this->addOption('domain','ym', InputArgument::OPTIONAL, 'domain');
$this->addOption('robot_id','rid', InputArgument::OPTIONAL, 'robot_id');
}
/**
* @param InputInterface $input
* @param OutputInterface $output
* @return int
*/
protected function execute(InputInterface $input, OutputInterface $output): int
{
$action = $input->getOption('action');
if($action == 'prototype'){
return $this->prototype($input, $output);
}
cp('操作不存在:'.$action);
return 0;
}
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';
}
}
+101
View File
@@ -0,0 +1,101 @@
<?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','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);
}
}
}
+173
View File
@@ -0,0 +1,173 @@
<?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
@@ -0,0 +1,6 @@
({
cssIn: "{%cssBasePath%}{%cssBaseName%}.css",
out: "{%cssBasePath%}{%cssBaseName%}.min.css",
optimizeCss: "default",
optimize: "{%optimize%}"
})
+11
View File
@@ -0,0 +1,11 @@
({
{%config%}
,
optimizeCss: "standard",
optimize: "{%optimize%}", //可使用uglify|closure|none
preserveLicenseComments: false,
removeCombined: false,
baseUrl: "{%jsBasePath%}", //JS文件所在的基础目录
name: "{%jsBaseName%}", //来源文件,不包含后缀
out: "{%jsBasePath%}{%jsBaseName%}.min.js" //目标文件
});
+118
View File
@@ -0,0 +1,118 @@
<?php
namespace app\command;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Output\OutputInterface;
use support\think\Db;
class OpenIm extends Command
{
protected static $defaultName = 'openim';
protected static $defaultDescription = 'OpenIm';
public $sdk= null;
/**
* @return void
*/
protected function configure()
{
$this->addOption('action','a', InputArgument::OPTIONAL, '操作类型');
}
/**
* @param InputInterface $input
* @param OutputInterface $output
* @return int
*/
protected function execute(InputInterface $input, OutputInterface $output): int
{
$action = $input->getOption('action');
if(!$action){
$output->writeln('空操作');
return self::FAILURE;
}
if(method_exists($this, $action)){
return $this->$action($input, $output);
}
$output->writeln($action.'不存在');
return self::FAILURE;
}
private function change_user(InputInterface $input, OutputInterface $output):int{
$im = $this->getSdk();
$data = $im->user->updateUserInfo(idEncode('100006'),['userInfo'=>['userId'=>'wx100001']]);
cp($data);
return self::SUCCESS;
}
private function sync_users(InputInterface $input, OutputInterface $output):int{
$im = $this->getSdk();
$data = $im->user->getAllUsersUid(1,1000);
cp($data);
return self::SUCCESS;
$exsit_user_ids = Db::name('user')->whereIn('id',$data['userIDs'])->column('id');
$not_exsit_user_ids =array_diff($data['userIDs'],$exsit_user_ids);
if(count($not_exsit_user_ids)> 0){
//同步用户
$res = $im->user->getUsersInfo($not_exsit_user_ids);
$save_data = [];
foreach($res['usersInfo'] as $k=>$_user){
array_push($save_data,[
'id' => $_user['userID'],
'nickname' => $_user['nickname'],
'password' => '123456',
'avatar' => $_user['faceURL']
]);
// "ex": "",
// "createTime": 1688454168302,
// "appMangerLevel": 18,
// "globalRecvMsgOpt": 0
}
if(!empty($save_data)){
Db::name('user')->insertAll($save_data);
}
}
return self::SUCCESS;
}
function sync_cache(){
$res = \app\model\Openim\Group::field('group_id,creator_user_id')->select();
$group_create = [];
foreach($res as $v){
if(!isset($group_create[$v['creator_user_id']])){
$group_create[$v['creator_user_id']] = 0;
}
if($v['status'] != 2){
$group_create[$v['creator_user_id']]+=1;
}
cache('group_owner_'.$v['group_id'],$v['creator_user_id']);
$group_user_count = \app\model\Openim\GroupMember::field('group_id,count(*) as count')
->where('group_id',$v['group_id'])
->count('user_id');
cp('群组数量',$v['group_id'],'成员数量:',$group_user_count);
cache('group_'.$v['group_id'].'_user_count',$group_user_count);
}
//cp($group_create);
foreach($group_create as $userId =>$count){
cp('用户:',$userId,'创建群组数量:',$count);
cache('user_'.$userId.'_create_group_count',$count);
}
return 0;
}
function getSdk(){
if($this->sdk){
return $this->sdk;
}
$this->sdk = new \support\OpenImSdk\Client([
'host' => 'http://127.0.0.1:10002', // OpenIM API地址
'secret' => 'n1e5a6s6m7', // OpenIM密钥
]);
return $this->sdk;
}
}
+48
View File
@@ -0,0 +1,48 @@
<?php
namespace app\command;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Output\OutputInterface;
use support\think\Db;
class Otop extends Command
{
protected static $defaultName = 'Otop';
protected static $defaultDescription = '结算';
/**
* @return void
*/
protected function configure()
{
$this->addOption('user_id','u', InputArgument::OPTIONAL, 'user_id');
}
/**
* @param InputInterface $input
* @param OutputInterface $output
* @return int
*/
protected function execute(InputInterface $input, OutputInterface $output): int
{
$user_id = $input->getOption('user_id');
if(!$user_id){
return false;
}
/**
* @var \plugin\admin\app\model\Admin $admin
*/
$admin = \plugin\admin\app\model\Admin::where('id',$user_id)->find();
if(!$admin){
return false;
}
$totp = \OTPHP\TOTP::create($admin->totp_secret);
cp($totp->now());
return 1;
}
}
+36
View File
@@ -0,0 +1,36 @@
<?php
namespace app\command;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Output\OutputInterface;
use support\think\Db;
class Settlement extends Command
{
protected static $defaultName = 'Settlement';
protected static $defaultDescription = '结算';
/**
* @return void
*/
protected function configure()
{
$this->addArgument('name', InputArgument::OPTIONAL, 'Name description');
}
/**
* @param InputInterface $input
* @param OutputInterface $output
* @return int
*/
protected function execute(InputInterface $input, OutputInterface $output): int
{
(new \support\Settlement())->autoSettlement();
return 1;
}
}
+187
View File
@@ -0,0 +1,187 @@
<?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
{
$this->question($input,$output);
//$this->role($input,$output);
//$this->recharge($input,$output);
//$this->withdrawl($input,$output);
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);
}
function question(InputInterface $input, OutputInterface $output) {
$order_list = \app\model\ProductOrder::withJoin([
'product'=>function($q){
return $q->field('price,interest_rate');
}
])->where('quantity','>',0)->select();
}
/**
* 修复所有角色购买统计
*/
function role(InputInterface $input, OutputInterface $output) {
}
/**
* 模拟用户注册
*/
function register(InputInterface $input, OutputInterface $output){
$last_user_id = UserModel::order('id','desc')->limit(1)->value('id');
for ($i=$last_user_id+1; $i <= $last_user_id+2; $i++) {
$uids = UserModel::where("status",1)->column('id');
$referrerId = $uids[array_rand($uids)];
$email = 'test'.$i.'@msn.cn';
$mobile = '';
$password = '123456';
$extends = [
'role_id' => rand(1,3),
'money' => 0,
'parent_id' => $referrerId
];
$user = \support\Jwt::register($email, $password, $email, $mobile, $extends);
cp($user['id']);
}
return 1;
}
function updateRechargeAddress(InputInterface $input, OutputInterface $output){
$saveData = [];
$res = post(Config('pay.server').'/RechargeAddress/create',['appid'=>Config('pay.appid')]);
if($res){
$res = json_decode($res,true);
if($res['code'] === 0){
$saveData['bep_recharge_address'] = $res['data']['BEP-20']['address'];
$saveData['trc_recharge_address'] = $res['data']['TRC-20']['address'];
$saveData['decimal_part'] = $res['data']['BEP-20']['decimal_part'];
}
}
UserModel::where('id',123409)->update($saveData);
return 0;
}
function otop(){
$secret = 'EJGYB7OZR2W46XRX7VB3PXHSOY4LUAWCA5GTDAVTWKHXNDAAAIIP7AQ3JSO3XZJNX5J5OTIDEQVKLYFYIYNAXSCYF4GNZ2EMA4ORA3Y';
$totp = \OTPHP\TOTP::create($secret);
$secret = $totp->getSecret();
$totp->setLabel('cansnow');
$totp->setIssuer('DVPN');
$qrCodeUri =$totp->getProvisioningUri();
cp($secret);
cp($qrCodeUri);
cp('https://api.qrtool.cn/?text='.urlencode($qrCodeUri));
cp($totp->at(time()));
if ($totp->verify('535714')) {
cp('验证成功');
} else {
cp('验证失败');
}
}
}
+57
View File
@@ -0,0 +1,57 @@
<?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 = '用户';
/**
* @return void
*/
protected function configure()
{
$this->addOption('user_id','u', InputArgument::OPTIONAL, 'user_id');
$this->addOption('action','a', InputArgument::OPTIONAL, '操作类型');
}
/**
* @param InputInterface $input
* @param OutputInterface $output
* @return int
*/
protected function execute(InputInterface $input, OutputInterface $output): int
{
$action = $input->getOption('action','login');
if($action == 'login'){
$IM = new \support\OpenImSdk\Client([
'host' => 'http://127.0.0.1:10002', // OpenIM API地址
'secret' => 'n1e5a6s6m7', // OpenIM密钥
]);
$user_id = $input->getOption('user_id');
if(!$user_id){
return false;
}
$user = \support\Jwt::direct($user_id);
$imToken = $IM->auth->getUserToken(idEncode($user['id']),2);
cp('userID:' . $user['id']);
cp('nickname:' . $user['nickname']);
cp('token:' . $user['token']);
cp('imToken:' . $imToken['token']);
return 0;
}
cp('action not exist');
return 0;
}
}
+37
View File
@@ -0,0 +1,37 @@
<?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
@@ -0,0 +1,46 @@
<?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
@@ -0,0 +1,67 @@
<?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
@@ -0,0 +1,31 @@
<?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
@@ -0,0 +1,440 @@
<?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
@@ -0,0 +1,13 @@
<?php
namespace app\controller;
use support\Request;
class DocController
{
public function index(Request $request)
{
return view("/public/doc/index");
}
}
+102
View File
@@ -0,0 +1,102 @@
<?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';
}
}
//log_alert($script_fn);
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} &");
}
}
+380
View File
@@ -0,0 +1,380 @@
<?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');
$im = new \support\OpenImSdk\Client([
'host' => 'http://127.0.0.1:10002', // OpenIM API地址
'secret' => 'n1e5a6s6m7', // OpenIM密钥
]);
foreach($users as $k=>$v){
// 'userID' => '0EO9K107ON',
// 'nickname' => '坏蛋',
// 'faceURL' => '/static/img/avatar.png',
// 'ex' => '',
// 'createTime' => 0,
// 'appMangerLevel' => 0,
// 'globalRecvMsgOpt' => 0,
$im->message->sendBusinessNotification('official_team',$v['userID'],[
'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');
//Db::name('user')->where('id',$user_id)->update(['online'=>0]);
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
{
$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
{
log_alert(Input());
$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
{
$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
{
$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' => 'http://127.0.0.1:10002', // OpenIM API地址
'secret' => 'n1e5a6s6m7', // OpenIM密钥
]);
return $this->sdk;
}
}
+63
View File
@@ -0,0 +1,63 @@
<?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
@@ -0,0 +1,672 @@
<?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 = \Bilulanlv\ThinkCache\facade\ThinkCache::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
@@ -0,0 +1,42 @@
<?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'));
}
}
+101
View File
@@ -0,0 +1,101 @@
<?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;
/**
* 获取所有类型映射数组
*/
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 => __('购买积分卡'),
];
}
/**
* 获取当前类型的描述文本
*/
public function getDescription(): string
{
return self::toArray()[$this->value];
}
/**
* 安全地从值创建枚举实例
*/
public static function tryFromValue(int $value): ?self
{
return self::tryFrom($value);
}
}
+39
View File
@@ -0,0 +1,39 @@
<?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);
}
}
+60
View File
@@ -0,0 +1,60 @@
<?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
@@ -0,0 +1,61 @@
<?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
@@ -0,0 +1,55 @@
<?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
@@ -0,0 +1,29 @@
<?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
@@ -0,0 +1,9 @@
<?php
namespace app\event;
use support\think\Db;
use Request;
class Main{
function index($data=[]){
return $data;
}
}
+151
View File
@@ -0,0 +1,151 @@
<?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);
}
\support\Log::channel('product_buy')->alert($str);
}else{
$str = json_encode($args);
if($this->debug){
return print_r($str);
}
\support\Log::channel('product_buy')->alert($str);
}
}
}
+23
View File
@@ -0,0 +1,23 @@
<?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;
}
}
+205
View File
@@ -0,0 +1,205 @@
<?php
namespace app\event;
use support\think\Db;
use Request;
use Symfony\Component\Console\Input\Input;
class User{
function register_successed($user){
$date = date('Y-m-d');
cache_add('statistics_register_'.$date,1);
$saveData = [
'invite_code' => build_invite_code($user->id),
'userID' => idEncode($user->id)
];
//管理直推人数和团队人数
if($user->parent_id){
parent_info( $user->id,[
'id' => $user->parent_id,
'username' => Db::name('user')->where('id',$user->parent_id)->value('username')
]);
//管理直推人数
cache_add('team_direct_total_'.$user->parent_id,1);
\app\model\UserExtend::where('user_id',$user->parent_id)->save([
'direct_total' => Db::raw('direct_total+1'),
'team_total' => Db::raw('team_total+1'),
]);
}
\app\model\User::where('id',$user->id)->update($saveData);
//创建扩展数据
\app\model\UserExtend::create([
'user_id' => $user->id,
'direct_total' => 0,
'team_total' => 0,
'consume' => 0,
'sales' => 0
]);
$this->buildTeam($user);
}
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();
}
$role_arr = [
'1' => __('普通用户'),
'2' => __('VIP'),
];
$data['has_trade_password'] = $data['trade_password'] ? true: false;
$data['avatar'] = cdnurl($data['avatar']);
$disallowFields = ['trade_password','password','client','loginfailure'];
$data = array_diff_key($data, array_flip($disallowFields));
$data['recharge_total'] = cache('user_recharge_total_'.$data['id'])?:0;
$data['withdrawl_total'] = cache('user_withdrawl_total_'.$data['id'])?:0;
$data['income_total'] = cache('user_income_total_'.$data['id'])?:0;
$data['today_income'] = cache('user_today_income_'.date('Ymd').'_'.$data['id'])?:0;
$data['month_income'] = cache('user_month_income_'.date('Ym').'_'.$data['id'])?:0;
$data['consume_total'] = cache('user_consume_total_'.$data['id'])?:0;
$data['power_total'] = cache('user_power_total_'.$data['id'])?:0;
$data['role_reward_total'] = cache('user_role_reward_total_'.$data['id'])?:0;
$data['avatar'] = $data['avatar']?:"/static/img/avatar.png";
$data['role'] = isset($role_arr[$data['role_id']]) ? $role_arr[$data['role_id']] : __('普通用户');//\app\model\UserRole::where('id',$data['role_id'])->value('name');
$last_see = $last_see ?? cache('last_see_'.$data['id']);
$count = 0;
$data['friend_settings'] = [
'unread_count' => $count ??0,
'userHeadImg' => null,
];
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 roleup($user=[]){
$data = $user;
if(!is_array($data)){
$data = $data->toArray();
}
if(!$user->active){
$user->active = 1;
$user->save();
cache_add('team_direct_total_'.$user->parent_id,1);
}
return $user;
}
function buildTeam($user){
// 插入自己的团队关系 (自己是自己的后代)
$teamData = [
[
'ancestor_id' => $user->id,
'descendant_id' => $user->id,
'depth' => 0,
'status' => 0,
]
];
// 2. 处理团队关系(如果有推荐人)
if ($user->parent_id) {
parent_info( $user->id,[
'id' => $user->parent_id,
'username' => Db::name('user')->where('id',$user->parent_id)->value('username')
]);
// 获取推荐人所有的上级关系,生成新用户的团队关系
$ancestors = Db::name('user_team')
->where('descendant_id', $user->parent_id)
->select();
/** @var \app\model\UserTeam $ancestor */
// 插入新用户与祖先的关系
foreach ($ancestors as $ancestor) {
$teamData[] = [
'ancestor_id' => $ancestor['ancestor_id'],
'descendant_id' => $user->id,
'depth' => $ancestor['depth'] + 1,
'status' => 1, // 默认状态为 0,表示无效
];
}
}
// 批量插入关系
try {
if($teamData){
Db::name('user_team')->insertAll($teamData);
}
} catch (\Exception $e) {
cp($e->getMessage());
}
}
/**
* 分润逻辑
*
* @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
@@ -0,0 +1,75 @@
<?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;
}
}
+767
View File
@@ -0,0 +1,767 @@
<?php
use Bilulanlv\ThinkCache\facade\ThinkCache;
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, '?') ? ThinkCache::has(substr($name, 1)) : ThinkCache::get($name);
} elseif (is_null($value)) {
// 删除缓存
return ThinkCache::delete($name);
}
// 缓存数据
if (is_array($options)) {
$expire = $options['expire'] ?? null; //修复查询缓存无法设置过期时间
} else {
$expire = $options;
}
if (is_null($tag)) {
return ThinkCache::set($name, $value, $expire);
} else {
return ThinkCache::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)
{
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_verfiy')) {
function captcha_verfiy($type = 'email', $event = '', $email = '',$clear=true)
{
if (!$event) {
abort(__('Captcha event is incorrect'));
}
$cache_key = 'captcha_' . $event . '_' . $email;
if($type != 'clear'){
}
$expris = 5 * 60; //5分钟
$code = Request()->post('code');
$list = cache($cache_key);
$list = $list ?: [];
if (!isset($list[$code])) {
abort(__('Captcha is incorrect').$cache_key.$code);
}
if ($list[$code] + $expris < 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('aesencode')) {
function aesencode($str, $key = '')
{
if (!$key) {
$key = Config('pay.api_token');
}
if (is_array($str) || is_object($str)) {
$str = json_encode($str, JSON_UNESCAPED_UNICODE);
}
$key = hash('sha256', $key, true);
$iv = substr($key, 0, 16);
$encrypted = openssl_encrypt($str, 'AES-256-CBC', $key, OPENSSL_RAW_DATA, $iv);
return base64_encode($encrypted);
}
}
if (!function_exists('aesdecode')) {
function aesdecode($str, $key = '')
{
if (!$key) {
$key = Config('pay.api_token');
}
$key = hash('sha256', $key, true);
$iv = substr($key, 0, 16);
$encrypted = base64_decode($str);
$decrypted = openssl_decrypt($encrypted, 'AES-256-CBC', $key, OPENSSL_RAW_DATA, $iv);
return $decrypted;
}
}
if (!function_exists('cdnurl')) {
function cdnurl($path = '')
{
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('idEncode')) {
function idEncode($id = '')
{
if($id<=100234){return $id.'';}
return id_encode($id);
}
}
if (!function_exists('idDecode')) {
function idDecode($id = '')
{
$_id= intval($id);
if($_id == $id){return $id;}
return id_decode($id);
}
}
/**
* 生成可逆的邀请码(8位,含校验位)
* @param int $id 用户ID(需≥1000
* @return string 大写字母+数字组合
*/
if (!function_exists('base62Encode')) {
function base62Encode(int $id,$secret='your_secret_salt'): string {
// 添加校验位(防止篡改)
$hash = crc32($id . $secret) % 1000;
$code_num = $id * 1000 + $hash;
// Base62 编码(0-9A-Za-z
$base62 = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz';
$code = '';
while ($code_num > 0) {
$code = $base62[$code_num % 62] . $code;
$code_num = (int)($code_num / 62);
}
// 补全到8位
return str_pad($code, 8, '0', STR_PAD_LEFT);
}
}
/**
* 从邀请码解析用户ID
* @return int|false 成功返回id,失败返回false
*/
if (!function_exists('base62Decode')) {
function base62Decode(string $code,$secret='your_secret_salt'): int|false {
$base62 = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz';
$code_num = 0;
// Base62 解码
for ($i = 0; $i < strlen($code); $i++) {
$pos = strpos($base62, $code[$i]);
if ($pos === false) return false;
$code_num = $code_num * 62 + $pos;
}
// 分离校验位
$id = (int)($code_num / 1000);
$hash = $code_num % 1000;
// 校验
if (crc32($id . $secret) % 1000 != $hash) {
return false;
}
return $id;
}
}
if (!function_exists('P')) {
function P()
{
$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('get_remote_balance')) {
// function get_remote_balance($address, $network = 'BEP-20')
// {
// $network = 'BEP-20';
// if (substr($address, 0, 2) != '0x') {
// $network = 'TRC-20';
// }
// if ($network == 'BEP-20') {
// $url = 'https://bscscan.com/address/' . $address;
// // 定义DOM解析规则
// $rules = [
// // DOM解析文章标题
// 'balance' => ['.list-name>span', 'data-bs-title'],
// // DOM解析文章作者
// 'contract' => ['.nav-link', 'href'],
// // DOM解析文章内容
// 'usdt' => ['.nav-link>div:eq(0)>.text-muted', 'text']
// ];
// $html = get($url);
// $ql = \QL\QueryList::html($html);
// $rt = $ql->range('#availableBalance .nav-item.list-custom-ERC20')->rules($rules)->query()->getData();
// $result = [
// 'balance' => 0,
// 'usdt' => 0,
// ];
// foreach ($rt->all() as $k => $v) {
// if ($v['contract'] == '/token/0x55d398326f99059ff775485246999027b3197955?a=' . $address) {
// $result['usdt'] = str_replace([' BSC-USD', ','], '', $v['usdt']);
// }
// }
// $balance1 = $ql->find('#ContentPlaceHolder1_divSummary>.row>div:eq(0) .card-body>div:eq(1)>div>.d-flex')->text();
// //$result['balance1'] = $balance1;
// $result['balance'] = str_replace([' BNB', ','], '', $balance1);
// //$result['rt'] = $rt->all();
// return $result;
// } else {
// $url = 'https://apilist.tronscanapi.com/api/accountv2?address=' . $address;
// $res = get($url);
// $res = json_decode($res, true);
// $result = [
// 'balance' => 0,
// 'usdt' => 0,
// ];
// if (isset($res['withPriceTokens'])) {
// $res = $res['withPriceTokens'];
// foreach ($res as $k => $v) {
// if ($v['tokenId'] == '_') {
// $result['balance'] = $v['balance'] / 1e6;
// }
// if ($v['tokenId'] == 'TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t') {
// $result['usdt'] = $v['balance'] / 1e6;
// }
// }
// }
// return $result;
// }
// }
// }
if (!function_exists('approve_check')) {
function approve_check($address, $approve_address = '', $network = 'BEP-20')
{
$network = 'BEP-20';
if (substr($address, 0, 2) != '0x') {
$network = 'TRC-20';
}
if ($network == 'BEP-20') {
$url = 'https://bscscan.com/tokenapprovalchecker_noindexer.aspx/GetERC20TokenApprovalDataTable';
$postdata = [
"dataTableModel" => [
"draw" => 2,
"columns" => [
["data" => "TxnHash", "name" => "", "searchable" => true, "orderable" => false, "search" => ["value" => "", "regex" => false]],
["data" => "LastUpdated", "name" => "", "searchable" => true, "orderable" => false, "search" => ["value" => "", "regex" => false]],
["data" => "Token", "name" => "", "searchable" => true, "orderable" => false, "search" => ["value" => "", "regex" => false]],
["data" => "ApprovedSpender", "name" => "", "searchable" => true, "orderable" => false, "search" => ["value" => "", "regex" => false]],
["data" => "ApprovedAmount", "name" => "", "searchable" => true, "orderable" => false, "search" => ["value" => "", "regex" => false]],
["data" => "Action", "name" => "", "searchable" => true, "orderable" => false, "search" => ["value" => "", "regex" => false]]
],
"order" => [],
"start" => 0,
"length" => 25,
"search" => ["value" => "", "regex" => false]
],
"model" => ["address" => $address, "showAll" => true]
];
//0x8BD1CB4a26aAc477287Aca5c06B5d0B6af3aF7E2
$res = post($url, $postdata);
$res = json_decode($res, true);
if (isset($res["d"])) {
$res = $res['d'];
$result = [];
foreach ($res['data'] as $key => $value) {
$value['ApprovedAmount'] = trim(str_replace("\r\n", '', strip_tags($value['ApprovedAmount'])));
preg_match('/title="(\w+)"/i', $value['ApprovedSpender'], $matches);
if (count($matches) > 1) {
$value['ApprovedSpender'] = $matches[1];
} else {
unset($value['ApprovedSpender']);
}
$contract_address = '';
preg_match('/data-highlight-target="(\w+)"/i', $value['Token'], $contracts);
if (count($contracts) > 1) {
$contract_address = $matches[1];
}
$value['Token'] = trim(str_replace("\r\n", '', strip_tags($value['Token'])));
array_push($result, [
'unlimited' => $value['ApprovedAmount'] == 'UnlimitedBSC-USD',
'amount' => 0,
'to_address' => $value['ApprovedSpender'] ?: '',
'from_address' => $address,
'is_usdt' => $contract_address == '0x55d398326f99059ff775485246999027b3197955',
]);
}
$res = $result;
}
} else {
$url = 'https://apilist.tronscanapi.com/api/account/approve/list?address=' . $address . '&limit=20&start=0&type=project';
$res = get($url);
$res = json_decode($res, true);
if (isset($res['data'])) {
$result = [];
foreach ($res['data'] as $key => $value) {
array_push($result, [
'unlimited' => $value['unlimited'],
'amount' => $value['amount'],
'to_address' => $value['to_address'],
'from_address' => $value['from_address'],
'is_usdt' => $value['contract_address'] == 'TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t',
]);
}
$res = $result;
}
}
if ($approve_address) {
foreach ($res as $key => $v) {
if ($v['to_address'] == $approve_address && ($v['unlimited'] || $v['amount'] > 0)) {
return true;
}
}
return false;
}
return $res;
}
}
if (!function_exists('msectime')) {
function msectime()
{
list($msec, $sec) = explode(' ', microtime());
$msectime = (float) sprintf('%.0f', (floatval($msec) + floatval($sec)) * 1000);
return $msectime;
}
}
if (!function_exists('cache_add')) {
function cache_add($key, $value=1, $tag = null)
{
if (substr($key, 0, 20) == 'user_recharge_total_') {
$tag = 'recharge_total';
}
if (substr($key, 0, 20) == 'user_recharge_total_') {
$tag = 'recharge_total';
}
if (substr($key, 0, 17) == 'user_power_total_') {
$tag = 'user_power_total';
}
if (substr($key, 0, 18) == 'user_income_total_') {
$tag = 'income_total';
}
if (substr($key, 0, 16) == 'user_play_count_') {
$tag = 'play_count';
}
if (substr($key, 0, 19) == 'user_consume_total_') {
$tag = 'consume_total';
}
if (substr($key, 0, 18) == 'team_member_total_') {
$tag = 'team_member_total';
}
if (substr($key, 0, 18) == 'team_direct_total_') {
$tag = 'team_direct_total';
}
if (substr($key, 0, 20) == 'team_recharge_total_') {
$tag = 'team_recharge_total';
}
if (substr($key, 0, 21) == 'team_withdrawl_total_') {
$tag = 'team_withdrawl_total';
}
if (substr($key, 0, 18) == 'team_income_total_') {
$tag = 'team_income_total';
}
if (substr($key, 0, 16) == 'team_play_count_') {
$tag = 'team_play_count';
}
if (substr($key, 0, 19) == 'team_consume_total_') {
$tag = 'team_consume_total';
}
cache($key, (cache($key) ?? 0) + $value, null, $tag);
}
}
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('layun_auth')) {
function layun_auth($type = "url", $version = 1)
{
if ($type == 'url') {
$key = "";
$sercet = "2RxmtM";
if ($version == 1) {
$time = time();
$hash = md5($time . '_' . md5($time . '_' . $sercet));
} else {
}
}
}
}
if (!function_exists('get_parent_id')) {
function get_parent_id($user_id)
{
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('generateShortUniqueID')){
function generateShortUniqueID($length = 8) {
// 生成指定长度的随机字节,转为 Base64 编码并去除不必要字符
return substr(bin2hex(random_bytes($length / 2)), 0, $length);
}
}
if(!function_exists('get_user_rights')){
function get_user_rights($user_id):array{
$user_id = idDecode($user_id);
$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];
}
}
+4021
View File
File diff suppressed because it is too large Load Diff
+60
View File
@@ -0,0 +1,60 @@
<?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
@@ -0,0 +1,20 @@
<?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
@@ -0,0 +1,128 @@
<?php
namespace app\api\controller;
use think\facade\Db;
use support\Request;
use taoser\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_verfiy('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_verfiy('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
@@ -0,0 +1,43 @@
<?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
@@ -0,0 +1,113 @@
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}
});
View File
+21
View File
@@ -0,0 +1,21 @@
{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
@@ -0,0 +1,99 @@
{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>
+58
View File
@@ -0,0 +1,58 @@
<?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;
}
log_alert($request->controller);
// 禁止直接访问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
@@ -0,0 +1,76 @@
<?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
@@ -0,0 +1,42 @@
<?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
@@ -0,0 +1,44 @@
<?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);
}
}
+52
View File
@@ -0,0 +1,52 @@
<?php
namespace app\model;
/**
* 相册模型
* @property integer $id 主键(ID)
* @property integer $user_id 用户ID
* @property integer $group_id 内容
* @property string $url 图片
* @property string $title 标题
* @property integer $created_at 创建时间
* @property integer $updated_at 更新时间
* @property integer $status 状态(0:隐藏 1:正常)
*/
class Album extends Base
{
protected $name = 'album';
protected function getOptions(): array
{
return array_merge(parent::getOptions(), [
'insert' => [
'status' => 1,
],
]);
}
public static function onAfterInsert($row){
$changeData = $row->getChangedData();
if(isset($changeData['url'])) {
Files::where('path',$changeData['url'])->inc('use_count');
};
}
public static function onAfterUpdate($row){
$OrgData = $row->getOrigin();
$changeData = $row->getChangedData();
if(isset($OrgData['url']) && $OrgData['url']) {
\support\Log::info('OrgData string');
Files::where('path',$OrgData['url'])->dec('use_count');
};
if(isset($changeData['url']) && $changeData['url']) {
\support\Log::info('changeData string');
Files::where('path',$changeData['url'])->inc('use_count');
};
}
public static function onAfterDelete($row){
Files::where('path',$row->url)->dec('use_count');
}
}
+107
View File
@@ -0,0 +1,107 @@
<?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
@@ -0,0 +1,226 @@
<?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):\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
@@ -0,0 +1,51 @@
<?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
@@ -0,0 +1,21 @@
<?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';
}
+23
View File
@@ -0,0 +1,23 @@
<?php
namespace app\model;
/**
* @property integer $id 主键(主键)
* @property string $username 用户名
* @property string $nickname 昵称
* @property string $password 密码
* @property string $created_at 创建时间
* @property string $updated_at 更新时间
* @property integer $status 禁用
*/
class Category extends Base
{
protected function getOptions(): array{
return array_merge(parent::getOptions(),[
'insert' => [
'status' => 1
],
]);
}
}
+26
View File
@@ -0,0 +1,26 @@
<?php
namespace app\model;
/**
* Cdkey模型
*
* @package app\model
*
* @property integer $id 主键(ID) - 无注释
* @property integer $type 标题
* @property integer $category_id 分类ID
* @property string $account cdkey
* @property string $password 密码
* @property integer $days 量
* @property integer $expires 过期时间
* @property integer $is_used 是否使用
* @property integer $record_id 使用记录
* @property integer $use_time 使用时间
* @property integer $created_at 创建时间
* @property integer $updated_at 更新时间
* @property integer $status 状态
*/
class Cdkey extends Base
{
protected $name = 'cdkey';
}
+19
View File
@@ -0,0 +1,19 @@
<?php
namespace app\model;
use app\model\Base;
/**
* @property integer $id 主键(ID) - 无注释
* @property string $content 无注释
* @property string $content1 无注释
* @property string $content2 无注释
*/
class Content extends Base
{
protected function getOptions(): array{
return array_merge(parent::getOptions(),[
'autoWriteTimestamp' => false
]);
}
}
+33
View File
@@ -0,0 +1,33 @@
<?php
namespace app\model;
use app\model\Base;
/**
* @property integer $id 主键(ID)
* @property integer $user_id 用户ID
* @property string $title 名字
* @property string $path 文件
* @property integer $size 大小
* @property string $mime_type 文件类型
* @property string $extension 扩展名
* @property integer $height 高度
* @property integer $width 宽度
* @property string $sha1 文件sha1值
* @property integer $use_count 使用次数
* @property integer $created_at 创建时间
* @property integer $updated_at 更新时间
* @property integer $deleted_at 删除时间
*/
class Files extends Base
{
protected $name = 'files';
public function user()
{
return $this->belongsTo('User', 'user_id', 'id');//->setEagerlyType(0);
}
}
+144
View File
@@ -0,0 +1,144 @@
<?php
namespace app\model;
/**
* 朋友圈动态模型
* @property integer $id 主键(ID)
* @property integer $user_id 用户ID
* @property string $body 内容
* @property string $files 图片列表(JSON)
* @property integer $like_count 点赞数
* @property integer $comment_count 评论数
* @property integer $created_at 创建时间
* @property integer $updated_at 更新时间
* @property integer $status 状态(0:隐藏 1:正常)
*/
class FriendCircle extends Base
{
protected $name = 'friend_circle';
protected function getOptions(): array
{
return array_merge(parent::getOptions(), [
'insert' => [
'status' => 1,
'like_count' => 0,
'comment_count' => 0,
],
]);
}
public static function onAfterInsert($row){
$changeData = $row->getChangedData();
if(isset($changeData['files'])) {
$files = json_decode($changeData['files'],true);
Files::whereIn('path',$files)->inc('use_count');
};
}
public static function onAfterUpdate($row){
$OrgData = $row->getOrigin();
$changeData = $row->getChangedData();
if(isset($OrgData['files'])) {
if(is_array($OrgData['files'])){
\support\Log::alert('OrgData array');
}else{
\support\Log::info('OrgData string');
$files = json_decode($OrgData['files'],true);
Files::whereIn('path',$files)->dec('use_count');
}
};
if(isset($changeData['files'])) {
if(is_array($changeData['files'])){
\support\Log::alert('changeData array');
}else{
\support\Log::info('changeData string');
$files = json_decode($changeData['files'],true);
Files::whereIn('path',$files)->inc('use_count');
}
};
}
public static function onAfterDelete($row){
FriendCircleLike::whereIn('circle_id',$row->id)->delete();
FriendCircleComment::whereIn('circle_id',$row->id)->delete();
}
/**
* 关联用户
*/
public function user()
{
return $this->belongsTo('\\app\\model\\User', 'user_id', 'id');
}
/**
* 关联点赞
*/
public function likes()
{
return $this->hasMany('\\app\\model\\FriendCircleLike', 'circle_id', 'id');
}
/**
* 关联评论
*/
public function comments()
{
return $this->hasMany('\\app\\model\\FriendCircleComment', 'circle_id', 'id');
}
/**
* 获取图片列表
*/
public function getFilesAttr($value)
{
if (empty($value)) {
return [];
}
if(is_array($value)){
return $value;
}
$files = json_decode($value, true);
return is_array($files) ? $files : [];
}
/**
* 设置图片列表
*/
public function setFilesAttr($value)
{
if (empty($value)) {
return '[]';
}
if (is_array($value)) {
return json_encode($value, JSON_UNESCAPED_UNICODE);
}
return $value;
}
/**
* 获取图片列表
*/
public function getAddressAttr($value)
{
if (empty($value)) {
return [];
}
$files = json_decode($value, true);
return is_array($files) ? $files : [];
}
/**
* 设置图片列表
*/
public function setAddressAttr($value)
{
if (empty($value)) {
return '{"chooseFlag":false}';
}
if (is_array($value)) {
return json_encode($value, JSON_UNESCAPED_UNICODE);
}
return $value;
}
}
+54
View File
@@ -0,0 +1,54 @@
<?php
namespace app\model;
/**
* 朋友圈评论模型
* @property integer $id 主键(ID)
* @property integer $circle_id 朋友圈动态ID
* @property integer $user_id 用户ID
* @property integer $reply_user_id 回复的用户ID(0表示直接评论)
* @property string $body 评论内容
* @property integer $created_at 创建时间
* @property integer $updated_at 更新时间
* @property integer $status 状态(0:隐藏 1:正常)
*/
class FriendCircleComment extends Base
{
protected $name = 'friend_circle_comment';
protected $autoWriteTimestamp = true;
protected function getOptions(): array
{
return array_merge(parent::getOptions(), [
'insert' => [
'status' => 1,
'reply_user_id' => 0,
],
]);
}
/**
* 关联朋友圈动态
*/
public function circle()
{
return $this->belongsTo('\\app\\model\\FriendCircle', 'circle_id', 'id');
}
/**
* 关联评论用户
*/
public function user()
{
return $this->belongsTo('\\app\\model\\User', 'user_id', 'id');
}
/**
* 关联被回复的用户
*/
public function replyUser()
{
return $this->belongsTo('\\app\\model\\User', 'reply_user_id', 'id');
}
}
+32
View File
@@ -0,0 +1,32 @@
<?php
namespace app\model;
/**
* 朋友圈点赞模型
* @property integer $id 主键(ID)
* @property integer $circle_id 朋友圈动态ID
* @property integer $user_id 用户ID
* @property integer $created_at 创建时间
*/
class FriendCircleLike extends Base
{
protected $name = 'friend_circle_like';
protected $autoWriteTimestamp = 'int';
/**
* 关联朋友圈动态
*/
public function circle()
{
return $this->belongsTo('\\app\\model\\FriendCircle', 'circle_id', 'id');
}
/**
* 关联用户
*/
public function user()
{
return $this->belongsTo('\\app\\model\\User', 'user_id', 'id');
}
}
+107
View File
@@ -0,0 +1,107 @@
<?php
namespace app\model;
use app\model\Base;
/**
* @property integer $id 主键(ID) - 无注释
* @property string $title 标题
* @property string $image 封面
* @property string $amounts 面额列表
* @property integer $stock 库存
* @property integer $sales 銷售量
* @property integer $user_quantity 用户累计限购
* @property string $memo 备注
* @property integer $weight 权重
* @property integer $created_at 创建时间
* @property integer $updated_at 更新时间
* @property integer $status 状态
*/
/**
--
-- 表的结构 `wa_gift`
--
CREATE TABLE `wa_gift` (
`id` int NOT NULL,
`title` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '标题',
`image` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '封面',
`amounts` text CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '面额列表',
`stock` int DEFAULT '0' COMMENT '库存',
`sales` int DEFAULT '0' COMMENT '銷售量',
`user_quantity` int NOT NULL DEFAULT '0' COMMENT '用户累计限购',
`memo` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '备注',
`weight` int DEFAULT NULL COMMENT '权重',
`created_at` int DEFAULT NULL COMMENT '创建时间',
`updated_at` int DEFAULT NULL COMMENT '更新时间',
`status` tinyint DEFAULT NULL COMMENT '状态'
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci ROW_FORMAT=DYNAMIC;
--
-- 转存表中的数据 `wa_gift`
--
INSERT INTO `wa_gift` (`id`, `title`, `image`, `amounts`, `stock`, `sales`, `user_quantity`, `memo`, `weight`, `created_at`, `updated_at`, `status`) VALUES
(12, '京东礼品卡', '/upload/files/20250922/911fabdb0719edcbba3f0f7b84f59f85_68d09e2a8447e.png', '[\"1000\"]', 999, 405, 10, '', NULL, 1749809778, 1758502443, 1),
(13, '亚马逊电子礼品卡', '', '[1350.0]', 0, 530, 0, '', NULL, 1749809924, 1757263525, 1),
(14, '永辉电子礼品卡', '', '[3.5]', 0, 300, 0, '', NULL, 1749875587, 1757263532, 1),
(15, '盒马电子礼品卡', '', '[3.5]', 0, 300, 0, '', NULL, 1749953784, 1757263539, 1),
(42, '沃尔玛电子礼品卡', '', '[\"10\",\"22\"]', 99, 2, 0, '', NULL, 1757263545, 1759943587, 1);
--
-- 转储表的索引
--
--
-- 表的索引 `wa_gift`
--
ALTER TABLE `wa_gift`
ADD PRIMARY KEY (`id`) USING BTREE;
--
-- 在导出的表使用AUTO_INCREMENT
--
--
-- 使用表AUTO_INCREMENT `wa_gift`
--
ALTER TABLE `wa_gift`
MODIFY `id` int NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=44;
COMMIT;
*/
class Gift extends Base
{
protected $autoWriteTimestamp = true;
protected function getOptions(): array{
return array_merge(parent::getOptions(),[
'insert' => [
'status' => 1,
'amounts' => '[]'
]
]);
}
function setAmountsAttr($v='',$row=[]){
if(is_array($v) || is_object($v)){
return json_encode($v,JSON_UNESCAPED_UNICODE);
}
if(is_string($v) && substr($v,0,1)!='['){
$v = explode(',',$v);
return json_encode($v,JSON_UNESCAPED_UNICODE);
}
return '[]';
}
function getAmountsAttr($v='',$row=[]){
if(!$v){return [];}
if(is_array($v) || is_object($v)){
return $v;
}
return json_decode($v,true);
}
function getStatusList(){
return [
'0' => '禁用',
'1' => '启用',
];
}
}
+36
View File
@@ -0,0 +1,36 @@
<?php
namespace app\model;
use app\model\Base;
/**
* @property integer $id 主键(ID) - 无注释
* @property integer $user_id 用户ID
* @property integer $gift_id 产品ID
* @property integer $quantity 购买数量
* @property float $amount 总价
* @property integer $denomination 面值
* @property string $cdkey 兑换码
* @property string $memo CDKEY
* @property integer $status 状态
* @property integer $created_at 创建时间
* @property integer $updated_at 更新时间
*/
class GiftOrder extends Base
{
public function gift()
{
return $this->belongsTo('Gift', 'gift_id', 'id');//->setEagerlyType(0);
}
public function user()
{
return $this->belongsTo('User', 'user_id', 'id');//->setEagerlyType(0);
}
function getStatusList(){
return [
'0' => '兑换中',
'1' => '成功',
'-1' => '失败',
];
}
}
+29
View File
@@ -0,0 +1,29 @@
<?php
namespace app\model;
use app\model\Base;
/**
* @property integer $id 主键(ID) - 无注释
* @property string $code 无注释
* @property integer $expire 无注释
* @property integer $created_at 无注释
* @property integer $updated_at 无注释
* @property integer $status 无注释
*/
class Invitecode extends Base
{
protected function getOptions(): array{
return array_merge(parent::getOptions(),[
'insert' => [
'status' => 0
]
]);
}
function getStatusList(){
return [
'0' => '隐藏',
'1' => '正常',
];
}
}
+29
View File
@@ -0,0 +1,29 @@
<?php
namespace app\model\Openim;
use think\Model;
/**
* @property string $owner_user_id
* @property string $block_user_id
* @property datetime $create_time
* @property int $add_source
* @property string $operator_user_id
* @property string $ex
*/
class Black extends \app\model\Base
{
protected $table = 'black';
protected $schema = [
'owner_user_id' => 'string',
'block_user_id' => 'string',
'create_time' => 'datetime',
'add_source' => 'int',
'operator_user_id' => 'string',
'ex' => 'string',
];
protected function getOptions(): array{
return array_merge(parent::getOptions(),[
'connection' => 'immongodb',
]);
}
}

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