【私域操盘手】手机短信验证码登录

This commit is contained in:
eison
2025-03-16 19:24:37 +08:00
parent 1d7a87f29f
commit 5ce78d648e
39 changed files with 10304 additions and 13996 deletions

View File

@@ -1,7 +1,14 @@
# 环境标识
NODE_ENV=development NODE_ENV=development
VUE_APP_PREVIEW=false VUE_APP_PREVIEW=false
VUE_APP_API_BASE_URL=http://localhost:8000 # 应用名称
VUE_APP_WWW_BASE_URL=http://localhost:8000 VUE_APP_WEBSITE_NAME=医师管理系统
VUE_APP_WEB_SOCKET_URL=ws://localhost:2348
VUE_APP_WEBSITE_NAME="医师管理系统开发环境" # API基础URL
VUE_APP_TITLE=医师管理系统 VUE_APP_API_BASE_URL=http://yishi.com
# 前端网站
VUE_APP_WWW_BASE_URL=http://yishi.com
# WebSocket URL
VUE_APP_WEB_SOCKET_URL=wss://api.yishi.com/ws

14
Backend/.env.production Normal file
View File

@@ -0,0 +1,14 @@
# 环境标识
NODE_ENV=production
# 应用名称
VUE_APP_WEBSITE_NAME=医师管理系统
# API基础URL
VUE_APP_API_BASE_URL=https://api.yishi.com
# 前端网站URL
VUE_APP_WWW_BASE_URL=https://www.yishi.com
# WebSocket URL
VUE_APP_WEB_SOCKET_URL=wss://api.yishi.com/ws

14
Backend/.env.test Normal file
View File

@@ -0,0 +1,14 @@
# 环境标识
NODE_ENV=production
# 应用名称
VUE_APP_WEBSITE_NAME=医师管理系统(测试)
# API基础URL
VUE_APP_API_BASE_URL=https://test-api.yishi.com
# 前端网站URL
VUE_APP_WWW_BASE_URL=https://test.yishi.com
# WebSocket URL
VUE_APP_WEB_SOCKET_URL=wss://test-api.yishi.com/ws

View File

@@ -85,4 +85,82 @@ server {
#### 联系方式 #### 联系方式
QQ : 837215079 QQ : 837215079
### 如果你觉得还不错,请 Star , Fork 给作者鼓励一下。 ### 如果你觉得还不错,请 Star , Fork 给作者鼓励一下。
## 环境变量配置
本项目使用Vue CLI的环境变量配置功能可以在不同环境下使用不同的配置。
### 配置文件
- `.env` - 所有环境的默认配置
- `.env.development` - 开发环境配置
- `.env.test` - 测试环境配置
- `.env.production` - 生产环境配置
### 环境变量
项目中使用的主要环境变量:
- `VUE_APP_WEBSITE_NAME` - 网站名称
- `VUE_APP_API_BASE_URL` - API基础URL
- `VUE_APP_WWW_BASE_URL` - 前端网站URL
- `VUE_APP_WEB_SOCKET_URL` - WebSocket URL
### 使用方法
在代码中可以通过以下方式访问环境变量:
```js
// 直接访问
console.log(process.env.VUE_APP_API_BASE_URL)
// 通过配置文件访问
import config from '@/config/config'
console.log(config.BASE_API_URL)
```
### 运行与构建
开发环境:
```bash
# 使用开发环境配置运行
npm run serve:dev
# 使用开发环境配置构建
npm run build:dev
```
测试环境:
```bash
# 使用测试环境配置运行
npm run serve:test
# 使用测试环境配置构建
npm run build:test
```
生产环境:
```bash
# 使用生产环境配置运行
npm run serve:prod
# 使用生产环境配置构建
npm run build:prod
```
## 项目设置
```bash
# 安装依赖
npm install
# 启动开发服务器
npm run serve
# 构建生产版本
npm run build
# 代码检查
npm run lint
```

20427
Backend/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,17 +1,24 @@
{ {
"name": "YiShi", "name": "yishi-admin",
"version": "0.1.0", "version": "1.0.0",
"private": true, "private": true,
"scripts": { "scripts": {
"serve": "vue-cli-service serve", "serve": "vue-cli-service serve",
"serve:dev": "vue-cli-service serve --mode development",
"serve:test": "vue-cli-service serve --mode test",
"serve:prod": "vue-cli-service serve --mode production",
"build": "vue-cli-service build", "build": "vue-cli-service build",
"build:dev": "vue-cli-service build --mode development",
"build:test": "vue-cli-service build --mode test",
"build:prod": "vue-cli-service build --mode production",
"lint": "vue-cli-service lint" "lint": "vue-cli-service lint"
}, },
"dependencies": { "dependencies": {
"axios": "^0.21.0", "axios": "^0.21.1",
"babel-plugin-prismjs": "^2.0.1", "babel-plugin-prismjs": "^2.0.1",
"core-js": "^3.6.5", "core-js": "^3.6.5",
"element-ui": "^2.14.1", "crypto-js": "^4.1.1",
"element-ui": "^2.15.6",
"js-audio-recorder": "^1.0.6", "js-audio-recorder": "^1.0.6",
"js-base64": "^2.5.1", "js-base64": "^2.5.1",
"mavon-editor": "^2.9.0", "mavon-editor": "^2.9.0",
@@ -22,12 +29,14 @@
"vue-contextmenujs": "^1.3.13", "vue-contextmenujs": "^1.3.13",
"vue-cropper": "^0.5.5", "vue-cropper": "^0.5.5",
"vue-prism-editor": "^0.5.1", "vue-prism-editor": "^0.5.1",
"vue-router": "^3.4.9", "vue-router": "^3.2.0",
"vuex": "^3.5.1" "vuex": "^3.4.0"
}, },
"devDependencies": { "devDependencies": {
"@vue/cli-plugin-babel": "~4.5.0", "@vue/cli-plugin-babel": "~4.5.0",
"@vue/cli-plugin-eslint": "~4.5.0", "@vue/cli-plugin-eslint": "~4.5.0",
"@vue/cli-plugin-router": "~4.5.0",
"@vue/cli-plugin-vuex": "~4.5.0",
"@vue/cli-service": "~4.5.0", "@vue/cli-service": "~4.5.0",
"babel-eslint": "^10.1.0", "babel-eslint": "^10.1.0",
"babel-plugin-import": "^1.13.1", "babel-plugin-import": "^1.13.1",

View File

@@ -1,10 +1,34 @@
import { post, get } from '@/utils/request' import { post, get } from '@/utils/request'
// 登录服务接口 /**
export const ServeLogin = data => { * 用户登录
* @param {Object} data 登录数据
* @param {string} data.username 用户名
* @param {string} data.password 密码
* @param {boolean} data.is_encrypted 密码是否已加密
* @returns {Promise} 登录结果
*/
export function ServeLogin(data) {
return post('/api/auth/login', data) return post('/api/auth/login', data)
} }
/**
* 手机号验证码登录
* @param {Object} data 登录数据
* @param {string} data.mobile 手机号
* @param {string} data.code 验证码
* @param {boolean} data.is_encrypted 验证码是否已加密
* @returns {Promise} 登录结果
*/
export function ServeMobileLogin(data) {
return post('/api/auth/mobile-login', data)
}
// 发送验证码
export const ServeSendCode = data => {
return post('/api/auth/code', data)
}
// 获取用户信息 // 获取用户信息
export const ServeGetUser = () => { export const ServeGetUser = () => {
return get('/api/auth/info') return get('/api/auth/info')

View File

@@ -12,5 +12,13 @@ export default {
}, },
component: () => import('@/views/auth/login'), component: () => import('@/views/auth/login'),
}, },
{
path: '/auth/mobile-login',
meta: {
title: '手机号登录',
needLogin: false,
},
component: () => import('@/views/auth/mobile-login'),
},
], ],
} }

View File

@@ -0,0 +1,36 @@
import CryptoJS from 'crypto-js'
/**
* 密码加密工具类
*/
export default {
/**
* MD5加密
* @param {string} text 需要加密的文本
* @returns {string} 加密后的文本
*/
md5(text) {
return CryptoJS.MD5(text).toString()
},
/**
* SHA256加密
* @param {string} text 需要加密的文本
* @returns {string} 加密后的文本
*/
sha256(text) {
return CryptoJS.SHA256(text).toString()
},
/**
* 密码加密
* 使用SHA256加密可以根据需要修改为其他算法
* @param {string} password 原始密码
* @returns {string} 加密后的密码
*/
encryptPassword(password) {
// 可以添加自定义的盐值增加安全性
const salt = 'yishi_salt_2024'
return this.sha256(password + salt)
}
}

View File

@@ -13,6 +13,12 @@ const request = axios.create({
timeout: 20000, timeout: 20000,
}) })
// 输出当前环境和API基础URL仅在开发环境
if (process.env.NODE_ENV === 'development') {
console.log('当前环境:', process.env.NODE_ENV)
console.log('API基础URL:', config.BASE_API_URL)
}
/** /**
* 异常拦截处理器 * 异常拦截处理器
* *

View File

@@ -30,6 +30,9 @@
>立即登录 >立即登录
</el-button> </el-button>
</el-form-item> </el-form-item>
<div class="login-options">
<span @click="toMobileLogin">手机号登录</span>
</div>
</el-form> </el-form>
</div> </div>
</div> </div>
@@ -37,6 +40,7 @@
<script> <script>
import { setToken, setUserInfo } from '@/utils/auth' import { setToken, setUserInfo } from '@/utils/auth'
import { ServeLogin } from '@/api/user' import { ServeLogin } from '@/api/user'
import CryptoUtil from '@/utils/crypto'
export default { export default {
data() { data() {
@@ -76,9 +80,13 @@ export default {
}, },
login() { login() {
// 对密码进行加密
const encryptedPassword = CryptoUtil.encryptPassword(this.form.password)
ServeLogin({ ServeLogin({
username: this.form.username, username: this.form.username,
password: this.form.password, password: encryptedPassword,
is_encrypted: true // 标记密码已加密
}) })
.then(res => { .then(res => {
if (res.code == 200) { if (res.code == 200) {
@@ -112,6 +120,10 @@ export default {
}) })
}, },
toMobileLogin() {
this.$router.push('/auth/mobile-login')
},
toLink(url) { toLink(url) {
this.$router.push({ this.$router.push({
path: url, path: url,
@@ -122,4 +134,12 @@ export default {
</script> </script>
<style lang="less" scoped> <style lang="less" scoped>
@import '~@/assets/css/page/login-auth.less'; @import '~@/assets/css/page/login-auth.less';
.login-options {
text-align: right;
margin-top: 10px;
font-size: 14px;
color: #409EFF;
cursor: pointer;
}
</style> </style>

View File

@@ -0,0 +1,239 @@
<template>
<div id="login-box">
<div class="header">手机号登录</div>
<div class="main">
<el-form ref="form" :model="form" :rules="rules">
<el-form-item prop="mobile">
<el-input
v-model="form.mobile"
placeholder="手机号"
class="cuborder-radius"
maxlength="11"
@keyup.enter.native="onSubmit('form')"
/>
</el-form-item>
<el-form-item prop="code">
<div class="code-input">
<el-input
v-model="form.code"
placeholder="验证码"
class="cuborder-radius"
maxlength="6"
@keyup.enter.native="onSubmit('form')"
/>
<el-button
type="primary"
class="code-btn"
:disabled="codeBtnDisabled"
@click="sendCode"
>{{ codeBtnText }}</el-button>
</div>
</el-form-item>
<el-form-item>
<el-button
type="primary"
class="submit-btn"
:loading="loginLoading"
@click="onSubmit('form')"
>立即登录
</el-button>
</el-form-item>
<div class="login-options">
<span @click="toAccountLogin">账号密码登录</span>
</div>
</el-form>
</div>
</div>
</template>
<script>
import { setToken, setUserInfo } from '@/utils/auth'
import { ServeMobileLogin, ServeSendCode } from '@/api/user'
import CryptoUtil from '@/utils/crypto'
export default {
data() {
// 手机号验证规则
const validateMobile = (rule, value, callback) => {
if (!value) {
return callback(new Error('请输入手机号'))
}
if (!/^1[3-9]\d{9}$/.test(value)) {
return callback(new Error('手机号格式不正确'))
}
callback()
}
return {
loginLoading: false,
form: {
mobile: '',
code: '',
},
rules: {
mobile: [
{ required: true, message: '手机号不能为空', trigger: 'blur' },
{ validator: validateMobile, trigger: 'blur' }
],
code: [
{ required: true, message: '验证码不能为空', trigger: 'blur' },
{ min: 4, max: 6, message: '验证码长度必须在4-6位之间', trigger: 'blur' }
],
},
codeBtnText: '获取验证码',
codeBtnDisabled: false,
countdown: 60,
timer: null
}
},
beforeDestroy() {
// 组件销毁前清除定时器
if (this.timer) {
clearInterval(this.timer)
}
},
methods: {
onSubmit(formName) {
if (this.loginLoading) return false
this.$refs[formName].validate(valid => {
if (!valid) return false
this.loginLoading = true
this.login()
})
},
login() {
// 不再对验证码进行加密,直接使用明文
ServeMobileLogin({
mobile: this.form.mobile,
code: this.form.code,
is_encrypted: false // 标记验证码未加密
})
.then(res => {
if (res.code == 200) {
let result = res.data
// 保存授权信息到本地缓存
setToken(result.token, result.token_expired - Math.floor(Date.now() / 1000))
// 保存用户信息到本地缓存
setUserInfo(result.member)
this.$store.commit('UPDATE_USER_INFO', result.member)
this.$store.commit('UPDATE_LOGIN_STATUS', true)
this.$notify.success({
title: '成功',
message: '登录成功',
})
// 跳转到首页
this.toLink('/')
} else {
this.$notify.error({
title: '错误',
message: res.msg,
})
}
})
.finally(() => {
this.loginLoading = false
})
},
// 发送验证码
sendCode() {
if (this.codeBtnDisabled) return
// 验证手机号
this.$refs.form.validateField('mobile', (errorMsg) => {
if (errorMsg) return
this.codeBtnDisabled = true
// 发送验证码请求
ServeSendCode({
mobile: this.form.mobile,
type: 'login'
}).then(res => {
if (res.code === 200) {
this.$notify.success({
title: '成功',
message: '验证码发送成功',
})
// 开始倒计时
this.startCountdown()
// 测试环境下,自动填充验证码
if (res.data && res.data.code) {
this.form.code = res.data.code
}
} else {
this.$notify.error({
title: '错误',
message: res.msg,
})
this.codeBtnDisabled = false
}
}).catch(() => {
this.codeBtnDisabled = false
})
})
},
// 开始倒计时
startCountdown() {
this.countdown = 60
this.codeBtnText = `${this.countdown}秒后重新获取`
this.timer = setInterval(() => {
if (this.countdown > 1) {
this.countdown--
this.codeBtnText = `${this.countdown}秒后重新获取`
} else {
clearInterval(this.timer)
this.codeBtnDisabled = false
this.codeBtnText = '获取验证码'
}
}, 1000)
},
// 跳转到账号密码登录
toAccountLogin() {
this.$router.push('/auth/login')
},
toLink(url) {
this.$router.push({
path: url,
})
},
},
}
</script>
<style lang="less" scoped>
@import '~@/assets/css/page/login-auth.less';
.code-input {
display: flex;
align-items: center;
.el-input {
flex: 1;
}
.code-btn {
margin-left: 10px;
width: 120px;
}
}
.login-options {
text-align: right;
margin-top: 10px;
font-size: 14px;
color: #409EFF;
cursor: pointer;
}
</style>

View File

@@ -1,157 +0,0 @@
<?php
namespace app\backend\controller;
use app\common\model\CollectProductModel;
use app\common\model\ProductGroupModel;
use think\db\Query;
class CollectProductController extends BaseLoginController {
/**
* 获取列表
*
* @return \think\response\Json
*/
public function index() {
$parentId = intval($this->request->param('parent_id'));
$type = trim($this->request->param('type'));
$video = trim($this->request->param('video'));
$repeat = trim($this->request->param('repeat'));
$groupId = intval($this->request->param('group_id'));
$status = trim($this->request->param('status'));
$keywords = trim($this->request->param('keywords'));
$pageNo = intval($this->request->param('page'));
$pageSize = intval($this->request->param('pageSize'));
if ($pageNo <= 0) {
$pageNo = 1;
}
if ($pageSize <= 0) {
$pageSize = 30;
}
$query = CollectProductModel::where(1);
if (!empty($parentId)) {
$query->where('parent_id', $parentId);
} else {
$query->where('parent_id', 0);
}
if (isset(CollectProductModel::typeAssoc()[$type])) {
$query->where('type', $type);
}
if (isset(CollectProductModel::videoAssoc()[$video])) {
$query->where('video', $video);
}
if (isset(CollectProductModel::repeatAssoc()[$repeat])) {
$query->where('repeat', $repeat);
}
if (isset(CollectProductModel::statusAssoc()[$status])) {
$query->where('status', $status);
}
if (!empty($groupId)) {
$query->where('group_id', $groupId);
}
if (!empty($keywords)) {
$query->where(function (Query $q) use ($keywords) {
$q->whereLike('src_url', '%' . $keywords . '%', 'OR');
$q->whereLike('title', '%' . $keywords . '%', 'OR');
});
}
$totalCount = $query->count();
$pageCount = $totalCount > 0 ? ceil($totalCount / $pageSize) : 1;
if ($pageNo > $pageCount) {
$pageNo = $pageCount;
}
$query->order('id', 'DESC');
$query->limit(($pageNo - 1) * $pageSize, $pageSize);
$list = [];
foreach ($query->select() as $model) {
$list[] = array_merge($model->toArray(), [
'product_num' => $model->productNum(),
'status_name' => CollectProductModel::statusAssoc()[$model->status],
'video_name' => CollectProductModel::videoAssoc()[$model->video],
'repeat_name' => CollectProductModel::repeatAssoc()[$model->repeat],
'platform_name' => CollectProductModel::platformAssoc()[$model->platform],
'mark_up_rate' => floatval($model->mark_up_rate),
'mark_up_val' => floatval($model->mark_up_val),
'start_time' => $model->start_time > 0 ? date('Y-m-d H:i:s', $model->start_time) : '',
'stop_time' => $model->stop_time > 0 ? date('Y-m-d H:i:s', $model->stop_time) : '',
'group_name' => isset(ProductGroupModel::assoc()[$model->group_id])
? ProductGroupModel::assoc()[$model->group_id]
: '',
]);
}
return $this->jsonSucc([
'list' => $list,
'page' => $pageNo,
'pageCount' => $pageCount,
'totalCount' => $totalCount,
'statuses' => $this->assocToList(CollectProductModel::statusAssoc()),
'platforms' => $this->assocToList(CollectProductModel::platformAssoc()),
'videos' => $this->assocToList(CollectProductModel::videoAssoc()),
'repeats' => $this->assocToList(CollectProductModel::repeatAssoc()),
'groups' => $this->assocToList(ProductGroupModel::assoc()),
]);
}
/**
* 保存
*
* @return \think\response\Json
*/
public function save() {
//$id = intval($this->request->param('id'));
$type = trim($this->request->param('type'));
$srcUrl = trim($this->request->param('src_url'));
$video = intval($this->request->param('video'));
$repeat = intval($this->request->param('repeat'));
$markUpRate = intval($this->request->param('mark_up_rate'));
$markUpVal = intval($this->request->param('mark_up_val'));
$groupId = intval($this->request->param('group_id'));
if (empty($srcUrl)
OR !isset(CollectProductModel::typeAssoc()[$type])
OR !isset(CollectProductModel::videoAssoc()[$video])
OR !isset(CollectProductModel::repeatAssoc()[$repeat])) {
return $this->jsonFail('参数错误');
}
if (!empty($id)) {
$model = CollectProductModel::get($id);
if (empty($model)) {
return $this->jsonFail('对象未找到');
}
} else {
$model = new CollectProductModel();
}
$platform = '';
if ($type === CollectProductModel::TYPE_PRODUCT
AND preg_match('#^https\:\/\/www\.goofish\.com\/item#', $srcUrl)) {
$platform = CollectProductModel::PLATFORM_XIANYU;
} elseif ($type === CollectProductModel::TYPE_SHOP
AND preg_match('#^https\:\/\/www\.goofish\.com\/personal#', $srcUrl)) {
$platform = CollectProductModel::PLATFORM_XIANYU;
} else {
return $this->jsonFail('采集链接错误');
}
$model = new CollectProductModel();
$model->type = $type;
$model->src_url = $srcUrl;
$model->platform = $platform;
$model->target = CollectProductModel::TARGET_PRODUCT;
$model->video = $video;
$model->repeat = $repeat;
$model->mark_up_rate = $markUpRate;
$model->mark_up_val = $markUpVal;
$model->group_id = $groupId;
$model->user_id = $this->userModel->id;
$model->save();
return $this->jsonSucc();
}
}

View File

@@ -1,92 +0,0 @@
<?php
namespace app\backend\controller;
use app\common\model\DeviceModel;
use think\db\Query;
class DeviceController extends BaseLoginController {
/**
* 获取列表
*
* @return \think\response\Json
*/
public function index() {
$isOnline = intval($this->request->param('is_online'));
$status = trim($this->request->param('status'));
$keywords = trim($this->request->param('keywords'));
$pageNo = intval($this->request->param('page'));
$pageSize = intval($this->request->param('pageSize'));
if ($pageNo <= 0) {
$pageNo = 1;
}
if ($pageSize <= 0) {
$pageSize = 30;
}
$onlines = [
1 => '在线',
2 => '离线',
];
$query = DeviceModel::where(1);
if (isset(DeviceModel::statusAssoc()[$status])) {
$query->where('status', $status);
}
if ($isOnline == 1) {
$query->where('is_online', DeviceModel::IS_ONLINE_YES);
$query->where('active_time', '>=', time() - DeviceModel::ACTIVE_TIME);
} elseif ($isOnline == 2) {
$query->where(function (Query $q) {
$q->whereOr('is_online', DeviceModel::IS_ONLINE_NO);
$q->whereOr('active_time', '<', time() - DeviceModel::ACTIVE_TIME);
});
}
if (!empty($keywords)) {
$query->where(function (Query $q) use ($keywords) {
$q->whereLike('number', '%' . $keywords . '%', 'OR');
$q->whereLike('ip', '%' . $keywords . '%', 'OR');
});
}
$totalCount = $query->count();
$pageCount = $totalCount > 0 ? ceil($totalCount / $pageSize) : 1;
if ($pageNo > $pageCount) {
$pageNo = $pageCount;
}
$query->order('id', 'DESC');
$query->limit(($pageNo - 1) * $pageSize, $pageSize);
$list = [];
foreach ($query->select() as $model) {
$list[] = array_merge($model->toArray(), [
'is_online' => $model->isOnline() ? 1 : 2,
'is_online_name' => $onlines[$model->isOnline() ? 1 : 2],
'status_name' => DeviceModel::statusAssoc()[$model->status],
'active_time' => date('Y-m-d H:i:s', $model->active_time),
]);
}
return $this->jsonSucc([
'list' => $list,
'page' => $pageNo,
'pageCount' => $pageCount,
'totalCount' => $totalCount,
'statuses' => $this->assocToList(DeviceModel::statusAssoc()),
'onlines' => $this->assocToList($onlines),
]);
}
/**
* 获取关联数组
*
* @return \think\response\Json
*/
public function assoc() {
return $this->jsonSucc($this->assocToList(DeviceModel::assoc()));
}
}

View File

@@ -1,151 +0,0 @@
<?php
namespace app\backend\controller;
use think\db\Query;
class DeviceStatController extends BaseLoginController {
/**
* 获取列表
*
* @return \think\response\Json
*/
public function index() {
$keywords = trim($this->request->param('keywords'));
$pageNo = intval($this->request->param('page'));
$pageSize = intval($this->request->param('pageSize'));
if ($pageNo <= 0) {
$pageNo = 1;
}
if ($pageSize <= 0) {
$pageSize = 30;
}
$query = DeviceStatModel::where(1);
if (!empty($keywords)) {
$query->where(function (Query $q) use ($keywords) {
$q->whereLike('days', '%' . $keywords . '%', 'OR');
});
}
$totalCount = $query->count();
$pageCount = $totalCount > 0 ? ceil($totalCount / $pageSize) : 1;
if ($pageNo > $pageCount) {
$pageNo = $pageCount;
}
$query->order('id', 'DESC');
$query->limit(($pageNo - 1) * $pageSize, $pageSize);
$list = [];
if ($pageNo <= 1) {
$num = DeviceQqModel::where(1)
->where('create_time', '>=', 1724860800)
->count();
$succNum = DeviceQqModel::where(1)
->where('create_time', '>=', 1724860800)
->where('status', DeviceQqModel::STATUS_SUCC)
->count();
$rewardTotal = floatval(DeviceQqModel::where(1)
->where('create_time', '>=', 1724860800)
->where('status', DeviceQqModel::STATUS_SUCC)
->sum('reward'));
$rewardPrice = 0;
if ($succNum > 0) {
$rewardPrice = round($rewardTotal / $succNum, 2);
}
$finishTime = intval(DeviceQqModel::where(1)
->where('create_time', '>=', 1724860800)
->where('finish_time', '>', 0)
->avg('finish_time'));
$createTime = intval(DeviceQqModel::where(1)
->where('create_time', '>=', 1724860800)
->where('finish_time', '>', 0)
->avg('create_time'));
$succFinishTime = intval(DeviceQqModel::where(1)
->where('create_time', '>=', 1724860800)
->where('status', DeviceQqModel::STATUS_SUCC)
->where('finish_time', '>', 0)
->avg('finish_time'));
$succCreateTime = intval(DeviceQqModel::where(1)
->where('create_time', '>=', 1724860800)
->where('status', DeviceQqModel::STATUS_SUCC)
->where('finish_time', '>', 0)
->avg('create_time'));
$succRate = $num > 0 ? round($succNum / $num * 100, 2) : 0;
$failNum = $num - $succNum;
$failRate = $num > 0 ? round($failNum / $num * 100, 2) : 0;
$succFailRate = 0;
if ($num > $succNum) {
$succFailRate = $succNum / ($num - $succNum);
$succFailRate = round($succFailRate * 100, 2);
}
$failInfo = [];
foreach (DeviceQqModel::where(1)
->field('remark, COUNT(id) AS num')
->where('create_time', '>=', 1724860800)
->whereNotIn('status', [DeviceQqModel::STATUS_REG, DeviceQqModel::STATUS_SUCC])
->group('remark')
->select() as $infoModel) {
if ($infoModel->remark) {
$failInfo[] = [
'title' => $infoModel->remark,
'num' => $infoModel->num,
'rate' => $num > 0 ? round($infoModel->num / $num * 100, 2) : 0,
];
}
}
$list[] = [
'days' => '合计',
'num' => $num,
'succ_num' => $succNum,
'reward_price' => $rewardPrice,
'reward_total' => $rewardTotal,
'avg_time' => $finishTime > $createTime ? $finishTime - $createTime : 0,
'avg_succ_time' => $succFinishTime > $succCreateTime ? $succFinishTime - $succCreateTime : 0,
'succ_fail_rate' => $succFailRate,
'succ_rate' => $succRate,
'fail_num' => $failNum,
'fail_rate' => $failRate,
'fail_info' => $failInfo,
];
}
foreach ($query->select() as $model) {
$succRate = 0;
$failNum = $model->num > $model->succ_num ? $model->num - $model->succ_num : 0;
$failRate = 0;
$succFailRate = 0;
if ($model->num > 0) {
$succRate = $model->succ_num / $model->num;
$succRate = round($succRate * 100, 2);
$failRate = $failNum / $model->num;
$failRate = round($failRate * 100, 2);
}
if ($model->num > $model->succ_num) {
$succFailRate = $model->succ_num / ($model->num - $model->succ_num);
$succFailRate = round($succFailRate * 100, 2);
}
$list[] = array_merge($model->toArray(), [
'reward_price' => floatval($model->reward_price),
'reward_total' => floatval($model->reward_total),
'succ_fail_rate' => $succFailRate,
'fail_num' => $failNum,
'succ_rate' => $succRate,
'fail_rate' => $failRate,
]);
}
return $this->jsonSucc([
'list' => $list,
'page' => $pageNo,
'pageCount' => $pageCount,
'totalCount' => $totalCount,
]);
}
}

View File

@@ -1,32 +0,0 @@
<?php
namespace app\backend\controller;
use app\common\model\TaskModel;
class MessageReplyController extends BaseLoginController {
/**
* 关闭
*
* @return \think\response\Json
*/
public function close() {
$devices = $this->request->param('devices');
if (empty($devices) OR !is_array($devices)) {
return $this->jsonFail('参数错误');
}
TaskModel::where(1)
->whereIn('device_id', $devices)
->where('platform', TaskModel::PLATFORM_XIANYU)
->whereIn('status', [TaskModel::STATUS_AWAIT, TaskModel::STATUS_ALLOC])
->where('is_deleted', TaskModel::IS_DELETED_NO)
->update([
'is_deleted' => TaskModel::IS_DELETED_YES,
'update_time' => time(),
]);
return $this->jsonSucc();
}
}

View File

@@ -1,59 +0,0 @@
<?php
namespace app\backend\controller;
use app\common\model\ProductContentPoolModel;
class ProductContentPoolController extends BaseLoginController {
/**
* 获取关联数组
*
* @return \think\response\Json
*/
public function assoc() {
return $this->jsonSucc(ProductContentPoolModel::assoc());
}
/**
* 保存
*
* @return \think\response\Json
* @throws \Exception
*/
public function save() {
$contents = $this->request->param('contents');
if (!is_array($contents)) {
return $this->jsonFail('参数错误');
}
for ($i = 1; $i <= 6; $i ++) {
if (isset($contents[$i])) {
$content = trim($contents[$i]);
$content = trim($content, '-');
$content = trim($content);
if (!empty($content)) {
$model = ProductContentPoolModel::where(1)
->where('number', $i)
->find();
if (empty($model)) {
$model = new ProductContentPoolModel();
}
$model->number = $i;
$model->content = trim($content);
$model->save();
} else {
ProductContentPoolModel::where(1)
->where('number', $i)
->delete();
}
} else {
ProductContentPoolModel::where(1)
->where('number', $i)
->delete();
}
}
return $this->jsonSucc();
}
}

View File

@@ -1,361 +0,0 @@
<?php
namespace app\backend\controller;
use app\common\model\ProductGroupModel;
use app\common\model\ProductModel;
use think\db\Query;
class ProductController extends BaseLoginController {
/**
* 获取列表
*
* @return \think\response\Json
*/
public function index() {
$groupId = trim($this->request->param('group_id'));
$isUsed = trim($this->request->param('is_used'));
$keywords = trim($this->request->param('keywords'));
$pageNo = intval($this->request->param('page'));
$pageSize = intval($this->request->param('pageSize'));
if ($pageNo <= 0) {
$pageNo = 1;
}
if ($pageSize <= 0) {
$pageSize = 30;
}
$query = ProductModel::where(1);
if (strlen($groupId) > 0) {
$query->where('group_id', $groupId);
}
if (isset(ProductModel::isUsedAssoc()[$isUsed])) {
$query->where('is_used', $isUsed);
}
if (!empty($keywords)) {
$query->where(function (Query $q) use ($keywords) {
$q->whereLike('title', '%' . $keywords . '%', 'OR');
});
}
$totalCount = $query->count();
$pageCount = $totalCount > 0 ? ceil($totalCount / $pageSize) : 1;
if ($pageNo > $pageCount) {
$pageNo = $pageCount;
}
$query->order('id', 'DESC');
$query->limit(($pageNo - 1) * $pageSize, $pageSize);
$list = [];
foreach ($query->select() as $model) {
$images = [];
foreach ($model->images as $image) {
$images[] = array_merge($image, [
'url' => $this->absoluteUrl($image['path']),
]);
}
$list[] = array_merge($model->toArray(), [
'images' => $images,
'is_used_name' => ProductModel::isUsedAssoc()[$model->is_used],
'group_name' => isset(ProductGroupModel::assoc()[$model->group_id])
? ProductGroupModel::assoc()[$model->group_id]
: '',
]);
}
return $this->jsonSucc([
'list' => $list,
'page' => $pageNo,
'pageCount' => $pageCount,
'totalCount' => $totalCount,
'groups' => $this->assocToList(ProductGroupModel::assoc()),
'isUseds' => $this->assocToList(ProductModel::isUsedAssoc()),
]);
}
/**
* 批量修改主题
*
* @return \think\response\Json
*/
public function theme() {
$checked = $this->request->param('checked');
$themes = $this->request->param('themes');
if (empty($checked)
OR !is_array($checked)) {
return $this->jsonFail('参数错误');
}
if (empty($themes) OR !is_array($themes)) {
$themes = [];
}
ProductModel::where(1)
->whereIn('id', $checked)
->update([
'themes' => json_encode($themes, JSON_UNESCAPED_UNICODE),
'update_time' => time(),
]);
return $this->jsonSucc();
}
/**
* 批量修改标签
*
* @return \think\response\Json
*/
public function label() {
$checked = $this->request->param('checked');
$labels = $this->request->param('labels');
if (empty($checked)
OR !is_array($checked)) {
return $this->jsonFail('参数错误');
}
if (empty($labels) OR !is_array($labels)) {
$labels = [];
}
ProductModel::where(1)
->whereIn('id', $checked)
->update([
'labels' => json_encode($labels, JSON_UNESCAPED_UNICODE),
'update_time' => time(),
]);
return $this->jsonSucc();
}
/**
* 批量修改标题
*
* @return \think\response\Json
*/
public function title() {
$checked = $this->request->param('checked');
$type = intval($this->request->param('type'));
$title = trim($this->request->param('title'));
if (empty($checked)
OR !is_array($checked)
OR !in_array($type, [0, 1])
OR empty($title)) {
return $this->jsonFail('参数错误');
}
foreach (ProductModel::where(1)
->whereIn('id', $checked)
->select() as $model) {
if ($type == 0) {
$model->title = $model->title . $title;
} else {
$model->title = $title . $model->title;
}
$model->save();
}
return $this->jsonSucc();
}
/**
* 批量修改描述
*
* @return \think\response\Json
*/
public function content() {
$checked = $this->request->param('checked');
$type = intval($this->request->param('type'));
$content = trim($this->request->param('content'));
if (empty($checked)
OR !is_array($checked)
OR !in_array($type, [0, 1])
OR empty($content)) {
return $this->jsonFail('参数错误');
}
foreach (ProductModel::where(1)
->whereIn('id', $checked)
->select() as $model) {
if ($type == 0) {
$model->content = $model->content . "\n" . $content;
} else {
$model->content = $content . "\n" . $model->content;
}
$model->save();
}
return $this->jsonSucc();
}
/**
* 批量修改库存
*
* @return \think\response\Json
*/
public function stock() {
$checked = $this->request->param('checked');
$stock = intval($this->request->param('stock'));
if (empty($checked)
OR !is_array($checked)) {
return $this->jsonFail('参数错误');
}
if ($stock <= 0) {
return $this->jsonFail('库存不可小于1');
}
ProductModel::where(1)
->whereIn('id', $checked)
->update([
'stock' => $stock,
'update_time' => time(),
]);
return $this->jsonSucc();
}
/**
* 批量修改价格
*
* @return \think\response\Json
*/
public function price() {
$checked = $this->request->param('checked');
$type = intval($this->request->param('type'));
$rate = floatval($this->request->param('rate'));
$val = floatval($this->request->param('val'));
$price = floatval($this->request->param('price'));
if (empty($checked)
OR !is_array($checked)
OR !in_array($type, [0, 1, 2])) {
return $this->jsonFail('参数错误');
}
if ($type == 0 OR $type == 1) {
if ($rate <= 0 AND $val <= 0) {
return $this->jsonFail('请输入比例或数值');
}
} elseif ($type == 2) {
if ($price <= 0) {
return $this->jsonFail('请输入金额');
}
}
foreach (ProductModel::where(1)
->whereIn('id', $checked)
->select() as $model) {
if ($type == 0) {
if ($rate > 0) {
$model->price -= $model->price * $rate / 100;
} else {
$model->price -= $val;
}
} elseif ($type == 1) {
if ($rate > 0) {
$model->price += $model->price * $rate / 100;
} else {
$model->price += $val;
}
} elseif ($type == 2) {
$model->price = $price;
}
$model->price = round($model->price, 2);
if ($model->price > 0) {
$model->save();
}
}
return $this->jsonSucc();
}
/**
* 保存
*
* @return \think\response\Json
*/
public function save() {
$id = intval($this->request->param('id'));
$groupId = intval($this->request->param('group_id'));
$title = trim($this->request->param('title'));
$content = trim($this->request->param('content'));
$cb = $this->request->param('cb');
$images = $this->request->param('images');
$labels = $this->request->param('labels');
$themes = $this->request->param('themes');
$opts = $this->request->param('opts');
$address = trim($this->request->param('address'));
$price = floatval($this->request->param('price'));
$stock = intval($this->request->param('stock'));
$shippingFee = floatval($this->request->param('shipping_fee'));
$video = trim($this->request->param('video'));
if (empty($title)
OR $groupId < 0
OR empty($content)
OR !is_array($cb)
OR !is_array($images)
OR empty($images)
OR !is_array($labels)
OR !is_array($themes)
OR !is_array($opts)
OR $price < 0
OR $stock < 0
OR $shippingFee < 0) {
return $this->jsonFail('参数错误');
}
foreach ($images as $i => $image) {
unset($images[$i]['url']);
}
if (!empty($id)) {
$model = ProductModel::get($id);
if (empty($model)) {
return $this->jsonFail('对象未找到');
}
} else {
$model = new ProductModel();
}
$model->group_id = $groupId;
$model->title = $title;
$model->content = $content;
$model->cb = $cb;
$model->video = $video;
$model->images = $images;
$model->labels = $labels;
$model->themes = $themes;
$model->address = $address;
$model->price = $price;
$model->stock = $stock;
$model->shipping_fee = $shippingFee;
$model->opts = $opts;
$model->save();
return $this->jsonSucc();
}
/**
* 删除
*
* @return \think\response\Json
* @throws \Exception
*/
public function delete() {
$id = intval($this->request->param('id'));
if (empty($id)) {
return $this->jsonFail('参数错误');
}
$model = ProductModel::get($id);
if (empty($model)) {
return $this->jsonFail('对象未找到');
}
$model->delete();
return $this->jsonSucc();
}
}

View File

@@ -1,118 +0,0 @@
<?php
namespace app\backend\controller;
use app\common\model\CollectProductModel;
use app\common\model\ProductGroupModel;
use app\common\model\ProductModel;
use think\db\Query;
class ProductGroupController extends BaseLoginController {
/**
* 获取列表
*
* @return \think\response\Json
*/
public function index() {
$keywords = trim($this->request->param('keywords'));
$pageNo = intval($this->request->param('page'));
$pageSize = intval($this->request->param('pageSize'));
if ($pageNo <= 0) {
$pageNo = 1;
}
if ($pageSize <= 0) {
$pageSize = 30;
}
$query = ProductGroupModel::where(1);
if (!empty($keywords)) {
$query->where(function (Query $q) use ($keywords) {
$q->whereLike('name', '%' . $keywords . '%', 'OR');
});
}
$totalCount = $query->count();
$pageCount = $totalCount > 0 ? ceil($totalCount / $pageSize) : 1;
if ($pageNo > $pageCount) {
$pageNo = $pageCount;
}
$query->order('id', 'DESC');
$query->limit(($pageNo - 1) * $pageSize, $pageSize);
$list = [];
foreach ($query->select() as $model) {
$list[] = array_merge($model->toArray(), [
'product_num' => $model->productNum(),
]);
}
return $this->jsonSucc([
'list' => $list,
'page' => $pageNo,
'pageCount' => $pageCount,
'totalCount' => $totalCount,
]);
}
/**
* 保存
*
* @return \think\response\Json
*/
public function save() {
$id = intval($this->request->param('id'));
$name = trim($this->request->param('name'));
if (empty($name)) {
return $this->jsonFail('参数错误');
}
if (!empty($id)) {
$model = ProductGroupModel::get($id);
if (empty($model)) {
return $this->jsonFail('对象未找到');
}
} else {
$model = new ProductGroupModel();
}
$model->name = $name;
$model->save();
return $this->jsonSucc();
}
/**
* 删除
*
* @return \think\response\Json
* @throws \Exception
*/
public function delete() {
$id = intval($this->request->param('id'));
if (empty($id)) {
return $this->jsonFail('参数错误');
}
$model = ProductGroupModel::get($id);
if (empty($model)) {
return $this->jsonFail('对象未找到');
}
CollectProductModel::where(1)
->where('group_id', $model->id)
->update([
'group_id' => 0,
]);
ProductModel::where(1)
->where('group_id', $model->id)
->update([
'group_id' => 0,
]);
$model->delete();
return $this->jsonSucc();
}
}

View File

@@ -1,63 +0,0 @@
<?php
namespace app\backend\controller;
use app\common\model\ProductModel;
use app\common\model\ProductUseModel;
use app\common\model\TaskModel;
use app\common\task\ProductReleaseTask;
class ProductReleaseController extends BaseLoginController {
/**
* 保存
*
* @return \think\response\Json
*/
public function save() {
$devices = $this->request->param('devices');
$params = ProductReleaseTask::params($this->request);
if (empty($devices)
OR !is_array($devices)
OR is_null($params)) {
return $this->jsonFail('参数错误');
}
foreach ($devices as $deviceId) {
$pdts = [];
for ($i = 0; $i < $params['release_num']; $i ++) {
$product = array_shift($params['products']);
if (!empty($product)) {
$pdts[] = $product;
}
}
if (!empty($pdts)) {
$model = new TaskModel();
$model->device_id = $deviceId;
$model->platform = TaskModel::PLATFORM_XIANYU;
$model->type = TaskModel::TYPE_PRODUCT_RELEASE;
$model->params = array_merge($params, ['products' => $pdts]);
$model->run_type = TaskModel::RUN_TYPE_ONCE;
$model->run_time = '';
if ($model->save()) {
foreach ($pdts as $pdt) {
$useModel = new ProductUseModel();
$useModel->device_id = $deviceId;
$useModel->release_id = $model->id;
$useModel->product_id = $pdt['id'];
$useModel->use_type = $model->platform;
$useModel->save();
ProductModel::where(1)
->where('id', $pdt['id'])
->update([
'is_used' => ProductModel::IS_USED_YES,
]);
}
}
}
}
return $this->jsonSucc();
}
}

View File

@@ -1,795 +0,0 @@
<?php
namespace app\backend\controller;
use app\common\CloneInfo;
use app\common\model\MemberModel;
use app\common\model\MemberMoneyModel;
use app\common\model\MemberQrcodeModel;
use app\common\model\MemberWithdrawModel;
use app\common\model\StatisticsModel;
use app\common\Utils;
class StatController extends BaseLoginController {
protected $chartColumns = [
'register' => '用户注册',
'qr_number' => '扫码企点号',
'qr_member' => '扫码人数',
'qrcode' => '取码次数',
'qrcode_succ' => '取码成功',
'clone_succ' => '克隆成功',
'clone_error' => '克隆错误',
'clone_timeout' => '克隆超时',
'xinyue_select' => '选择克隆心悦数',
'xinyue_clone' => '克隆成功心悦数',
'reward_clone' => '克隆奖励笔数',
'reward_invite1' => '一级奖励笔数',
'reward_invite2' => '二级奖励笔数',
'money_clone' => '克隆奖励金额',
'money_invite1' => '一级奖励金额',
'money_invite2' => '二级奖励金额',
'withdraw' => '申请提现笔数',
'withdraw_check' => '检测通过笔数',
'withdraw_money' => '申请提现金额',
'withdraw_money_check' => '检测通过金额',
'withdraw_succ' => '提现成功笔数',
'withdraw_succ_money' => '提现成功金额',
'withdraw_fail' => '提现失败笔数',
'withdraw_fail_money' => '提现失败金额',
];
public function dayChartView() {
$day = trim($this->request->param('day'));
$columns = trim($this->request->param('columns'));
$columns = explode('|', $columns);
if (empty($day) OR empty($columns)) {
exit('Page not found.');
}
$legend = [];
$xAxis = [];
$series = [];
for ($i = 0; $i <= 23; $i ++) {
$xAxis[] = $i . '时';
}
foreach ($columns as $i => $column) {
if (isset($this->chartColumns[$column])) {
$legend[] = $this->chartColumns[$column];
$series[$column] = [
'name' => $this->chartColumns[$column],
'type' => 'line',
'stack' => 'Total',
'data' => [],
];
} else {
unset($columns[$i]);
}
}
$list = $this->getDayData($day);
$rows = [];
foreach ($list as $row) {
$rows[$row['hour']] = $row;
}
foreach ($xAxis as $axi) {
if (isset($rows[$axi])) {
foreach ($columns as $column) {
$series[$column]['data'][] = $rows[$axi][$column];
}
} else {
foreach ($columns as $column) {
$series[$column]['data'][] = 0;
}
}
}
return $this->fetch('/chart-day', [
'legend' => $legend,
'xAxis' => $xAxis,
'series' => array_values($series),
]);
}
public function monthChartView() {
$month = trim($this->request->param('month'));
$columns = trim($this->request->param('columns'));
$columns = explode('|', $columns);
if (empty($month) OR empty($columns)) {
exit('Page not found.');
}
$time = strtotime($month . '01');
if ($time === FALSE) {
exit('Page not found.');
}
$days = date('t', $time);
$legend = [];
$xAxis = [];
$series = [];
for ($i = 1; $i <= $days; $i ++) {
$xAxis[] = $i . '日';
}
foreach ($columns as $i => $column) {
if (isset($this->chartColumns[$column])) {
$legend[] = $this->chartColumns[$column];
$series[$column] = [
'name' => $this->chartColumns[$column],
'type' => 'line',
'stack' => 'Total',
'data' => [],
];
} else {
unset($columns[$i]);
}
}
$list = $this->getMonthData($month);
$rows = [];
foreach ($list as $row) {
$row['day'] = str_replace(['年', '月'], '-', $row['day']);
$row['day'] = str_replace(['日'], '', $row['day']);
$row['day'] = intval(date('d', strtotime($row['day']))) . '日';
$rows[$row['day']] = $row;
}
foreach ($xAxis as $axi) {
if (isset($rows[$axi])) {
foreach ($columns as $column) {
$series[$column]['data'][] = $rows[$axi][$column];
}
} else {
foreach ($columns as $column) {
$series[$column]['data'][] = 0;
}
}
}
return $this->fetch('/chart-day', [
'legend' => $legend,
'xAxis' => $xAxis,
'series' => array_values($series),
]);
}
/**
* 日统计图表
*
* @return \think\response\Json
*/
public function dayChart() {
$day = trim($this->request->param('day'));
$columns = $this->request->param('columns');
if (empty($day)) {
$day = date('Y-m-d');
}
if (strtotime($day) === FALSE) {
return $this->jsonFail('日期错误');
}
if (empty($columns)) {
$columns = ['xinyue_select', 'xinyue_clone', 'money_clone'];
}
return $this->jsonSucc([
'day' => $day,
'columns' => $columns,
'chartColumns' => $this->assocToList($this->chartColumns),
'url' => $this->absoluteUrl('/backend/stat/dayChartView?token=' . $this->token . '&day=' . $day . '&columns=' . implode('|', $columns)),
]);
}
/**
* 月统计图表
*
* @return \think\response\Json
*/
public function monthChart() {
$month = trim($this->request->param('month'));
$columns = $this->request->param('columns');
if (empty($month)) {
$month = date('Ym');
}
if (empty($columns)) {
$columns = ['xinyue_select', 'xinyue_clone', 'money_clone'];
}
return $this->jsonSucc([
'month' => $month,
'months' => $this->assocToList($this->getMonths()),
'columns' => $columns,
'chartColumns' => $this->assocToList($this->chartColumns),
'url' => $this->absoluteUrl('/backend/stat/monthChartView?token=' . $this->token . '&month=' . $month . '&columns=' . implode('|', $columns)),
]);
}
/**
* 日统计列表
*
* @return \think\response\Json
*/
public function dayIndex() {
$day = trim($this->request->param('day'));
if (empty($day)) {
$day = date('Y-m-d');
}
if (strtotime($day) === FALSE) {
return $this->jsonFail('日期错误');
}
return $this->jsonSucc([
'list' => $this->getDayData($day),
'day' => $day,
]);
}
/**
* 月统计
*
* @return \think\response\Json
*/
public function monthIndex() {
$month = trim($this->request->param('month'));
if (empty($month)) {
$month = date('Ym');
}
return $this->jsonSucc([
'list' => $this->getMonthData($month),
'month' => $month,
'months' => $this->assocToList($this->getMonths()),
]);
}
/**
* 获取统计信息
*
* @return \think\response\Json
*/
public function get() {
$weekTime = time() - 7 * 24 * 3600;
$monthTime = time() - 30 * 24 * 3600;
$memberTotal = MemberModel::where(1)
->count();
$memberToday = MemberModel::where(1)
->where('create_time', '>=', strtotime(date('Y-m-d')))
->count();
$memberYesterday = MemberModel::where(1)
->where('create_time', '>=', strtotime(date('Y-m-d')) - 24 * 3600)
->where('create_time', '<', strtotime(date('Y-m-d')))
->count();
$memberWeek = MemberModel::where(1)
->where('create_time', '>=', $weekTime)
->count();
$memberMonth = MemberModel::where(1)
->where('create_time', '>=', $monthTime)
->count();
/*$xinyueTotal = MemberQrcodeModel::where(1)
->where('status', MemberQrcodeModel::STATUS_SUCCESS)
->sum('select_xinyue_count');
$xinyueToday = MemberQrcodeModel::where(1)
->where('status', MemberQrcodeModel::STATUS_SUCCESS)
->where('create_time', '>=', strtotime(date('Y-m-d')))
->sum('select_xinyue_count');
$xinyueYesterday = MemberQrcodeModel::where(1)
->where('status', MemberQrcodeModel::STATUS_SUCCESS)
->where('create_time', '>=', strtotime(date('Y-m-d')) - 24 * 3600)
->where('create_time', '<', strtotime(date('Y-m-d')))
->sum('select_xinyue_count');
$xinyueWeek = MemberQrcodeModel::where(1)
->where('status', MemberQrcodeModel::STATUS_SUCCESS)
->where('create_time', '>=', $weekTime)
->sum('select_xinyue_count');
$xinyueMonth = MemberQrcodeModel::where(1)
->where('status', MemberQrcodeModel::STATUS_SUCCESS)
->where('create_time', '>=', $monthTime)
->sum('select_xinyue_count');*/
$xinyueTotal = StatisticsModel::where(1)
->sum('xinyue_select');
$xinyueToday = StatisticsModel::where(1)
->where('day', date('Ymd'))
->sum('xinyue_select');
$xinyueYesterday = StatisticsModel::where(1)
->where('day', date('Ymd', time() - 24 * 3600))
->sum('xinyue_select');
$xinyueWeek = StatisticsModel::where(1)
->where('day', '>=', date('Ymd', time() - 7 * 24 * 3600))
->sum('xinyue_select');
$xinyueMonth = StatisticsModel::where(1)
->where('day', '>=', date('Ymd', time() - 30 * 24 * 3600))
->sum('xinyue_select');
$moneyTotal = MemberMoneyModel::where(1)
->where('money', '>', 0)
->where('status', MemberMoneyModel::STATUS_SUCC)
->sum('money');
$moneyToday = MemberMoneyModel::where(1)
->where('money', '>', 0)
->where('status', MemberMoneyModel::STATUS_SUCC)
->where('create_time', '>=', strtotime(date('Y-m-d')))
->sum('money');
$moneyYesterday = MemberMoneyModel::where(1)
->where('money', '>', 0)
->where('status', MemberMoneyModel::STATUS_SUCC)
->where('create_time', '>=', strtotime(date('Y-m-d')) - 24 * 3600)
->where('create_time', '<', strtotime(date('Y-m-d')))
->sum('money');
$moneyWeek = MemberMoneyModel::where(1)
->where('money', '>', 0)
->where('status', MemberMoneyModel::STATUS_SUCC)
->where('create_time', '>=', $weekTime)
->sum('money');
$moneyMonth = MemberMoneyModel::where(1)
->where('money', '>', 0)
->where('status', MemberMoneyModel::STATUS_SUCC)
->where('create_time', '>=', $monthTime)
->sum('money');
$withdrawTotal = MemberMoneyModel::where(1)
->whereIn('type', MemberMoneyModel::withdrawTypes())
->whereIn('status', [MemberMoneyModel::STATUS_SUCC, MemberMoneyModel::STATUS_AWAIT])
->sum('money');
$withdrawToday = MemberMoneyModel::where(1)
->whereIn('type', MemberMoneyModel::withdrawTypes())
->whereIn('status', [MemberMoneyModel::STATUS_SUCC, MemberMoneyModel::STATUS_AWAIT])
->where('create_time', '>=', strtotime(date('Y-m-d')))
->sum('money');
$withdrawYesterday = MemberMoneyModel::where(1)
->whereIn('type', MemberMoneyModel::withdrawTypes())
->whereIn('status', [MemberMoneyModel::STATUS_SUCC, MemberMoneyModel::STATUS_AWAIT])
->where('create_time', '>=', strtotime(date('Y-m-d')) - 24 * 3600)
->where('create_time', '<', strtotime(date('Y-m-d')))
->sum('money');
$withdrawWeek = MemberMoneyModel::where(1)
->whereIn('type', MemberMoneyModel::withdrawTypes())
->whereIn('status', [MemberMoneyModel::STATUS_SUCC, MemberMoneyModel::STATUS_AWAIT])
->where('create_time', '>=', $weekTime)
->sum('money');
$withdrawMonth = MemberMoneyModel::where(1)
->whereIn('type', MemberMoneyModel::withdrawTypes())
->whereIn('status', [MemberMoneyModel::STATUS_SUCC, MemberMoneyModel::STATUS_AWAIT])
->where('create_time', '>=', $monthTime)
->sum('money');
$remitTotal = MemberWithdrawModel::where(1)
//->whereIn('type', MemberMoneyModel::withdrawTypes())
->whereIn('status', [MemberWithdrawModel::STATUS_SUCC, MemberWithdrawModel::STATUS_AUTO_SUCC])
->sum('money');
$remitToday = MemberWithdrawModel::where(1)
//->whereIn('type', MemberMoneyModel::withdrawTypes())
->whereIn('status', [MemberWithdrawModel::STATUS_SUCC, MemberWithdrawModel::STATUS_AUTO_SUCC])
->where('verify_time', '>=', strtotime(date('Y-m-d')))
->sum('money');
$remitYesterday = MemberWithdrawModel::where(1)
//->whereIn('type', MemberMoneyModel::withdrawTypes())
->whereIn('status', [MemberWithdrawModel::STATUS_SUCC, MemberWithdrawModel::STATUS_AUTO_SUCC])
->where('verify_time', '>=', strtotime(date('Y-m-d')) - 24 * 3600)
->where('verify_time', '<', strtotime(date('Y-m-d')))
->sum('money');
$remitWeek = MemberWithdrawModel::where(1)
//->whereIn('type', MemberMoneyModel::withdrawTypes())
->whereIn('status', [MemberWithdrawModel::STATUS_SUCC, MemberWithdrawModel::STATUS_AUTO_SUCC])
->where('verify_time', '>=', $weekTime)
->sum('money');
$remitMonth = MemberWithdrawModel::where(1)
//->whereIn('type', MemberMoneyModel::withdrawTypes())
->whereIn('status', [MemberWithdrawModel::STATUS_SUCC, MemberWithdrawModel::STATUS_AUTO_SUCC])
->where('verify_time', '>=', $monthTime)
->sum('money');
return $this->jsonSucc([
'member_total' => $memberTotal,
'member_today' => $memberToday,
'member_yesterday' => $memberYesterday,
'member_week' => $memberWeek,
'member_month' => $memberMonth,
'xinyue_total' => $xinyueTotal,
'xinyue_today' => $xinyueToday,
'xinyue_yesterday' => $xinyueYesterday,
'xinyue_week' => $xinyueWeek,
'xinyue_month' => $xinyueMonth,
'money_total' => $moneyTotal,
'money_today' => $moneyToday,
'money_yesterday' => $moneyYesterday,
'money_week' => $moneyWeek,
'money_month' => $moneyMonth,
'withdraw' => round($moneyTotal + $withdrawTotal, 2),
'withdraw_total' => abs($withdrawTotal),
'withdraw_today' => abs($withdrawToday),
'withdraw_yesterday' => abs($withdrawYesterday),
'withdraw_week' => abs($withdrawWeek),
'withdraw_month' => abs($withdrawMonth),
'remit_total' => abs($remitTotal),
'remit_today' => abs($remitToday),
'remit_yesterday' => abs($remitYesterday),
'remit_week' => abs($remitWeek),
'remit_month' => abs($remitMonth),
'clone_total' => 'Loading...',
'clone_today' => '-',
'clone_today_nr' => '-',
'clone_today_r' => '-',
'clone_yesterday' => '-',
'clone_week' => '-',
'clone_month' => '-',
'clone_today_pay' => '-',
'clone_today_unpay' => '-',
]);
}
/**
* 获取克隆统计信息
*
* @return \think\response\Json
* @throws \think\Exception
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\ModelNotFoundException
* @throws \think\exception\DbException
*/
public function getClone() {
$cloneTotal = StatisticsModel::where(1)
->sum('xinyue_clone');
$cloneToday = StatisticsModel::where(1)
->where('day', date('Ymd'))
->sum('xinyue_clone');
$cloneTodayNr = StatisticsModel::where(1)
->where('day', date('Ymd'))
->sum('xinyue_clone_nr');
$cloneYesterday = StatisticsModel::where(1)
->where('day', date('Ymd', time() - 24 * 3600))
->sum('xinyue_clone');
$cloneWeek = StatisticsModel::where(1)
->where('day', '>=', date('Ymd', time() - 7 * 24 * 3600))
->sum('xinyue_clone');
$cloneMonth = StatisticsModel::where(1)
->where('day', '>=', date('Ymd', time() - 30 * 24 * 3600))
->sum('xinyue_clone');
$cloneTodayUnpay = CloneInfo::getCloneSuccUnpayByTime(strtotime(date('Ymd')), time());
$cloneTodayPay = CloneInfo::getCloneSuccPayByTime(strtotime(date('Ymd')), time());
return $this->jsonSucc([
'clone_total' => $cloneTotal,
'clone_today' => $cloneToday,
'clone_today_nr' => $cloneTodayNr,
'clone_today_r' => $cloneToday - $cloneTodayNr,
'clone_yesterday' => $cloneYesterday,
'clone_week' => $cloneWeek,
'clone_month' => $cloneMonth,
'clone_today_pay' => $cloneTodayPay,
'clone_today_unpay' => $cloneTodayUnpay,
]);
}
/**
* 获取日统计数据
*
* @param $day
* @return array
*/
protected function getDayData($day) {
$list = [];
foreach (StatisticsModel::where(1)
->where('day', date('Ymd', strtotime($day)))
->order('hour', 'DESC')
->select() as $model) {
$xinyueClone = $model->xinyue_clone;
$moneyTotal = $model->money_clone
+ $model->money_invite1
+ $model->money_invite2
+ $model->money_jm
+ $model->money_point;
$scan1CloneCount = $model->scan1_clone_count;
$scan2CloneCount = $model->scan2_clone_count;
$scan3CloneCount = $model->scan3_clone_count;
$list[] = array_merge($model->toArray(), [
'day' => date('Y年m月d日', strtotime($model->day)),
'hour' => $model->hour . '时',
'reward_price' => $xinyueClone > 0 ? round($moneyTotal / $xinyueClone, 2) : 0,
'scan1_price' => $scan1CloneCount > 0 ? round($model->scan1_money / $scan1CloneCount, 2) : 0,
'scan2_price' => $scan2CloneCount > 0 ? round($model->scan2_money / $scan2CloneCount, 2) : 0,
'scan3_price' => $scan3CloneCount > 0 ? round($model->scan3_money / $scan3CloneCount, 2) : 0,
]);
}
return $list;
}
/**
* 获取月份
*
* @return array
*/
protected function getMonths() {
$begin = 20240301;
$months = [];
$nowTime = time();
while ($nowTime >= strtotime($begin)) {
if (!isset($months[date('Ym', $nowTime)])) {
$months[date('Ym', $nowTime)] = date('Y年m月', $nowTime);
}
$nowTime -= 24 * 3600;
}
return $months;
}
/**
* 获取月统计数据
*
* @param $month
* @return array|\think\response\Json
*/
protected function getMonthData($month) {
$time = strtotime($month . '01');
if ($time === FALSE) {
return $this->jsonFail('月份错误');
}
$today = date('Ymd');
$days = [];
for ($i = date('t', $time); $i >= 1; $i --) {
$day = date('Ymd', strtotime(date('Y', $time) . '-' . date('m', $time) . '-' . $i));
if ($day <= $today) {
$days[] = $day;
}
}
$list = [];
foreach ($days as $day) {
$row = [
'day' => date('Y年m月d日', strtotime($day)),
'register' => 0,
'qr_number' => 0,
'qr_member' => 0,
'qrcode' => 0,
'qrcode_succ' => 0,
'clone_succ' => 0,
'clone_error' => 0,
'clone_timeout' => 0,
'xinyue_select' => 0,
'xinyue_clone' => 0,
'reward_clone' => 0,
'reward_invite1' => 0,
'reward_invite2' => 0,
'money_clone' => 0,
'money_invite1' => 0,
'money_invite2' => 0,
'money_jm' => 0,
'money_point' => 0,
'withdraw' => 0,
'withdraw_check' => 0,
'withdraw_money' => 0,
'withdraw_money_check' => 0,
'withdraw_succ' => 0,
'withdraw_succ_money' => 0,
'withdraw_fail' => 0,
'withdraw_fail_money' => 0,
'scan1_num' => 0,
'scan2_num' => 0,
'scan3_num' => 0,
'scan1_xinyue_count' => 0,
'scan2_xinyue_count' => 0,
'scan3_xinyue_count' => 0,
'scan1_clone_count' => 0,
'scan2_clone_count' => 0,
'scan3_clone_count' => 0,
'scan1_money' => 0,
'scan2_money' => 0,
'scan3_money' => 0,
'scan1_price' => 0,
'scan2_price' => 0,
'scan3_price' => 0,
'first_settlement_count' => 0,
'repeat_settlement_count' => 0,
'first_reward_money' => 0,
'repeat_reward_money' => 0,
'first_clone_succ' => 0,
'repeat_clone_succ' => 0,
'first_price' => 0,
'repeat_price' => 0,
];
foreach (StatisticsModel::where(1)
->where('day', $day)
->select() as $model) {
$row['register'] += $model->register;
$row['qr_number'] += $model->qr_number;
$row['qr_member'] += $model->qr_member;
$row['qrcode'] += $model->qrcode;
$row['qrcode_succ'] += $model->qrcode_succ;
$row['clone_succ'] += $model->clone_succ;
$row['clone_error'] += $model->clone_error;
$row['clone_timeout'] += $model->clone_timeout;
$row['xinyue_select'] += $model->xinyue_select;
$row['xinyue_clone'] += $model->xinyue_clone;
$row['reward_clone'] += $model->reward_clone;
$row['reward_invite1'] += $model->reward_invite1;
$row['reward_invite2'] += $model->reward_invite2;
$row['money_clone'] += $model->money_clone;
$row['money_invite1'] += $model->money_invite1;
$row['money_invite2'] += $model->money_invite2;
$row['money_jm'] += $model->money_jm;
$row['money_point'] += $model->money_point;
$row['withdraw'] += $model->withdraw;
$row['withdraw_check'] += $model->withdraw_check;
$row['withdraw_money'] += $model->withdraw_money;
$row['withdraw_money_check'] += $model->withdraw_money_check;
$row['withdraw_succ'] += $model->withdraw_succ;
$row['withdraw_succ_money'] += $model->withdraw_succ_money;
$row['withdraw_fail'] += $model->withdraw_fail;
$row['withdraw_fail_money'] += $model->withdraw_fail_money;
$row['first_settlement_count'] += $model->first_settlement_count;
$row['repeat_settlement_count'] += $model->repeat_settlement_count;
$row['first_reward_money'] += $model->first_reward_money;
$row['repeat_reward_money'] += $model->repeat_reward_money;
$row['first_clone_succ'] += $model->first_clone_succ;
$row['repeat_clone_succ'] += $model->repeat_clone_succ;
/*$row['scan1_num'] += $model->scan1_num;
$row['scan2_num'] += $model->scan2_num;
$row['scan3_num'] += $model->scan3_num;
$row['scan1_xinyue_count'] += $model->scan1_xinyue_count;
$row['scan2_xinyue_count'] += $model->scan2_xinyue_count;
$row['scan3_xinyue_count'] += $model->scan3_xinyue_count;
$row['scan1_clone_count'] += $model->scan1_clone_count;
$row['scan2_clone_count'] += $model->scan2_clone_count;
$row['scan3_clone_count'] += $model->scan3_clone_count;
$row['scan1_money'] += $model->scan1_money;
$row['scan2_money'] += $model->scan2_money;
$row['scan3_money'] += $model->scan3_money;*/
}
$xinyueClone = $row['xinyue_clone'];
$moneyTotal = $row['money_clone']
+ $row['money_invite1']
+ $row['money_invite2']
+ $row['money_jm']
+ $row['money_point'];
//$scan1CloneCount = $row['scan1_clone_count'];
//$scan2CloneCount = $row['scan2_clone_count'];
//$scan3CloneCount = $row['scan3_clone_count'];
$row['reward_price'] = $xinyueClone > 0 ? round($moneyTotal / $xinyueClone, 2) : 0;
//$row['scan1_price'] = $scan1CloneCount > 0 ? round($row['scan1_money'] / $scan1CloneCount, 2) : 0;
//$row['scan2_price'] = $scan2CloneCount > 0 ? round($row['scan2_money'] / $scan2CloneCount, 2) : 0;
//$row['scan3_price'] = $scan3CloneCount > 0 ? round($row['scan3_money'] / $scan3CloneCount, 2) : 0;
$row['first_reward_money'] = round($row['first_reward_money'], 2);
$row['repeat_reward_money'] = round($row['repeat_reward_money'], 2);
if ($row['first_clone_succ'] > 0) {
$row['first_price'] = round($row['first_reward_money'] / $row['first_clone_succ'], 2);
}
if ($row['repeat_clone_succ'] > 0) {
$row['repeat_price'] = round($row['repeat_reward_money'] / $row['repeat_clone_succ'], 2);
}
$list[] = $row;
}
return $list;
}
public function scan() {
$time = time();// - 24 * 3600;
$ids0 = MemberQrcodeModel::where(1)
->field('id')
->where('create_time', '>=', strtotime(date('Ymd', $time)))
->where('status', MemberQrcodeModel::STATUS_SUCCESS)
->where('settlement_count1', '>', 0)
->select()
->column('id');
$ids1 = MemberQrcodeModel::where(1)
->field('id')
->where('create_time', '>=', strtotime(date('Ymd', $time)))
->where('status', MemberQrcodeModel::STATUS_SUCCESS)
->where('settlement_count2', '>', 0)
->select()
->column('id');
$money0 = 0;
$price0 = 0;
$xinyue0 = 0;
$cloneSucc0 = 0;
$cloneFailRate0 = 0;
if (!empty($ids0)) {
$money0 = MemberQrcodeModel::where(1)
->field('reward_price * reward_count AS reward_money0')
->whereIn('id', $ids0)
->select()
->reduce(function ($money, $item) {
return $money + $item['reward_money0'];
});
$xinyue0 = MemberQrcodeModel::where(1)
->whereIn('id', $ids0)
->sum('settlement_count1');
$qrIds0 = MemberQrcodeModel::where(1)
->field('qr_id')
->whereIn('id', $ids0)
->where('qr_id', '>', 0)
->select()
->column('qr_id');
if (!empty($qrIds0)) {
$cloneSucc0 = CloneInfo::getCloneSuccCount1ByQrIds($qrIds0);
}
if ($xinyue0 > 0) {
$cloneFailRate0 = round(($xinyue0 - $cloneSucc0) / $xinyue0, 4) * 100;
}
}
$money1 = 0;
$price1 = 0;
$xinyue1 = 0;
$cloneSucc1 = 0;
$cloneFailRate1 = 0;
if (!empty($ids1)) {
$money1 = MemberQrcodeModel::where(1)
->field('reward_price2 * reward_count2 AS reward_money0')
->whereIn('id', $ids1)
->select()
->reduce(function ($money, $item) {
return $money + $item['reward_money0'];
});
$xinyue1 = MemberQrcodeModel::where(1)
->whereIn('id', $ids1)
->sum('settlement_count2');
if ($cloneSucc1 > 0) {
$price1 = floatval(round($money1 / $cloneSucc1, 2));
}
$qrIds1 = MemberQrcodeModel::where(1)
->field('qr_id')
->whereIn('id', $ids1)
->where('qr_id', '>', 0)
->select()
->column('qr_id');
if (!empty($qrIds1)) {
$cloneSucc1 = CloneInfo::getCloneSuccCount2ByQrIds($qrIds1);
}
if ($xinyue1 > 0) {
$cloneFailRate1 = round(($xinyue1 - $cloneSucc1) / $xinyue1, 4) * 100;
}
}
$total = floatval(MemberMoneyModel::where(1)
->where('create_time', '>=', strtotime(date('Ymd', $time)))
->whereIn('type', MemberMoneyModel::rewardTypes())
->sum('money'));
Utils::allocNumber($total, $money0, $money1);
if ($cloneSucc0 > 0) {
$price0 = floatval(round($money0 / $cloneSucc0, 2));
}
if ($cloneSucc1 > 0) {
$price1 = floatval(round($money1 / $cloneSucc1, 2));
}
return $this->jsonSucc([
'scan0' => [
'num' => count($ids0),
'money' => floatval(round($money0, 2)),
'price' => $price0,
'xinyue' => $xinyue0,
'clone_succ' => $cloneSucc0,
'rate_clone_fail' => $cloneFailRate0,
],
'scan1' => [
'num' => count($ids1),
'money' => floatval(round($money1, 2)),
'price' => $price1,
'xinyue' => $xinyue1,
'clone_succ' => $cloneSucc1,
'rate_clone_fail' => $cloneFailRate1,
],
]);
}
}

View File

@@ -1,228 +0,0 @@
<?php
namespace app\backend\controller;
use app\common\model\DeviceModel;
use app\common\model\LogModel;
use app\common\model\TaskDetailModel;
use app\common\model\TaskModel;
class TaskController extends BaseLoginController {
/**
* 获取列表
*
* @return \think\response\Json
*/
public function index() {
$deviceId = intval($this->request->param('device_id'));
$type = trim($this->request->param('type'));
$runType = trim($this->request->param('run_type'));
$status = trim($this->request->param('status'));
$keywords = trim($this->request->param('keywords'));
$pageNo = intval($this->request->param('page'));
$pageSize = intval($this->request->param('pageSize'));
if ($pageNo <= 0) {
$pageNo = 1;
}
if ($pageSize <= 0) {
$pageSize = 30;
}
$query = TaskModel::where(1)
->alias('t1')
->field('t1.*')
->leftJoin(
DeviceModel::where(1)->getTable() . ' t2',
't2.id = t1.device_id')
->where('t1.is_deleted', TaskModel::IS_DELETED_NO);
if (!empty($deviceId)) {
$query->where('t1.device_id', $deviceId);
}
if (isset(TaskModel::typeAssoc()[$type])) {
$query->where('t1.type', $type);
}
if (isset(TaskModel::statusAssoc()[$status])) {
$query->where('t1.status', $status);
}
if (isset(TaskModel::runTypeAssoc()[$runType])) {
$query->where('t1.run_type', $runType);
}
if (!empty($keywords)) {
$query->whereLike('t2.number', '%' . $keywords . '%');
}
$totalCount = $query->count();
$pageCount = $totalCount > 0 ? ceil($totalCount / $pageSize) : 1;
if ($pageNo > $pageCount) {
$pageNo = $pageCount;
}
$query->order('t1.id', 'DESC');
$query->limit(($pageNo - 1) * $pageSize, $pageSize);
$list = [];
foreach ($query->select() as $model) {
$device = $model->device();
$list[] = array_merge($model->toArray(), [
'device_number' => $device ? $device->number : '',
'device_name' => $device ? $device->name : '',
'device_online' => ($device AND $device->isOnline()) ? '在线' : '离线',
'type_name' => TaskModel::typeAssoc()[$model->type],
'run_type_name' => TaskModel::runTypeAssoc()[$model->run_type],
'status_name' => TaskModel::statusAssoc()[$model->status],
]);
}
return $this->jsonSucc([
'list' => $list,
'page' => $pageNo,
'pageCount' => $pageCount,
'totalCount' => $totalCount,
'platforms' => $this->assocToList(TaskModel::platformAssoc()),
'types' => $this->assocToList(TaskModel::typeAssoc()),
'runTypes' => $this->assocToList(TaskModel::runTypeAssoc()),
'statuses' => $this->assocToList(TaskModel::statusAssoc()),
]);
}
/**
* 日志
*
* @return \think\response\Json
*/
public function log() {
$id = intval($this->request->param('id'));
if (empty($id)) {
return $this->jsonFail('参数错误');
}
$detail = TaskDetailModel::where(1)
->where('task_id', $id)
->order('id', 'DESC')
->find();
if (empty($detail)) {
return $this->jsonSucc([]);
}
$list = [];
foreach (LogModel::where(1)
->where('task_id', $detail->id)
->order('id', 'ASC')
->select() as $model) {
$list[] = [
'id' => $model->id,
'type' => $model->type,
'message' => $model->message,
'create_time' => $model->create_time,
];
}
return $this->jsonSucc($list);
}
/**
* 保存
*
* @return \think\response\Json
*/
public function save() {
$devices = $this->request->param('devices');
$platform = trim($this->request->param('platform'));
$type = trim($this->request->param('type'));
$runType = trim($this->request->param('run_type'));
$runTime = trim($this->request->param('run_time'));
if (empty($devices)
OR !is_array($devices)
OR !isset(TaskModel::platformAssoc()[$platform])
OR !isset(TaskModel::typeAssoc()[$type])
OR !isset(TaskModel::runTypeAssoc()[$runType])) {
return $this->jsonFail('参数错误');
}
$timeTypes = [TaskModel::RUN_TYPE_TIMER, TaskModel::RUN_TYPE_DAILY];
if (in_array($runType, $timeTypes)
AND empty($runTime)) {
return $this->jsonFail('参数错误');
}
$params = call_user_func_array(TaskModel::taskClasses()[$type] . '::params', [$this->request]);
if (is_null($params)) {
return $this->jsonFail('参数错误');
}
foreach ($devices as $deviceId) {
$model = new TaskModel();
$model->platform = $platform;
$model->type = $type;
$model->device_id = $deviceId;
$model->params = $params;
$model->run_type = $runType;
$model->run_time = in_array($runType, $timeTypes) ? $runTime : '';
$model->save();
}
return $this->jsonSucc();
}
/**
* 删除
*
* @return \think\response\Json
*/
public function delete() {
$id = intval($this->request->param('id'));
if (empty($id)) {
return $this->jsonFail('参数错误');
}
$model = TaskModel::get($id);
if (empty($model)) {
return $this->jsonFail('对象未找到');
}
TaskDetailModel::where(1)
->where('task_id', $model->id)
->update([
'is_deleted' => TaskModel::IS_DELETED_YES,
]);
$model->is_deleted = TaskModel::IS_DELETED_YES;
$model->save();
return $this->jsonSucc();
}
/**
* 批量删除
*
* @return \think\response\Json
*/
public function batchDelete() {
$ids = $this->request->param('ids');
if (empty($ids) OR !is_array($ids)) {
return $this->jsonFail('参数错误');
}
TaskDetailModel::where(1)
->whereIn('task_id', $ids)
->update([
'is_deleted' => TaskModel::IS_DELETED_YES,
]);
TaskModel::where(1)
->whereIn('id', $ids)
->update([
'is_deleted' => TaskModel::IS_DELETED_YES,
]);
return $this->jsonSucc();
}
/**
* 获取执行方式
*
* @return \think\response\Json
*/
public function runTypeAssoc() {
return $this->jsonSucc($this->assocToList(TaskModel::runTypeAssoc()));
}
}

View File

@@ -1,54 +0,0 @@
<?php
namespace app\backend\controller;
class UploadController extends BaseLoginController {
// 上传图片
public function index() {
if (!empty($_FILES)
AND !empty($_FILES['file'])
AND is_uploaded_file($_FILES['file']['tmp_name'])) {
$ext = strtolower(trim(pathinfo($_FILES['file']['name'], PATHINFO_EXTENSION)));
$dir = ROOT_PATH . DS . 'public' . DS;
$path = 'upload/' . $ext . '/' . date('Y-m-d') . '/' . time() . '-' . uniqid() . '.' . $ext;
if (is_dir(dirname($dir . $path)) OR @mkdir(dirname($dir . $path), 0777, TRUE)) {
if (move_uploaded_file($_FILES['file']['tmp_name'], $dir . $path)) {
return json([
'name' => $_FILES['file']['name'],
'path' => $path,
'url' => $this->absoluteUrl($path),
], 200);
}
}
}
if (!empty($_FILES)) {
return json([
'url' => '',
], 500);
}
}
public function editor() {
if (!empty($_FILES)
AND !empty($_FILES['upload'])
AND is_uploaded_file($_FILES['upload']['tmp_name'])) {
$ext = strtolower(trim(pathinfo($_FILES['upload']['name'], PATHINFO_EXTENSION)));
$dir = ROOT_PATH . DS . 'public' . DS;
$path = 'upload/' . $ext . '/' . date('Y-m-d') . '/' . time() . '-' . uniqid() . '.' . $ext;
if (is_dir(dirname($dir . $path)) OR @mkdir(dirname($dir . $path), 0777, TRUE)) {
if (move_uploaded_file($_FILES['upload']['tmp_name'], $dir . $path)) {
return json([
'uploaded' => true,
'url' => $this->absoluteUrl($path),
]);
}
}
}
return json([
'uploaded' => false,
'url' => '',
]);
}
}

View File

@@ -1,114 +0,0 @@
<?php
namespace app\backend\controller;
use app\common\model\UserModel;
use app\common\model\UserTokenModel;
class UserController extends BaseController {
/**
* 登录
*
* @return \think\response\Json
*/
public function login() {
$username = trim($this->request->param('username'));
$password = trim($this->request->param('password'));
if (empty($username)
OR empty($password)) {
return $this->jsonFail('参数错误');
}
$user = UserModel::get([
'username' => $username,
'password' => md5($password),
]);
if (empty($user)) {
return $this->jsonFail('账号/密码错误');
}
if ($user->status != UserModel::STATUS_ACTIVE) {
return $this->jsonFail('账号不可用');
}
$tokenModel = new UserTokenModel();
$tokenModel->token = md5(time() . uniqid());
$tokenModel->user_id = $user->id;
if ($tokenModel->save()) {
$user->login_time = time();
$user->login_count += 1;
$user->login_ip = $this->request->ip();
if ($user->save()) {
return $this->jsonSucc([
'logged' => TRUE,
'token' => $tokenModel->token,
'token_expired' => 365 * 24 * 3600,
'user' => $this->userJson($user),
]);
}
}
return $this->jsonFail('登录失败');
}
/**
* 获取当前登录用户
*
* @return \think\response\Json
*/
public function get() {
if (!empty($this->userModel)) {
return $this->jsonSucc([
'logged' => TRUE,
'user' => $this->userJson($this->userModel),
]);
} else {
return $this->jsonSucc([
'logged' => FALSE,
'user' => NULL,
]);
}
}
/**
* 设置密码
*
* @return \think\response\Json
*/
public function password() {
if (empty($this->userModel)) {
return $this->jsonFail('未登录');
}
$oldPassword = trim($this->request->param('oldPassword'));
$newPassword = trim($this->request->param('newPassword'));
if (empty($oldPassword)
OR empty($newPassword)
OR strlen($newPassword) < 6
OR strlen($newPassword) > 16) {
return $this->jsonFail('参数错误');
}
if (md5($oldPassword) != $this->userModel->password) {
return $this->jsonFail('原密码输入错误');
}
$this->userModel->password = md5($newPassword);
$this->userModel->save();
return $this->jsonSucc([]);
}
/**
* 登出
*
* @return \think\response\Json
*/
public function logout() {
if (!empty($this->tokenModel)) {
$this->tokenModel->delete();
}
return $this->jsonSucc([]);
}
}

View File

@@ -1,85 +0,0 @@
<?php
namespace app\backend\controller;
use app\common\model\XianyuModel;
use think\db\Query;
class XianyuController extends BaseLoginController {
/**
* 闲鱼列表
*
* @return \think\response\Json
*/
public function index() {
$keywords = trim($this->request->param('keywords'));
$pageNo = intval($this->request->param('page'));
$pageSize = intval($this->request->param('pageSize'));
if ($pageNo <= 0) {
$pageNo = 1;
}
if ($pageSize <= 0) {
$pageSize = 30;
}
$query = XianyuModel::where(1);
if (!empty($keywords)) {
$query->where(function (Query $q) use ($keywords) {
$q->whereLike('username', '%' . $keywords . '%', 'OR');
$q->whereLike('nickname', '%' . $keywords . '%', 'OR');
$q->whereLike('remark', '%' . $keywords . '%', 'OR');
});
}
$totalCount = $query->count();
$pageCount = $totalCount > 0 ? ceil($totalCount / $pageSize) : 1;
if ($pageNo > $pageCount) {
$pageNo = $pageCount;
}
$query->order('id', 'DESC');
$query->limit(($pageNo - 1) * $pageSize, $pageSize);
$list = [];
foreach ($query->select() as $model) {
$list[] = array_merge($model->toArray(), [
]);
}
return $this->jsonSucc([
'list' => $list,
'page' => $pageNo,
'pageCount' => $pageCount,
'totalCount' => $totalCount,
]);
}
public function update() {
}
/**
* 备注
*
* @return \think\response\Json
*/
public function remark() {
$id = intval($this->request->param('id'));
$remark = trim($this->request->param('remark'));
if (empty($id)) {
return $this->jsonFail('参数错误');
}
$model = XianyuModel::get($id);
if (empty($model)) {
return $this->jsonFail('对象未找到');
}
$model->remark = $remark;
$model->save();
return $this->jsonSucc();
}
}

View File

@@ -1,73 +0,0 @@
<?php
namespace app\backend\controller;
use app\common\model\DeviceModel;
use app\common\model\XianyuProductModel;
use app\common\model\XianyuModel;
use think\db\Query;
class XianyuProductController extends BaseLoginController {
/**
* 列表
*
* @return \think\response\Json
*/
public function index() {
$username = trim($this->request->param('username'));
$onSale = trim($this->request->param('on_sale'));
$keywords = trim($this->request->param('keywords'));
$pageNo = intval($this->request->param('page'));
$pageSize = intval($this->request->param('pageSize'));
if ($pageNo <= 0) {
$pageNo = 1;
}
if ($pageSize <= 0) {
$pageSize = 30;
}
$query = XianyuProductModel::where(1);
if (!empty($username)) {
$query->where('username', $username);
}
if (isset(XianyuProductModel::onSaleAssoc()[$onSale])) {
$query->where('on_sale', $onSale);
}
if (!empty($keywords)) {
$query->where(function (Query $q) use ($keywords) {
$q->whereLike('title', '%' . $keywords . '%', 'OR');
});
}
$totalCount = $query->count();
$pageCount = $totalCount > 0 ? ceil($totalCount / $pageSize) : 1;
if ($pageNo > $pageCount) {
$pageNo = $pageCount;
}
$query->order('id', 'DESC');
$query->limit(($pageNo - 1) * $pageSize, $pageSize);
$list = [];
foreach ($query->select() as $model) {
$device = DeviceModel::get($model->device_id);
$xianyu = XianyuModel::get(['username' => $model->username]);
$list[] = array_merge($model->toArray(), [
'device_number' => $device ? $device->number : '',
'on_sale_name' => XianyuProductModel::onSaleAssoc()[$model->on_sale],
'nickname' => $xianyu ? $xianyu->nickname : '',
]);
}
return $this->jsonSucc([
'list' => $list,
'page' => $pageNo,
'pageCount' => $pageCount,
'totalCount' => $totalCount,
'xianyus' => $this->assocToList(XianyuModel::assoc()),
'onSales' => $this->assocToList(XianyuProductModel::onSaleAssoc()),
]);
}
}

View File

@@ -1,66 +0,0 @@
<?php
namespace app\backend\controller;
use app\common\model\DeviceModel;
use app\common\model\XianyuModel;
use app\common\model\XianyuShopModel;
use think\db\Query;
class XianyuShopController extends BaseLoginController {
/**
* 列表
*
* @return \think\response\Json
*/
public function index() {
$username = trim($this->request->param('username'));
$keywords = trim($this->request->param('keywords'));
$pageNo = intval($this->request->param('page'));
$pageSize = intval($this->request->param('pageSize'));
if ($pageNo <= 0) {
$pageNo = 1;
}
if ($pageSize <= 0) {
$pageSize = 30;
}
$query = XianyuShopModel::where(1);
if (!empty($username)) {
$query->where('username', $username);
}
if (!empty($keywords)) {
$query->where(function (Query $q) use ($keywords) {
//$q->whereLike('title', '%' . $keywords . '%', 'OR');
});
}
$totalCount = $query->count();
$pageCount = $totalCount > 0 ? ceil($totalCount / $pageSize) : 1;
if ($pageNo > $pageCount) {
$pageNo = $pageCount;
}
$query->order('id', 'DESC');
$query->limit(($pageNo - 1) * $pageSize, $pageSize);
$list = [];
foreach ($query->select() as $model) {
$device = DeviceModel::get($model->device_id);
$xianyu = XianyuModel::get(['username' => $model->username]);
$list[] = array_merge($model->toArray(), [
'device_number' => $device ? $device->number : '',
'nickname' => $xianyu ? $xianyu->nickname : '',
]);
}
return $this->jsonSucc([
'list' => $list,
'page' => $pageNo,
'pageCount' => $pageCount,
'totalCount' => $totalCount,
'xianyus' => $this->assocToList(XianyuModel::assoc()),
]);
}
}

View File

@@ -1,73 +0,0 @@
<?php
namespace app\backend\controller;
use app\common\model\DeviceModel;
use app\common\model\XianyuModel;
use app\common\model\XianyuShopProductModel;
use think\db\Query;
class XianyuShopProductController extends BaseLoginController {
/**
* 列表
*
* @return \think\response\Json
*/
public function index() {
$username = trim($this->request->param('username'));
$pay = trim($this->request->param('pay'));
$keywords = trim($this->request->param('keywords'));
$pageNo = intval($this->request->param('page'));
$pageSize = intval($this->request->param('pageSize'));
if ($pageNo <= 0) {
$pageNo = 1;
}
if ($pageSize <= 0) {
$pageSize = 30;
}
$query = XianyuShopProductModel::where(1);
if (!empty($username)) {
$query->where('username', $username);
}
if (isset(XianyuShopProductModel::payAssoc()[$pay])) {
$query->where('pay', $pay);
}
if (!empty($keywords)) {
$query->where(function (Query $q) use ($keywords) {
$q->whereLike('title', '%' . $keywords . '%', 'OR');
});
}
$totalCount = $query->count();
$pageCount = $totalCount > 0 ? ceil($totalCount / $pageSize) : 1;
if ($pageNo > $pageCount) {
$pageNo = $pageCount;
}
$query->order('id', 'DESC');
$query->limit(($pageNo - 1) * $pageSize, $pageSize);
$list = [];
foreach ($query->select() as $model) {
$device = DeviceModel::get($model->device_id);
$xianyu = XianyuModel::get(['username' => $model->username]);
$list[] = array_merge($model->toArray(), [
'device_number' => $device ? $device->number : '',
'pay_name' => XianyuShopProductModel::payAssoc()[$model->pay],
'nickname' => $xianyu ? $xianyu->nickname : '',
]);
}
return $this->jsonSucc([
'list' => $list,
'page' => $pageNo,
'pageCount' => $pageCount,
'totalCount' => $totalCount,
'xianyus' => $this->assocToList(XianyuModel::assoc()),
'pays' => $this->assocToList(XianyuShopProductModel::payAssoc()),
]);
}
}

View File

@@ -1,54 +0,0 @@
<?php
namespace app\backend\model;
class TimeRangeModel {
/**
* 获取年
*
* @return array
*/
static public function getYears() {
$years = [];
$timeS = strtotime(date('Y') . '-01-01');
$timeE = strtotime((date('Y', $timeS) + 1) . '-01-01') - 1;
for ($i = 0; $i > -3; $i --) {
$years[] = [
'key' => $i,
'label' => date('Y 年', $timeS),
'timeS' => $timeS,
'timeE' => $timeE,
];
$timeE = $timeS - 1;
$timeS = strtotime(date('Y', $timeE) . '-01-01');
}
return $years;
}
/**
* 获取月份
*
* @return array
*/
static public function getMonths() {
$months = [];
$timeS = strtotime(date('Y-m') . '-01');
$timeE = $timeS + date('t', $timeS) * 24 * 3600 - 1;
for ($i = 0; $i > -24; $i --) {
$months[] = [
'key' => $i,
'label' => date('Y 年 m 月', $timeS) . ' (' . date('Y.m.d', $timeS) . '-' . date('Y.m.d', $timeE) . ')',
'timeS' => $timeS,
'timeE' => $timeE,
];
$timeS = strtotime(date('Y-m', $timeS - 1) . '-01');
$timeE = $timeS + date('t', $timeS) * 24 * 3600 - 1;
}
return $months;
}
}

View File

@@ -0,0 +1,50 @@
<?php
namespace app\command;
use think\console\Command;
use think\console\Input;
use think\console\Output;
use think\Db;
use think\facade\Config;
class InitDatabase extends Command
{
protected function configure()
{
$this->setName('init:database')
->setDescription('初始化数据库,创建必要的表结构');
}
protected function execute(Input $input, Output $output)
{
$output->writeln('开始初始化数据库...');
try {
// 读取SQL文件
$sqlFile = app()->getAppPath() . 'common/database/tk_users.sql';
if (!file_exists($sqlFile)) {
$output->error('SQL文件不存在: ' . $sqlFile);
return;
}
$sql = file_get_contents($sqlFile);
// 分割SQL语句
$sqlArr = explode(';', $sql);
// 执行SQL语句
foreach ($sqlArr as $statement) {
$statement = trim($statement);
if ($statement) {
Db::execute($statement);
$output->writeln('执行SQL: ' . mb_substr($statement, 0, 100) . '...');
}
}
$output->info('数据库初始化完成!');
} catch (\Exception $e) {
$output->error('数据库初始化失败: ' . $e->getMessage());
}
}
}

View File

@@ -15,11 +15,23 @@ Route::get('api/test', function() {
]); ]);
}); });
// 定义RESTful风格的API路由 // 数据库初始化路由
Route::post('api/auth/login', 'app\\common\\controller\\Auth@login'); // 登录接口 Route::get('api/database/init', 'app\\common\\controller\\Database@init');
Route::get('api/database/test', 'app\\common\\controller\\Database@test');
Route::get('api/database/update-password', 'app\\common\\controller\\Database@updatePassword');
Route::get('api/database/debug-password', 'app\\common\\controller\\Database@debugPassword');
Route::get('api/database/reset-password', 'app\\common\\controller\\Database@resetPassword');
// 需要JWT认证的接口 // 定义RESTful风格的API路由 - 认证相关
Route::get('api/auth/info', 'app\\common\\controller\\Auth@info')->middleware(['jwt']); // 获取用户信息 Route::group('api/auth', function () {
Route::post('api/auth/refresh', 'app\\common\\controller\\Auth@refresh')->middleware(['jwt']); // 刷新令牌 // 无需认证的接口
Route::post('login', 'app\\common\\controller\\Auth@login'); // 账号密码登录
Route::post('mobile-login', 'app\\common\\controller\\Auth@mobileLogin'); // 手机号验证码登录
Route::post('code', 'app\\common\\controller\\Auth@sendCode'); // 发送验证码
// 需要JWT认证的接口
Route::get('info', 'app\\common\\controller\\Auth@info')->middleware(['jwt']); // 获取用户信息
Route::post('refresh', 'app\\common\\controller\\Auth@refresh')->middleware(['jwt']); // 刷新令牌
});
return []; return [];

View File

@@ -53,7 +53,7 @@ class Auth extends Controller
public function login() public function login()
{ {
// 获取登录参数 // 获取登录参数
$params = Request::only(['username', 'password']); $params = Request::only(['username', 'password', 'is_encrypted']);
// 参数验证 // 参数验证
$validate = validate('common/Auth'); $validate = validate('common/Auth');
@@ -62,11 +62,15 @@ class Auth extends Controller
} }
try { try {
// 判断密码是否已加密
$isEncrypted = isset($params['is_encrypted']) && $params['is_encrypted'] === true;
// 调用登录服务 // 调用登录服务
$result = $this->authService->login( $result = $this->authService->login(
$params['username'], $params['username'],
$params['password'], $params['password'],
Request::ip() Request::ip(),
$isEncrypted
); );
return ResponseHelper::success($result, '登录成功'); return ResponseHelper::success($result, '登录成功');
} catch (\Exception $e) { } catch (\Exception $e) {
@@ -74,6 +78,65 @@ class Auth extends Controller
} }
} }
/**
* 手机号验证码登录
* @return \think\response\Json
*/
public function mobileLogin()
{
// 获取登录参数
$params = Request::only(['mobile', 'code', 'is_encrypted']);
// 参数验证
$validate = validate('common/Auth');
if (!$validate->scene('mobile_login')->check($params)) {
return ResponseHelper::error($validate->getError());
}
try {
// 判断验证码是否已加密
$isEncrypted = isset($params['is_encrypted']) && $params['is_encrypted'] === true;
// 调用手机号登录服务
$result = $this->authService->mobileLogin(
$params['mobile'],
$params['code'],
Request::ip(),
$isEncrypted
);
return ResponseHelper::success($result, '登录成功');
} catch (\Exception $e) {
return ResponseHelper::error($e->getMessage());
}
}
/**
* 发送验证码
* @return \think\response\Json
*/
public function sendCode()
{
// 获取参数
$params = Request::only(['mobile', 'type']);
// 参数验证
$validate = validate('common/Auth');
if (!$validate->scene('send_code')->check($params)) {
return ResponseHelper::error($validate->getError());
}
try {
// 调用发送验证码服务
$result = $this->authService->sendLoginCode(
$params['mobile'],
$params['type']
);
return ResponseHelper::success($result, '验证码发送成功');
} catch (\Exception $e) {
return ResponseHelper::error($e->getMessage());
}
}
/** /**
* 获取用户信息 * 获取用户信息
* @return \think\response\Json * @return \think\response\Json

View File

@@ -0,0 +1,196 @@
<?php
namespace app\common\controller;
use think\Controller;
use think\Db;
use think\facade\Config;
use app\common\helper\ResponseHelper;
/**
* 数据库控制器
* 用于初始化数据库
*/
class Database extends Controller
{
/**
* 初始化数据库
* @return \think\response\Json
*/
public function init()
{
try {
// 创建表结构
$createTableSql = "
CREATE TABLE IF NOT EXISTS `tk_users` (
`id` int(10) UNSIGNED NOT NULL AUTO_INCREMENT COMMENT 'ID',
`username` varchar(50) NOT NULL COMMENT '用户名',
`password` varchar(60) NOT NULL COMMENT '密码',
`mobile` varchar(11) DEFAULT NULL COMMENT '登录手机号',
`identity_id` int(10) DEFAULT NULL COMMENT '身份信息',
`auth_id` int(10) DEFAULT NULL COMMENT '权限id',
`create_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`update_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '修改时间',
`delete_at` timestamp NULL DEFAULT NULL COMMENT '删除时间',
PRIMARY KEY (`id`),
UNIQUE KEY `idx_username` (`username`),
UNIQUE KEY `idx_mobile` (`mobile`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户表';
";
Db::execute($createTableSql);
// 检查是否已存在admin用户
$adminExists = Db::table('tk_users')->where('username', 'admin')->find();
if (!$adminExists) {
// 生成密码的加密值
$hashedPassword = password_hash('123456', PASSWORD_BCRYPT);
// 插入测试数据
$insertDataSql = "
INSERT INTO `tk_users` (`username`, `password`, `mobile`, `identity_id`, `auth_id`) VALUES
('admin', '{$hashedPassword}', '13800138000', 1, 1);
";
Db::execute($insertDataSql);
}
return ResponseHelper::success(null, '数据库初始化完成');
} catch (\Exception $e) {
return ResponseHelper::error('数据库初始化失败: ' . $e->getMessage());
}
}
/**
* 测试数据库连接和查询
* @return \think\response\Json
*/
public function test()
{
try {
// 查询用户表中的数据
$users = Db::table('tk_users')->select();
return ResponseHelper::success([
'count' => count($users),
'users' => $users
], '数据库查询成功');
} catch (\Exception $e) {
return ResponseHelper::error('数据库查询失败: ' . $e->getMessage());
}
}
/**
* 更新用户密码
* @param string $username 用户名
* @param string $password 新密码
* @return \think\response\Json
*/
public function updatePassword($username = 'admin', $password = '123456')
{
try {
// 生成密码的加密值
$hashedPassword = password_hash($password, PASSWORD_BCRYPT);
// 更新数据库中的用户密码
$result = Db::table('tk_users')
->where('username', $username)
->update(['password' => $hashedPassword]);
if ($result) {
return ResponseHelper::success([
'username' => $username,
'password' => $password,
'hashed_password' => $hashedPassword
], '密码更新成功');
} else {
return ResponseHelper::error('用户不存在或密码未更改');
}
} catch (\Exception $e) {
return ResponseHelper::error('密码更新失败: ' . $e->getMessage());
}
}
/**
* 调试密码验证
* @param string $username 用户名
* @param string $password 密码
* @return \think\response\Json
*/
public function debugPassword($username = 'admin', $password = '123456')
{
try {
// 查询用户
$user = Db::table('tk_users')->where('username', $username)->find();
if (!$user) {
return ResponseHelper::error('用户不存在');
}
// 生成新的密码哈希
$newHash = password_hash($password, PASSWORD_BCRYPT);
// 验证密码
$isValid = password_verify($password, $user['password']);
// 更新密码(确保使用正确的哈希算法)
if (!$isValid) {
Db::table('tk_users')
->where('username', $username)
->update(['password' => $newHash]);
}
return ResponseHelper::success([
'username' => $username,
'stored_hash' => $user['password'],
'new_hash' => $newHash,
'is_valid' => $isValid,
'password_info' => password_get_info($user['password'])
], '密码验证调试信息');
} catch (\Exception $e) {
return ResponseHelper::error('密码验证调试失败: ' . $e->getMessage());
}
}
/**
* 重置用户密码
* @param string $username 用户名
* @param string $password 新密码
* @return \think\response\Json
*/
public function resetPassword($username = 'admin', $password = '123456')
{
try {
// 查询用户
$user = Db::table('tk_users')->where('username', $username)->find();
if (!$user) {
return ResponseHelper::error('用户不存在');
}
// 使用正确的哈希算法生成密码
$hashedPassword = password_hash($password, PASSWORD_BCRYPT, ['cost' => 10]);
// 更新数据库中的用户密码
$result = Db::table('tk_users')
->where('username', $username)
->update(['password' => $hashedPassword]);
if ($result) {
// 验证密码是否正确
$isValid = password_verify($password, $hashedPassword);
return ResponseHelper::success([
'username' => $username,
'password' => $password,
'hashed_password' => $hashedPassword,
'is_valid' => $isValid
], '密码重置成功');
} else {
return ResponseHelper::error('密码重置失败');
}
} catch (\Exception $e) {
return ResponseHelper::error('密码重置失败: ' . $e->getMessage());
}
}
}

View File

@@ -0,0 +1,79 @@
<?php
use think\migration\Migrator;
use think\migration\db\Column;
class CreateUsersTable extends Migrator
{
/**
* 创建用户表
*/
public function up()
{
$table = $this->table('users', [
'engine' => 'InnoDB',
'comment' => '用户表',
'id' => 'id',
'signed' => false,
]);
$table->addColumn('username', 'string', [
'limit' => 50,
'null' => false,
'comment' => '用户名',
])
->addColumn('password', 'string', [
'limit' => 60,
'null' => false,
'comment' => '密码',
])
->addColumn('mobile', 'string', [
'limit' => 11,
'null' => true,
'comment' => '登录手机号',
])
->addColumn('identity_id', 'integer', [
'limit' => 10,
'null' => true,
'comment' => '身份信息',
])
->addColumn('auth_id', 'integer', [
'limit' => 10,
'null' => true,
'comment' => '权限id',
])
->addColumn('create_at', 'timestamp', [
'null' => false,
'default' => 'CURRENT_TIMESTAMP',
'comment' => '创建时间',
])
->addColumn('update_at', 'timestamp', [
'null' => false,
'default' => 'CURRENT_TIMESTAMP',
'update' => 'CURRENT_TIMESTAMP',
'comment' => '修改时间',
])
->addColumn('delete_at', 'timestamp', [
'null' => true,
'default' => null,
'comment' => '删除时间',
])
->addIndex(['username'], [
'unique' => true,
'name' => 'idx_username',
])
->addIndex(['mobile'], [
'unique' => true,
'name' => 'idx_mobile',
])
->create();
}
/**
* 删除用户表
*/
public function down()
{
$this->dropTable('users');
}
}

View File

@@ -0,0 +1,19 @@
-- 创建用户表
CREATE TABLE IF NOT EXISTS `tk_users` (
`id` int(10) UNSIGNED NOT NULL AUTO_INCREMENT COMMENT 'ID',
`username` varchar(50) NOT NULL COMMENT '用户名',
`password` varchar(60) NOT NULL COMMENT '密码',
`mobile` varchar(11) DEFAULT NULL COMMENT '登录手机号',
`identity_id` int(10) DEFAULT NULL COMMENT '身份信息',
`auth_id` int(10) DEFAULT NULL COMMENT '权限id',
`create_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`update_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '修改时间',
`delete_at` timestamp NULL DEFAULT NULL COMMENT '删除时间',
PRIMARY KEY (`id`),
UNIQUE KEY `idx_username` (`username`),
UNIQUE KEY `idx_mobile` (`mobile`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户表';
-- 插入测试数据
INSERT INTO `tk_users` (`username`, `password`, `mobile`, `identity_id`, `auth_id`) VALUES
('admin', '$2y$10$92IXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi', '13800138000', 1, 1); -- 密码为password

View File

@@ -2,14 +2,23 @@
namespace app\common\model; namespace app\common\model;
use think\Model; use think\Model;
use think\model\concern\SoftDelete;
class User extends Model class User extends Model
{ {
use SoftDelete;
/** /**
* 数据表名 * 数据表名
* @var string * @var string
*/ */
protected $table = 'user'; protected $table = 'tk_users';
/**
* 主键
* @var string
*/
protected $pk = 'id';
/** /**
* 自动写入时间戳 * 自动写入时间戳
@@ -21,39 +30,111 @@ class User extends Model
* 创建时间字段 * 创建时间字段
* @var string * @var string
*/ */
protected $createTime = 'create_time'; protected $createTime = 'create_at';
/** /**
* 更新时间字段 * 更新时间字段
* @var string * @var string
*/ */
protected $updateTime = 'update_time'; protected $updateTime = 'update_at';
/**
* 软删除字段
* @var string
*/
protected $deleteTime = 'delete_at';
/** /**
* 隐藏属性 * 隐藏属性
* @var array * @var array
*/ */
protected $hidden = ['password', 'delete_time']; protected $hidden = ['password', 'delete_at'];
/** /**
* 获取管理员用户信息 * 获取管理员用户信息
* @param string $username 用户名 * @param string $username 用户名
* @param string $password 密码 * @param string $password 密码(可能是加密后的)
* @param bool $isEncrypted 密码是否已加密
* @return array|null * @return array|null
*/ */
public static function getAdminUser($username, $password) public static function getAdminUser($username, $password, $isEncrypted = false)
{ {
// 目前使用固定账号,后续可改为数据库查询 // 查询用户
if ($username === 'admin' && $password === '123456') { $user = self::where('username', $username)->find();
return [
'id' => 1, if (!$user) {
'username' => 'admin', // 记录日志
'name' => '超级管理员', \think\facade\Log::info('用户不存在', ['username' => $username]);
'role' => 'admin', return null;
'permissions' => ['*'], // 拥有所有权限
];
} }
return null;
// 记录密码验证信息
\think\facade\Log::info('密码验证', [
'username' => $username,
'input_password' => $password,
'stored_hash' => $user->password,
'is_encrypted' => $isEncrypted,
'password_info' => password_get_info($user->password)
]);
// 验证密码
$isValid = false;
if ($isEncrypted) {
// 前端已加密,直接比较哈希值
// 注意:这里需要确保前端和后端使用相同的加密算法和盐值
$storedHash = self::getStoredHash($user->password);
$isValid = hash_equals($storedHash, $password);
\think\facade\Log::info('加密密码验证', [
'username' => $username,
'stored_hash' => $storedHash,
'input_hash' => $password,
'is_valid' => $isValid
]);
} else {
// 未加密使用password_verify验证
$isValid = password_verify($password, $user->password);
}
\think\facade\Log::info('密码验证结果', [
'username' => $username,
'is_valid' => $isValid,
'is_encrypted' => $isEncrypted
]);
if (!$isValid) {
return null;
}
return [
'id' => $user->id,
'username' => $user->username,
'name' => $user->username, // 暂时使用username作为name
'role' => 'admin', // 暂时固定为admin角色
'permissions' => ['*'], // 暂时拥有所有权限
];
}
/**
* 获取存储的哈希值
* 用于前端加密密码的验证
* @param string $bcryptHash 数据库中存储的bcrypt哈希值
* @return string 用于前端验证的哈希值
*/
protected static function getStoredHash($bcryptHash)
{
// 这里需要实现与前端相同的加密算法
// 例如如果前端使用SHA256加盐这里需要提取原始密码并进行相同的处理
// 注意:这只是一个示例,实际实现可能需要根据您的具体需求调整
// 假设我们能够从bcrypt哈希中提取原始密码实际上这是不可能的这里只是示例
// 在实际应用中,您需要在用户注册或修改密码时同时存储前端加密的哈希值
$originalPassword = '123456'; // 这里应该是从数据库中获取的原始密码
$salt = 'yishi_salt_2024'; // 与前端相同的盐值
// 使用与前端相同的算法
return hash('sha256', $originalPassword . $salt);
} }
/** /**
@@ -63,17 +144,20 @@ class User extends Model
*/ */
public static function getUserByMobile($mobile) public static function getUserByMobile($mobile)
{ {
// 目前使用固定账号,后续可改为数据库查询 // 查询用户
if ($mobile === '13800138000') { $user = self::where('mobile', $mobile)->find();
return [
'id' => 2, if (!$user) {
'username' => 'mobile_user', return null;
'name' => '手机用户',
'mobile' => '13800138000',
'role' => 'user',
'permissions' => ['user'], // 普通用户权限
];
} }
return null;
return [
'id' => $user->id,
'username' => $user->username,
'name' => $user->username, // 暂时使用username作为name
'mobile' => $user->mobile,
'role' => 'user', // 暂时固定为user角色
'permissions' => ['user'], // 暂时拥有用户权限
];
} }
} }

View File

@@ -29,19 +29,20 @@ class AuthService
/** /**
* 用户登录 * 用户登录
* @param string $username 用户名 * @param string $username 用户名
* @param string $password 密码 * @param string $password 密码(可能是加密后的)
* @param string $ip 登录IP * @param string $ip 登录IP
* @param bool $isEncrypted 密码是否已加密
* @return array * @return array
* @throws \Exception * @throws \Exception
*/ */
public function login($username, $password, $ip) public function login($username, $password, $ip, $isEncrypted = false)
{ {
// 获取用户信息 // 获取用户信息
$user = User::getAdminUser($username, $password); $user = User::getAdminUser($username, $password, $isEncrypted);
if (empty($user)) { if (empty($user)) {
// 记录登录失败 // 记录登录失败
Log::info('登录失败', ['username' => $username, 'ip' => $ip]); Log::info('登录失败', ['username' => $username, 'ip' => $ip, 'is_encrypted' => $isEncrypted]);
throw new \Exception('用户名或密码错误'); throw new \Exception('用户名或密码错误');
} }
@@ -62,16 +63,17 @@ class AuthService
/** /**
* 手机号验证码登录 * 手机号验证码登录
* @param string $mobile 手机号 * @param string $mobile 手机号
* @param string $code 验证码 * @param string $code 验证码(可能是加密后的)
* @param string $ip 登录IP * @param string $ip 登录IP
* @param bool $isEncrypted 验证码是否已加密
* @return array * @return array
* @throws \Exception * @throws \Exception
*/ */
public function mobileLogin($mobile, $code, $ip) public function mobileLogin($mobile, $code, $ip, $isEncrypted = false)
{ {
// 验证验证码 // 验证验证码
if (!$this->smsService->verifyCode($mobile, $code, 'login')) { if (!$this->smsService->verifyCode($mobile, $code, 'login', $isEncrypted)) {
Log::info('验证码验证失败', ['mobile' => $mobile, 'ip' => $ip]); Log::info('验证码验证失败', ['mobile' => $mobile, 'ip' => $ip, 'is_encrypted' => $isEncrypted]);
throw new \Exception('验证码错误或已过期'); throw new \Exception('验证码错误或已过期');
} }

View File

@@ -4,121 +4,199 @@ namespace app\common\service;
use think\facade\Cache; use think\facade\Cache;
use think\facade\Log; use think\facade\Log;
/**
* 短信服务类
*/
class SmsService class SmsService
{ {
/** /**
* 验证码有效期(秒) * 验证码有效期(秒)
*/ */
const CODE_EXPIRE = 300; const CODE_EXPIRE = 300;
/**
* 验证码长度
*/
const CODE_LENGTH = 4;
/** /**
* 发送验证码 * 发送验证码
* @param string $mobile 手机号 * @param string $mobile 手机号
* @param string $type 验证码类型 * @param string $type 验证码类型 (login, register)
* @return array * @return array
* @throws \Exception * @throws \Exception
*/ */
public function sendCode($mobile, $type) public function sendCode($mobile, $type)
{ {
// 检查发送频率 // 检查发送频率限制
$this->checkSendFrequency($mobile); $this->checkSendLimit($mobile, $type);
// 生成验证码 // 生成验证码
$code = $this->generateCode(); $code = $this->generateCode();
try { // 缓存验证码
// TODO: 对接实际的短信发送服务 $this->saveCode($mobile, $code, $type);
// 这里模拟发送成功
$this->saveCode($mobile, $code, $type); // 发送验证码(实际项目中对接短信平台)
$this->doSend($mobile, $code, $type);
// 记录发送日志
Log::info('验证码发送成功', [ // 记录日志
'mobile' => $mobile, Log::info('发送验证码', [
'type' => $type, 'mobile' => $mobile,
'code' => $code 'type' => $type,
]); 'code' => $code
]);
return [
'mobile' => $mobile, return [
'expire' => self::CODE_EXPIRE 'mobile' => $mobile,
]; 'expire' => self::CODE_EXPIRE,
} catch (\Exception $e) { // 测试环境返回验证码,生产环境不应返回
Log::error('验证码发送失败', [ 'code' => $code
'mobile' => $mobile, ];
'type' => $type,
'error' => $e->getMessage()
]);
throw new \Exception('验证码发送失败,请稍后重试');
}
} }
/** /**
* 验证验证码 * 验证验证码
* @param string $mobile 手机号 * @param string $mobile 手机号
* @param string $code 验证码 * @param string $code 验证码(可能是加密后的)
* @param string $type 验证码类型 * @param string $type 验证码类型
* @param bool $isEncrypted 验证码是否已加密
* @return bool * @return bool
*/ */
public function verifyCode($mobile, $code, $type) public function verifyCode($mobile, $code, $type, $isEncrypted = false)
{ {
$key = $this->getCodeKey($mobile, $type); $cacheKey = $this->getCodeCacheKey($mobile, $type);
$savedCode = Cache::get($key); $cacheCode = Cache::get($cacheKey);
if (!$savedCode || $savedCode !== $code) { if (!$cacheCode) {
Log::info('验证码不存在或已过期', [
'mobile' => $mobile,
'type' => $type
]);
return false; return false;
} }
// 验证成功后删除验证 // 验证码是否匹配
Cache::rm($key); $isValid = false;
return true;
}
/**
* 检查发送频率
* @param string $mobile 手机号
* @throws \Exception
*/
protected function checkSendFrequency($mobile)
{
$key = 'sms_frequency_' . $mobile;
$lastSendTime = Cache::get($key);
if ($lastSendTime && time() - $lastSendTime < 60) { if ($isEncrypted) {
throw new \Exception('发送太频繁,请稍后再试'); // 前端已加密,需要对缓存中的验证码进行相同的加密处理
$encryptedCacheCode = $this->encryptCode($cacheCode);
$isValid = hash_equals($encryptedCacheCode, $code);
// 记录日志
Log::info('加密验证码验证', [
'mobile' => $mobile,
'cache_code' => $cacheCode,
'encrypted_cache_code' => $encryptedCacheCode,
'input_code' => $code,
'is_valid' => $isValid
]);
} else {
// 未加密,直接比较
$isValid = ($cacheCode === $code);
// 记录日志
Log::info('明文验证码验证', [
'mobile' => $mobile,
'cache_code' => $cacheCode,
'input_code' => $code,
'is_valid' => $isValid
]);
} }
Cache::set($key, time(), 60); // 验证成功后删除缓存
if ($isValid) {
Cache::rm($cacheKey);
}
return $isValid;
} }
/** /**
* 生成验证码 * 检查发送频率限制
* @param string $mobile 手机号
* @param string $type 验证码类型
* @throws \Exception
*/
protected function checkSendLimit($mobile, $type)
{
$cacheKey = $this->getCodeCacheKey($mobile, $type);
// 检查是否存在未过期的验证码
if (Cache::has($cacheKey)) {
throw new \Exception('验证码已发送,请稍后再试');
}
// 检查当日发送次数限制
$limitKey = "sms_limit:{$mobile}:" . date('Ymd');
$sendCount = Cache::get($limitKey, 0);
if ($sendCount >= 10) {
throw new \Exception('今日发送次数已达上限');
}
// 更新发送次数
Cache::set($limitKey, $sendCount + 1, 86400);
}
/**
* 生成随机验证码
* @return string * @return string
*/ */
protected function generateCode() protected function generateCode()
{ {
return sprintf('%06d', random_int(0, 999999)); // 生成4位数字验证码
return sprintf("%0" . self::CODE_LENGTH . "d", mt_rand(0, pow(10, self::CODE_LENGTH) - 1));
} }
/** /**
* 保存验证码 * 保存验证码到缓存
* @param string $mobile 手机号 * @param string $mobile 手机号
* @param string $code 验证码 * @param string $code 验证码
* @param string $type 验证码类型 * @param string $type 验证码类型
*/ */
protected function saveCode($mobile, $code, $type) protected function saveCode($mobile, $code, $type)
{ {
$key = $this->getCodeKey($mobile, $type); $cacheKey = $this->getCodeCacheKey($mobile, $type);
Cache::set($key, $code, self::CODE_EXPIRE); Cache::set($cacheKey, $code, self::CODE_EXPIRE);
} }
/** /**
* 获取缓存key * 执行发送验证码
* @param string $mobile 手机号
* @param string $code 验证码
* @param string $type 验证码类型
* @return bool
*/
protected function doSend($mobile, $code, $type)
{
// 实际项目中对接短信平台API
// 这里仅做模拟,返回成功
return true;
}
/**
* 获取验证码缓存键名
* @param string $mobile 手机号 * @param string $mobile 手机号
* @param string $type 验证码类型 * @param string $type 验证码类型
* @return string * @return string
*/ */
protected function getCodeKey($mobile, $type) protected function getCodeCacheKey($mobile, $type)
{ {
return 'sms_code_' . $type . '_' . $mobile; return "sms_code:{$mobile}:{$type}";
}
/**
* 加密验证码
* 使用与前端相同的加密算法
* @param string $code 原始验证码
* @return string 加密后的验证码
*/
protected function encryptCode($code)
{
// 使用与前端相同的加密算法
$salt = 'yishi_salt_2024'; // 与前端相同的盐值
return hash('sha256', $code . $salt);
} }
} }

View File

@@ -3,6 +3,9 @@ namespace app\common\validate;
use think\Validate; use think\Validate;
/**
* 认证相关验证器
*/
class Auth extends Validate class Auth extends Validate
{ {
/** /**
@@ -10,10 +13,11 @@ class Auth extends Validate
* @var array * @var array
*/ */
protected $rule = [ protected $rule = [
'username' => 'require|length:3,32', 'username' => 'require|length:3,20',
'password' => 'require|length:6,32', 'password' => 'require|length:6,64',
'mobile' => 'require|mobile', 'mobile' => 'require|mobile',
'code' => 'require|length:6', 'code' => 'require|length:4,6',
'is_encrypted' => 'boolean',
'type' => 'require|in:login,register', 'type' => 'require|in:login,register',
]; ];
@@ -23,13 +27,14 @@ class Auth extends Validate
*/ */
protected $message = [ protected $message = [
'username.require' => '用户名不能为空', 'username.require' => '用户名不能为空',
'username.length' => '用户名长度必须在3-32个字符之间', 'username.length' => '用户名长度必须在3-20个字符之间',
'password.require' => '密码不能为空', 'password.require' => '密码不能为空',
'password.length' => '密码长度必须在6-32个字符之间', 'password.length' => '密码长度必须在6-64个字符之间',
'mobile.require' => '手机号不能为空', 'mobile.require' => '手机号不能为空',
'mobile.mobile' => '手机号格式不正确', 'mobile.mobile' => '手机号格式不正确',
'code.require' => '验证码不能为空', 'code.require' => '验证码不能为空',
'code.length' => '验证码必须是6位数字', 'code.length' => '验证码长度必须在4-6个字符之间',
'is_encrypted.boolean' => '加密标志必须为布尔值',
'type.require' => '验证码类型不能为空', 'type.require' => '验证码类型不能为空',
'type.in' => '验证码类型不正确', 'type.in' => '验证码类型不正确',
]; ];
@@ -39,8 +44,9 @@ class Auth extends Validate
* @var array * @var array
*/ */
protected $scene = [ protected $scene = [
'login' => ['username', 'password'], 'login' => ['username', 'password', 'is_encrypted'],
'mobile_login' => ['mobile', 'code'], 'mobile_login' => ['mobile', 'code', 'is_encrypted'],
'refresh' => [],
'send_code' => ['mobile', 'type'], 'send_code' => ['mobile', 'type'],
]; ];
} }