【私域操盘手】账号密码登录
This commit is contained in:
9
.Cursorignore
Normal file
9
.Cursorignore
Normal file
@@ -0,0 +1,9 @@
|
||||
Server/runtime/
|
||||
*.png
|
||||
*.jpg
|
||||
*.jpeg
|
||||
*.gif
|
||||
*.bmp
|
||||
*.webp
|
||||
*.ico
|
||||
*.svg
|
||||
@@ -3,4 +3,6 @@ VUE_APP_PREVIEW=false
|
||||
VUE_APP_API_BASE_URL=http://yishi.com
|
||||
VUE_APP_WWW_BASE_URL=http://yishi.com
|
||||
VUE_APP_WEB_SOCKET_URL=ws://yishi.com:2348
|
||||
VUE_APP_WEBSITE_NAME="管理后台"
|
||||
VUE_APP_WEBSITE_NAME="管理后台"
|
||||
VUE_APP_TITLE=医师管理系统
|
||||
VUE_APP_API_URL=http://yishi.com
|
||||
@@ -1,6 +1,7 @@
|
||||
NODE_ENV=development
|
||||
VUE_APP_PREVIEW=true
|
||||
VUE_APP_API_BASE_URL=http://yishi.com
|
||||
VUE_APP_WWW_BASE_URL=http://yishi.com
|
||||
VUE_APP_WEB_SOCKET_URL=ws://yishi.com:2348
|
||||
VUE_APP_WEBSITE_NAME="管理后台"
|
||||
VUE_APP_PREVIEW=false
|
||||
VUE_APP_API_BASE_URL=http://localhost:8000
|
||||
VUE_APP_WWW_BASE_URL=http://localhost:8000
|
||||
VUE_APP_WEB_SOCKET_URL=ws://localhost:2348
|
||||
VUE_APP_WEBSITE_NAME="医师管理系统开发环境"
|
||||
VUE_APP_TITLE=医师管理系统
|
||||
@@ -1,12 +1,18 @@
|
||||
import { post } from '@/utils/request'
|
||||
import { post, get } from '@/utils/request'
|
||||
|
||||
// 登录服务接口
|
||||
export const ServeLogin = data => {
|
||||
return post('/backend/user/login', data)
|
||||
return post('/api/auth/login', data)
|
||||
}
|
||||
|
||||
// 获取用户信息
|
||||
export const ServeGetUser = () => {
|
||||
return post('/backend/user/get')
|
||||
return get('/api/auth/info')
|
||||
}
|
||||
|
||||
// 刷新token
|
||||
export const ServeRefreshToken = () => {
|
||||
return post('/api/auth/refresh')
|
||||
}
|
||||
|
||||
export const ServeSetUserPassword = (data) => {
|
||||
@@ -14,8 +20,9 @@ export const ServeSetUserPassword = (data) => {
|
||||
}
|
||||
|
||||
// 退出登录服务接口
|
||||
export const ServeLogout = data => {
|
||||
return post('/backend/user/logout', data)
|
||||
export const ServeLogout = () => {
|
||||
// JWT不需要服务端登出,直接清除本地token即可
|
||||
return Promise.resolve({ code: 200, msg: '退出成功' })
|
||||
}
|
||||
|
||||
export const UserIndex = data => {
|
||||
|
||||
@@ -7,7 +7,7 @@ export default {
|
||||
{
|
||||
path: '/auth/login',
|
||||
meta: {
|
||||
title: '账号登录?',
|
||||
title: '账号登录',
|
||||
needLogin: false,
|
||||
},
|
||||
component: () => import('@/views/auth/login'),
|
||||
|
||||
@@ -6,6 +6,8 @@ import SystemRouter from './system'
|
||||
import ProductRouter from '@/router/product';
|
||||
import TaskRouter from '@/router/task';
|
||||
import DeviceRouter from '@/router/device';
|
||||
import { isLogin } from '@/utils/auth'
|
||||
import store from '@/store'
|
||||
|
||||
const originalPush = Router.prototype.push
|
||||
Router.prototype.push = function push(location) {
|
||||
@@ -46,7 +48,33 @@ const routes = [
|
||||
},
|
||||
]
|
||||
|
||||
export default new Router({
|
||||
const router = new Router({
|
||||
routes,
|
||||
mode: 'hash',
|
||||
})
|
||||
|
||||
// 全局前置守卫
|
||||
router.beforeEach((to, from, next) => {
|
||||
// 设置页面标题
|
||||
document.title = to.meta.title ? to.meta.title : '医视管理系统'
|
||||
|
||||
// 检查路由是否需要登录
|
||||
if (to.meta.needLogin) {
|
||||
// 检查登录状态
|
||||
if (isLogin()) {
|
||||
next()
|
||||
} else {
|
||||
// 未登录,重定向到登录页
|
||||
next({ path: '/auth/login' })
|
||||
}
|
||||
} else {
|
||||
// 如果是访问登录页且已登录,则跳转到首页
|
||||
if (to.path === '/auth/login' && isLogin()) {
|
||||
next({ path: '/' })
|
||||
} else {
|
||||
next()
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
export default router
|
||||
|
||||
@@ -1,56 +1,55 @@
|
||||
import { setUserInfo, getUserInfo, removeAll, getToken } from '@/utils/auth'
|
||||
import { setUserInfo, getUserInfo, removeAll, getToken, isLogin } from '@/utils/auth'
|
||||
|
||||
import { ServeLogout } from '@/api/user'
|
||||
import { ServeLogout, ServeRefreshToken } from '@/api/user'
|
||||
|
||||
let state = {
|
||||
// 用户ID
|
||||
id: 0,
|
||||
// 渠道ID
|
||||
channel_id: 0,
|
||||
// 渠道名称
|
||||
channel_name: '',
|
||||
// 角色
|
||||
role: '',
|
||||
// 权限
|
||||
permissions: [],
|
||||
// 账号
|
||||
username: '',
|
||||
// 姓名
|
||||
name: '',
|
||||
// 手机号
|
||||
mobile: '',
|
||||
// 登录时间
|
||||
login_time: '',
|
||||
// 登录次数
|
||||
login_count: 0,
|
||||
// 登录IP
|
||||
login_ip: '',
|
||||
// 创建时间
|
||||
create_time: '',
|
||||
// 个性头像
|
||||
avatar: require('@/assets/image/detault-avatar.jpg'),
|
||||
// 角色
|
||||
roles: [],
|
||||
// 当前登录状态
|
||||
loginStatus: false,
|
||||
// 是否启动游戏模块
|
||||
// 原有字段保持不变
|
||||
channel_id: 0,
|
||||
channel_name: '',
|
||||
mobile: '',
|
||||
login_time: '',
|
||||
login_count: 0,
|
||||
login_ip: '',
|
||||
create_time: '',
|
||||
mod_game: false,
|
||||
}
|
||||
|
||||
// 判断用户是否登录
|
||||
if (getToken()) {
|
||||
if (isLogin()) {
|
||||
let userInfo = getUserInfo()
|
||||
|
||||
state.id = userInfo.id
|
||||
state.channel_id = userInfo.channel_id
|
||||
state.channel_name = userInfo.channel_name
|
||||
state.username = userInfo.username
|
||||
state.name = userInfo.name
|
||||
state.mobile = userInfo.mobile
|
||||
state.login_time = userInfo.login_time
|
||||
state.login_count = userInfo.login_count
|
||||
state.login_ip = userInfo.login_ip
|
||||
state.create_time = userInfo.create_time
|
||||
state.roles = userInfo.roles ? userInfo.roles: []
|
||||
//state.avatar = userInfo.avatar ? userInfo.avatar : state.avatar
|
||||
// 更新状态
|
||||
state.id = userInfo.id
|
||||
state.username = userInfo.username
|
||||
state.name = userInfo.name
|
||||
state.role = userInfo.role
|
||||
state.permissions = userInfo.permissions || []
|
||||
state.loginStatus = true
|
||||
state.mod_game = userInfo.mod_game
|
||||
|
||||
// 兼容原有字段
|
||||
state.channel_id = userInfo.channel_id || 0
|
||||
state.channel_name = userInfo.channel_name || ''
|
||||
state.mobile = userInfo.mobile || ''
|
||||
state.login_time = userInfo.login_time || ''
|
||||
state.login_count = userInfo.login_count || 0
|
||||
state.login_ip = userInfo.login_ip || ''
|
||||
state.create_time = userInfo.create_time || ''
|
||||
state.mod_game = userInfo.mod_game || false
|
||||
state.roles = userInfo.roles || []
|
||||
|
||||
console.log('userInfo: ', userInfo)
|
||||
}
|
||||
@@ -60,24 +59,28 @@ const User = {
|
||||
mutations: {
|
||||
// 用户退出登录
|
||||
USER_LOGOUT(state) {
|
||||
state.id = 0
|
||||
state.channel_id = 0
|
||||
state.channel_name = ''
|
||||
state.username = ''
|
||||
state.name = ''
|
||||
state.mobile = ''
|
||||
state.login_time = ''
|
||||
state.login_count = 0
|
||||
state.login_ip = ''
|
||||
state.create_time = ''
|
||||
state.roles = []
|
||||
state.id = 0
|
||||
state.username = ''
|
||||
state.name = ''
|
||||
state.role = ''
|
||||
state.permissions = []
|
||||
state.loginStatus = false
|
||||
state.mod_game = false
|
||||
|
||||
// 原有字段重置
|
||||
state.channel_id = 0
|
||||
state.channel_name = ''
|
||||
state.mobile = ''
|
||||
state.login_time = ''
|
||||
state.login_count = 0
|
||||
state.login_ip = ''
|
||||
state.create_time = ''
|
||||
state.roles = []
|
||||
state.mod_game = false
|
||||
},
|
||||
|
||||
// 设置用户登录状态
|
||||
UPDATE_LOGIN_STATUS(state) {
|
||||
state.loginStatus = true
|
||||
UPDATE_LOGIN_STATUS(state, status) {
|
||||
state.loginStatus = status
|
||||
},
|
||||
|
||||
// 更新用户信息
|
||||
@@ -91,18 +94,20 @@ const User = {
|
||||
// 保存用户信息到缓存
|
||||
setUserInfo({
|
||||
id: state.id,
|
||||
channel_id: state.channel_id,
|
||||
channel_name: state.channel_name,
|
||||
username: state.username,
|
||||
name: state.name,
|
||||
role: state.role,
|
||||
permissions: state.permissions || [],
|
||||
// 兼容原有字段
|
||||
channel_id: state.channel_id,
|
||||
channel_name: state.channel_name,
|
||||
mobile: state.mobile,
|
||||
login_time: state.login_time,
|
||||
login_count: state.login_count,
|
||||
login_ip: state.login_ip,
|
||||
create_time: state.create_time,
|
||||
roles: state.roles ? state.roles: [],
|
||||
roles: state.roles || [],
|
||||
mod_game: state.mod_game,
|
||||
//avatar: state.avatar,
|
||||
})
|
||||
},
|
||||
},
|
||||
@@ -115,6 +120,25 @@ const User = {
|
||||
location.reload()
|
||||
})
|
||||
},
|
||||
|
||||
// 刷新令牌
|
||||
ACT_REFRESH_TOKEN({ commit }) {
|
||||
return new Promise((resolve, reject) => {
|
||||
ServeRefreshToken()
|
||||
.then(res => {
|
||||
if (res.code === 200) {
|
||||
// 更新token
|
||||
setToken(res.data.token, res.data.token_expired - Math.floor(Date.now() / 1000))
|
||||
resolve(res)
|
||||
} else {
|
||||
reject(res)
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
reject(error)
|
||||
})
|
||||
})
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@@ -7,8 +7,8 @@ const USER_SETTING = 'MANAGE_SETTING'
|
||||
/**
|
||||
* 设置用户授权token
|
||||
*
|
||||
* @param {String} token
|
||||
* @param {Number} expires
|
||||
* @param {String} token - JWT令牌
|
||||
* @param {Number} expires - 过期时间戳(秒)
|
||||
*/
|
||||
export function setToken(token, expires) {
|
||||
expires = new Date().getTime() + expires * 1000
|
||||
@@ -23,6 +23,7 @@ export function setToken(token, expires) {
|
||||
|
||||
/**
|
||||
* 获取授权token
|
||||
* @returns {String} token
|
||||
*/
|
||||
export function getToken() {
|
||||
const result = JSON.parse(
|
||||
@@ -33,9 +34,24 @@ export function getToken() {
|
||||
})
|
||||
)
|
||||
|
||||
// 检查token是否过期
|
||||
if (result.expires > 0 && result.expires < new Date().getTime()) {
|
||||
// token已过期,清除token
|
||||
removeAll()
|
||||
return ''
|
||||
}
|
||||
|
||||
return result.token
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查用户是否登录
|
||||
* @returns {Boolean}
|
||||
*/
|
||||
export function isLogin() {
|
||||
return !!getToken()
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置用户信息
|
||||
*
|
||||
|
||||
@@ -39,7 +39,8 @@ const errorHandler = error => {
|
||||
request.interceptors.request.use(config => {
|
||||
const token = getToken()
|
||||
if (token) {
|
||||
config.headers['token'] = token
|
||||
// 设置JWT认证头
|
||||
config.headers['Authorization'] = 'Bearer ' + token
|
||||
}
|
||||
|
||||
return config
|
||||
|
||||
@@ -35,7 +35,7 @@
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import { setToken } from '@/utils/auth'
|
||||
import { setToken, setUserInfo } from '@/utils/auth'
|
||||
import { ServeLogin } from '@/api/user'
|
||||
|
||||
export default {
|
||||
@@ -85,16 +85,24 @@ export default {
|
||||
let result = res.data
|
||||
|
||||
// 保存授权信息到本地缓存
|
||||
setToken(result.token, result.token_expired)
|
||||
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')
|
||||
this.$store.commit('UPDATE_LOGIN_STATUS', true)
|
||||
|
||||
// 登录成功后连接 WebSocket 服务器
|
||||
this.$notify.success({
|
||||
title: '成功',
|
||||
message: '登录成功',
|
||||
})
|
||||
|
||||
// 跳转到首页
|
||||
this.toLink('/')
|
||||
} else {
|
||||
this.$notify.info({
|
||||
title: '提示',
|
||||
this.$notify.error({
|
||||
title: '错误',
|
||||
message: res.msg,
|
||||
})
|
||||
}
|
||||
|
||||
247
Backend/src/views/device/detail.vue
Normal file
247
Backend/src/views/device/detail.vue
Normal file
@@ -0,0 +1,247 @@
|
||||
<template>
|
||||
<el-container v-loading="loading" style="height: 100%;">
|
||||
<el-header style="display: flex; align-items: center; padding: 0;">
|
||||
<div style="margin-left: 20px;">
|
||||
<el-page-header @back="goBack" :content="'设备ID: ' + deviceId"></el-page-header>
|
||||
</div>
|
||||
</el-header>
|
||||
<el-main>
|
||||
<el-card v-if="deviceInfo" class="device-card">
|
||||
<div slot="header" class="card-header">
|
||||
<span>设备详情</span>
|
||||
<el-tag :type="getStatusType(deviceInfo.status)" class="status-tag">
|
||||
{{ getStatusText(deviceInfo.status) }}
|
||||
</el-tag>
|
||||
</div>
|
||||
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="12">
|
||||
<div class="info-item">
|
||||
<span class="label">设备ID:</span>
|
||||
<span class="value">{{ deviceInfo.id }}</span>
|
||||
</div>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<div class="info-item">
|
||||
<span class="label">IMEI:</span>
|
||||
<span class="value">{{ deviceInfo.imei }}</span>
|
||||
</div>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="12">
|
||||
<div class="info-item">
|
||||
<span class="label">设备手机号:</span>
|
||||
<span class="value">{{ deviceInfo.phone || '--' }}</span>
|
||||
</div>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<div class="info-item">
|
||||
<span class="label">电量:</span>
|
||||
<span class="value">
|
||||
<el-progress
|
||||
:percentage="deviceInfo.battery"
|
||||
:color="getBatteryColor(deviceInfo.battery)"
|
||||
:format="() => `${deviceInfo.battery}%`"
|
||||
style="width: 200px;">
|
||||
</el-progress>
|
||||
</span>
|
||||
</div>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="12">
|
||||
<div class="info-item">
|
||||
<span class="label">微信ID:</span>
|
||||
<span class="value">{{ deviceInfo.wechat_id || '--' }}</span>
|
||||
</div>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<div class="info-item">
|
||||
<span class="label">微信号:</span>
|
||||
<span class="value">{{ deviceInfo.wechat_name || '--' }}</span>
|
||||
</div>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="12">
|
||||
<div class="info-item">
|
||||
<span class="label">分组名称:</span>
|
||||
<span class="value">{{ deviceInfo.group_name || '--' }}</span>
|
||||
</div>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<div class="info-item">
|
||||
<span class="label">保管者:</span>
|
||||
<span class="value">{{ deviceInfo.username || '--' }}</span>
|
||||
</div>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="24">
|
||||
<div class="info-item">
|
||||
<span class="label">备注:</span>
|
||||
<span class="value">{{ deviceInfo.remark || '--' }}</span>
|
||||
</div>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="12">
|
||||
<div class="info-item">
|
||||
<span class="label">创建时间:</span>
|
||||
<span class="value">{{ formatTime(deviceInfo.create_time) }}</span>
|
||||
</div>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<div class="info-item">
|
||||
<span class="label">更新时间:</span>
|
||||
<span class="value">{{ formatTime(deviceInfo.update_time) }}</span>
|
||||
</div>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-card>
|
||||
|
||||
<el-empty v-else description="未找到设备信息"></el-empty>
|
||||
</el-main>
|
||||
</el-container>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
// 引入API请求
|
||||
import request from '@/utils/request'
|
||||
|
||||
export default {
|
||||
name: 'DeviceDetail',
|
||||
data() {
|
||||
return {
|
||||
// 设备ID
|
||||
deviceId: null,
|
||||
// 设备信息
|
||||
deviceInfo: null,
|
||||
// 加载状态
|
||||
loading: false
|
||||
}
|
||||
},
|
||||
created() {
|
||||
// 获取路由参数中的设备ID
|
||||
this.deviceId = this.$route.params.id
|
||||
// 加载设备详情
|
||||
this.getDeviceDetail()
|
||||
},
|
||||
methods: {
|
||||
// 返回列表页
|
||||
goBack() {
|
||||
this.$router.push('/device/index')
|
||||
},
|
||||
// 获取设备详情
|
||||
getDeviceDetail() {
|
||||
if (!this.deviceId) {
|
||||
this.$message.error('设备ID不能为空')
|
||||
return
|
||||
}
|
||||
|
||||
this.loading = true
|
||||
request({
|
||||
url: `/api/devices/detail/${this.deviceId}`,
|
||||
method: 'get'
|
||||
}).then(res => {
|
||||
this.loading = false
|
||||
if (res.code === 200) {
|
||||
this.deviceInfo = res.data
|
||||
} else {
|
||||
this.$message.error(res.msg || '获取设备详情失败')
|
||||
}
|
||||
}).catch(() => {
|
||||
this.loading = false
|
||||
})
|
||||
},
|
||||
// 格式化时间
|
||||
formatTime(timestamp) {
|
||||
if (!timestamp) return '--'
|
||||
const date = new Date(timestamp * 1000)
|
||||
const year = date.getFullYear()
|
||||
const month = (date.getMonth() + 1).toString().padStart(2, '0')
|
||||
const day = date.getDate().toString().padStart(2, '0')
|
||||
const hour = date.getHours().toString().padStart(2, '0')
|
||||
const minute = date.getMinutes().toString().padStart(2, '0')
|
||||
const second = date.getSeconds().toString().padStart(2, '0')
|
||||
|
||||
return `${year}-${month}-${day} ${hour}:${minute}:${second}`
|
||||
},
|
||||
// 获取状态对应的类型
|
||||
getStatusType(status) {
|
||||
const statusMap = {
|
||||
'0': 'info', // 离线
|
||||
'1': 'success', // 在线
|
||||
'2': 'danger', // 故障
|
||||
'3': 'warning' // 维修中
|
||||
}
|
||||
return statusMap[status] || 'info'
|
||||
},
|
||||
// 获取状态对应的文本
|
||||
getStatusText(status) {
|
||||
const statusMap = {
|
||||
'0': '离线',
|
||||
'1': '在线',
|
||||
'2': '故障',
|
||||
'3': '维修中'
|
||||
}
|
||||
return statusMap[status] || status
|
||||
},
|
||||
// 获取电量对应的颜色
|
||||
getBatteryColor(battery) {
|
||||
if (battery < 20) {
|
||||
return '#F56C6C' // 红色,电量低
|
||||
} else if (battery < 50) {
|
||||
return '#E6A23C' // 黄色,电量中等
|
||||
} else {
|
||||
return '#67C23A' // 绿色,电量充足
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.el-header {
|
||||
border-bottom: 1px solid #e6e6e6;
|
||||
background-color: #fff;
|
||||
}
|
||||
|
||||
.device-card {
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
.card-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.status-tag {
|
||||
margin-left: 10px;
|
||||
}
|
||||
|
||||
.info-item {
|
||||
margin-bottom: 15px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.label {
|
||||
color: #606266;
|
||||
font-weight: bold;
|
||||
margin-right: 10px;
|
||||
min-width: 80px;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.value {
|
||||
color: #303133;
|
||||
}
|
||||
</style>
|
||||
14
Server/.env
Normal file
14
Server/.env
Normal file
@@ -0,0 +1,14 @@
|
||||
[APP]
|
||||
APP_DEBUG = true
|
||||
APP_TRACE = false
|
||||
|
||||
[DATABASE]
|
||||
TYPE = mysql
|
||||
HOSTNAME = localhost
|
||||
DATABASE = yishi
|
||||
USERNAME = root
|
||||
PASSWORD = 123456
|
||||
HOSTPORT = 3306
|
||||
CHARSET = utf8mb4
|
||||
PREFIX = tk_
|
||||
DEBUG = true
|
||||
25
Server/application/common/config/route.php
Normal file
25
Server/application/common/config/route.php
Normal file
@@ -0,0 +1,25 @@
|
||||
<?php
|
||||
// common模块路由配置
|
||||
|
||||
use think\facade\Route;
|
||||
|
||||
// 添加测试路由
|
||||
Route::get('api/test', function() {
|
||||
return json([
|
||||
'code' => 200,
|
||||
'msg' => '路由测试成功',
|
||||
'data' => [
|
||||
'time' => date('Y-m-d H:i:s'),
|
||||
'module' => 'common'
|
||||
]
|
||||
]);
|
||||
});
|
||||
|
||||
// 定义RESTful风格的API路由
|
||||
Route::post('api/auth/login', 'app\\common\\controller\\Auth@login'); // 登录接口
|
||||
|
||||
// 需要JWT认证的接口
|
||||
Route::get('api/auth/info', 'app\\common\\controller\\Auth@info')->middleware(['jwt']); // 获取用户信息
|
||||
Route::post('api/auth/refresh', 'app\\common\\controller\\Auth@refresh')->middleware(['jwt']); // 刷新令牌
|
||||
|
||||
return [];
|
||||
104
Server/application/common/controller/Auth.php
Normal file
104
Server/application/common/controller/Auth.php
Normal file
@@ -0,0 +1,104 @@
|
||||
<?php
|
||||
namespace app\common\controller;
|
||||
|
||||
use app\common\helper\ResponseHelper;
|
||||
use app\common\service\AuthService;
|
||||
use think\Controller;
|
||||
use think\facade\Request;
|
||||
|
||||
/**
|
||||
* 认证控制器
|
||||
* 处理用户登录和身份验证
|
||||
*/
|
||||
class Auth extends Controller
|
||||
{
|
||||
/**
|
||||
* 允许跨域请求的域名
|
||||
* @var string
|
||||
*/
|
||||
protected $allowOrigin = '*';
|
||||
|
||||
/**
|
||||
* 认证服务实例
|
||||
* @var AuthService
|
||||
*/
|
||||
protected $authService;
|
||||
|
||||
/**
|
||||
* 初始化
|
||||
* 设置跨域相关响应头
|
||||
*/
|
||||
public function initialize()
|
||||
{
|
||||
parent::initialize();
|
||||
|
||||
// 允许跨域访问
|
||||
header('Access-Control-Allow-Origin: ' . $this->allowOrigin);
|
||||
header('Access-Control-Allow-Headers: Origin, X-Requested-With, Content-Type, Accept, Authorization');
|
||||
header('Access-Control-Allow-Methods: GET, POST, PUT, DELETE, OPTIONS');
|
||||
|
||||
// 预检请求直接返回200
|
||||
if (Request::method(true) == 'OPTIONS') {
|
||||
exit();
|
||||
}
|
||||
|
||||
// 初始化认证服务
|
||||
$this->authService = new AuthService();
|
||||
}
|
||||
|
||||
/**
|
||||
* 用户登录
|
||||
* @return \think\response\Json
|
||||
*/
|
||||
public function login()
|
||||
{
|
||||
// 获取登录参数
|
||||
$params = Request::only(['username', 'password']);
|
||||
|
||||
// 参数验证
|
||||
$validate = validate('common/Auth');
|
||||
if (!$validate->scene('login')->check($params)) {
|
||||
return ResponseHelper::error($validate->getError());
|
||||
}
|
||||
|
||||
try {
|
||||
// 调用登录服务
|
||||
$result = $this->authService->login(
|
||||
$params['username'],
|
||||
$params['password'],
|
||||
Request::ip()
|
||||
);
|
||||
return ResponseHelper::success($result, '登录成功');
|
||||
} catch (\Exception $e) {
|
||||
return ResponseHelper::error($e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取用户信息
|
||||
* @return \think\response\Json
|
||||
*/
|
||||
public function info()
|
||||
{
|
||||
try {
|
||||
$result = $this->authService->getUserInfo(request()->userInfo);
|
||||
return ResponseHelper::success($result);
|
||||
} catch (\Exception $e) {
|
||||
return ResponseHelper::unauthorized($e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 刷新令牌
|
||||
* @return \think\response\Json
|
||||
*/
|
||||
public function refresh()
|
||||
{
|
||||
try {
|
||||
$result = $this->authService->refreshToken(request()->userInfo);
|
||||
return ResponseHelper::success($result, '刷新成功');
|
||||
} catch (\Exception $e) {
|
||||
return ResponseHelper::unauthorized($e->getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
57
Server/application/common/helper/ResponseHelper.php
Normal file
57
Server/application/common/helper/ResponseHelper.php
Normal file
@@ -0,0 +1,57 @@
|
||||
<?php
|
||||
namespace app\common\helper;
|
||||
|
||||
class ResponseHelper
|
||||
{
|
||||
/**
|
||||
* 成功响应
|
||||
* @param mixed $data 响应数据
|
||||
* @param string $msg 响应消息
|
||||
* @param int $code 响应代码
|
||||
* @return \think\response\Json
|
||||
*/
|
||||
public static function success($data = null, $msg = '操作成功', $code = 200)
|
||||
{
|
||||
return json([
|
||||
'code' => $code,
|
||||
'msg' => $msg,
|
||||
'data' => $data
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 错误响应
|
||||
* @param string $msg 错误消息
|
||||
* @param int $code 错误代码
|
||||
* @param mixed $data 错误数据
|
||||
* @return \think\response\Json
|
||||
*/
|
||||
public static function error($msg = '操作失败', $code = 400, $data = null)
|
||||
{
|
||||
return json([
|
||||
'code' => $code,
|
||||
'msg' => $msg,
|
||||
'data' => $data
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 未授权响应
|
||||
* @param string $msg 错误消息
|
||||
* @return \think\response\Json
|
||||
*/
|
||||
public static function unauthorized($msg = '未授权访问')
|
||||
{
|
||||
return self::error($msg, 401);
|
||||
}
|
||||
|
||||
/**
|
||||
* 禁止访问响应
|
||||
* @param string $msg 错误消息
|
||||
* @return \think\response\Json
|
||||
*/
|
||||
public static function forbidden($msg = '禁止访问')
|
||||
{
|
||||
return self::error($msg, 403);
|
||||
}
|
||||
}
|
||||
79
Server/application/common/model/User.php
Normal file
79
Server/application/common/model/User.php
Normal file
@@ -0,0 +1,79 @@
|
||||
<?php
|
||||
namespace app\common\model;
|
||||
|
||||
use think\Model;
|
||||
|
||||
class User extends Model
|
||||
{
|
||||
/**
|
||||
* 数据表名
|
||||
* @var string
|
||||
*/
|
||||
protected $table = 'user';
|
||||
|
||||
/**
|
||||
* 自动写入时间戳
|
||||
* @var bool
|
||||
*/
|
||||
protected $autoWriteTimestamp = true;
|
||||
|
||||
/**
|
||||
* 创建时间字段
|
||||
* @var string
|
||||
*/
|
||||
protected $createTime = 'create_time';
|
||||
|
||||
/**
|
||||
* 更新时间字段
|
||||
* @var string
|
||||
*/
|
||||
protected $updateTime = 'update_time';
|
||||
|
||||
/**
|
||||
* 隐藏属性
|
||||
* @var array
|
||||
*/
|
||||
protected $hidden = ['password', 'delete_time'];
|
||||
|
||||
/**
|
||||
* 获取管理员用户信息
|
||||
* @param string $username 用户名
|
||||
* @param string $password 密码
|
||||
* @return array|null
|
||||
*/
|
||||
public static function getAdminUser($username, $password)
|
||||
{
|
||||
// 目前使用固定账号,后续可改为数据库查询
|
||||
if ($username === 'admin' && $password === '123456') {
|
||||
return [
|
||||
'id' => 1,
|
||||
'username' => 'admin',
|
||||
'name' => '超级管理员',
|
||||
'role' => 'admin',
|
||||
'permissions' => ['*'], // 拥有所有权限
|
||||
];
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 通过手机号获取用户信息
|
||||
* @param string $mobile 手机号
|
||||
* @return array|null
|
||||
*/
|
||||
public static function getUserByMobile($mobile)
|
||||
{
|
||||
// 目前使用固定账号,后续可改为数据库查询
|
||||
if ($mobile === '13800138000') {
|
||||
return [
|
||||
'id' => 2,
|
||||
'username' => 'mobile_user',
|
||||
'name' => '手机用户',
|
||||
'mobile' => '13800138000',
|
||||
'role' => 'user',
|
||||
'permissions' => ['user'], // 普通用户权限
|
||||
];
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
155
Server/application/common/service/AuthService.php
Normal file
155
Server/application/common/service/AuthService.php
Normal file
@@ -0,0 +1,155 @@
|
||||
<?php
|
||||
namespace app\common\service;
|
||||
|
||||
use app\common\model\User;
|
||||
use app\common\util\JwtUtil;
|
||||
use think\facade\Log;
|
||||
|
||||
class AuthService
|
||||
{
|
||||
/**
|
||||
* 令牌有效期(秒)
|
||||
*/
|
||||
const TOKEN_EXPIRE = 7200;
|
||||
|
||||
/**
|
||||
* 短信服务实例
|
||||
* @var SmsService
|
||||
*/
|
||||
protected $smsService;
|
||||
|
||||
/**
|
||||
* 构造函数
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
$this->smsService = new SmsService();
|
||||
}
|
||||
|
||||
/**
|
||||
* 用户登录
|
||||
* @param string $username 用户名
|
||||
* @param string $password 密码
|
||||
* @param string $ip 登录IP
|
||||
* @return array
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function login($username, $password, $ip)
|
||||
{
|
||||
// 获取用户信息
|
||||
$user = User::getAdminUser($username, $password);
|
||||
|
||||
if (empty($user)) {
|
||||
// 记录登录失败
|
||||
Log::info('登录失败', ['username' => $username, 'ip' => $ip]);
|
||||
throw new \Exception('用户名或密码错误');
|
||||
}
|
||||
|
||||
// 生成JWT令牌
|
||||
$token = JwtUtil::createToken($user, self::TOKEN_EXPIRE);
|
||||
$expireTime = time() + self::TOKEN_EXPIRE;
|
||||
|
||||
// 记录登录成功
|
||||
Log::info('登录成功', ['username' => $username, 'ip' => $ip]);
|
||||
|
||||
return [
|
||||
'token' => $token,
|
||||
'token_expired' => $expireTime,
|
||||
'member' => $user
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* 手机号验证码登录
|
||||
* @param string $mobile 手机号
|
||||
* @param string $code 验证码
|
||||
* @param string $ip 登录IP
|
||||
* @return array
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function mobileLogin($mobile, $code, $ip)
|
||||
{
|
||||
// 验证验证码
|
||||
if (!$this->smsService->verifyCode($mobile, $code, 'login')) {
|
||||
Log::info('验证码验证失败', ['mobile' => $mobile, 'ip' => $ip]);
|
||||
throw new \Exception('验证码错误或已过期');
|
||||
}
|
||||
|
||||
// 获取用户信息
|
||||
$user = User::getUserByMobile($mobile);
|
||||
if (empty($user)) {
|
||||
Log::info('用户不存在', ['mobile' => $mobile, 'ip' => $ip]);
|
||||
throw new \Exception('用户不存在');
|
||||
}
|
||||
|
||||
// 生成JWT令牌
|
||||
$token = JwtUtil::createToken($user, self::TOKEN_EXPIRE);
|
||||
$expireTime = time() + self::TOKEN_EXPIRE;
|
||||
|
||||
// 记录登录成功
|
||||
Log::info('手机号登录成功', ['mobile' => $mobile, 'ip' => $ip]);
|
||||
|
||||
return [
|
||||
'token' => $token,
|
||||
'token_expired' => $expireTime,
|
||||
'member' => $user
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* 发送登录验证码
|
||||
* @param string $mobile 手机号
|
||||
* @param string $type 验证码类型
|
||||
* @return array
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function sendLoginCode($mobile, $type)
|
||||
{
|
||||
return $this->smsService->sendCode($mobile, $type);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取用户信息
|
||||
* @param array $userInfo JWT中的用户信息
|
||||
* @return array
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function getUserInfo($userInfo)
|
||||
{
|
||||
if (empty($userInfo)) {
|
||||
throw new \Exception('获取用户信息失败');
|
||||
}
|
||||
|
||||
// 移除不需要返回的字段
|
||||
unset($userInfo['exp']);
|
||||
unset($userInfo['iat']);
|
||||
|
||||
return $userInfo;
|
||||
}
|
||||
|
||||
/**
|
||||
* 刷新令牌
|
||||
* @param array $userInfo JWT中的用户信息
|
||||
* @return array
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function refreshToken($userInfo)
|
||||
{
|
||||
if (empty($userInfo)) {
|
||||
throw new \Exception('刷新令牌失败');
|
||||
}
|
||||
|
||||
// 移除过期时间信息
|
||||
unset($userInfo['exp']);
|
||||
unset($userInfo['iat']);
|
||||
|
||||
// 生成新令牌
|
||||
$token = JwtUtil::createToken($userInfo, self::TOKEN_EXPIRE);
|
||||
$expireTime = time() + self::TOKEN_EXPIRE;
|
||||
|
||||
return [
|
||||
'token' => $token,
|
||||
'token_expired' => $expireTime
|
||||
];
|
||||
}
|
||||
}
|
||||
124
Server/application/common/service/SmsService.php
Normal file
124
Server/application/common/service/SmsService.php
Normal file
@@ -0,0 +1,124 @@
|
||||
<?php
|
||||
namespace app\common\service;
|
||||
|
||||
use think\facade\Cache;
|
||||
use think\facade\Log;
|
||||
|
||||
class SmsService
|
||||
{
|
||||
/**
|
||||
* 验证码有效期(秒)
|
||||
*/
|
||||
const CODE_EXPIRE = 300;
|
||||
|
||||
/**
|
||||
* 发送验证码
|
||||
* @param string $mobile 手机号
|
||||
* @param string $type 验证码类型
|
||||
* @return array
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function sendCode($mobile, $type)
|
||||
{
|
||||
// 检查发送频率
|
||||
$this->checkSendFrequency($mobile);
|
||||
|
||||
// 生成验证码
|
||||
$code = $this->generateCode();
|
||||
|
||||
try {
|
||||
// TODO: 对接实际的短信发送服务
|
||||
// 这里模拟发送成功
|
||||
$this->saveCode($mobile, $code, $type);
|
||||
|
||||
// 记录发送日志
|
||||
Log::info('验证码发送成功', [
|
||||
'mobile' => $mobile,
|
||||
'type' => $type,
|
||||
'code' => $code
|
||||
]);
|
||||
|
||||
return [
|
||||
'mobile' => $mobile,
|
||||
'expire' => self::CODE_EXPIRE
|
||||
];
|
||||
} catch (\Exception $e) {
|
||||
Log::error('验证码发送失败', [
|
||||
'mobile' => $mobile,
|
||||
'type' => $type,
|
||||
'error' => $e->getMessage()
|
||||
]);
|
||||
throw new \Exception('验证码发送失败,请稍后重试');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证验证码
|
||||
* @param string $mobile 手机号
|
||||
* @param string $code 验证码
|
||||
* @param string $type 验证码类型
|
||||
* @return bool
|
||||
*/
|
||||
public function verifyCode($mobile, $code, $type)
|
||||
{
|
||||
$key = $this->getCodeKey($mobile, $type);
|
||||
$savedCode = Cache::get($key);
|
||||
|
||||
if (!$savedCode || $savedCode !== $code) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 验证成功后删除验证码
|
||||
Cache::rm($key);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查发送频率
|
||||
* @param string $mobile 手机号
|
||||
* @throws \Exception
|
||||
*/
|
||||
protected function checkSendFrequency($mobile)
|
||||
{
|
||||
$key = 'sms_frequency_' . $mobile;
|
||||
$lastSendTime = Cache::get($key);
|
||||
|
||||
if ($lastSendTime && time() - $lastSendTime < 60) {
|
||||
throw new \Exception('发送太频繁,请稍后再试');
|
||||
}
|
||||
|
||||
Cache::set($key, time(), 60);
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成验证码
|
||||
* @return string
|
||||
*/
|
||||
protected function generateCode()
|
||||
{
|
||||
return sprintf('%06d', random_int(0, 999999));
|
||||
}
|
||||
|
||||
/**
|
||||
* 保存验证码
|
||||
* @param string $mobile 手机号
|
||||
* @param string $code 验证码
|
||||
* @param string $type 验证码类型
|
||||
*/
|
||||
protected function saveCode($mobile, $code, $type)
|
||||
{
|
||||
$key = $this->getCodeKey($mobile, $type);
|
||||
Cache::set($key, $code, self::CODE_EXPIRE);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取缓存key
|
||||
* @param string $mobile 手机号
|
||||
* @param string $type 验证码类型
|
||||
* @return string
|
||||
*/
|
||||
protected function getCodeKey($mobile, $type)
|
||||
{
|
||||
return 'sms_code_' . $type . '_' . $mobile;
|
||||
}
|
||||
}
|
||||
135
Server/application/common/util/JwtUtil.php
Normal file
135
Server/application/common/util/JwtUtil.php
Normal file
@@ -0,0 +1,135 @@
|
||||
<?php
|
||||
namespace app\common\util;
|
||||
|
||||
use think\facade\Config;
|
||||
use think\facade\Request;
|
||||
|
||||
/**
|
||||
* JWT工具类
|
||||
* 用于生成和验证JWT令牌
|
||||
*/
|
||||
class JwtUtil
|
||||
{
|
||||
/**
|
||||
* 密钥
|
||||
* @var string
|
||||
*/
|
||||
protected static $secret = 'YiShi@2023#JWT';
|
||||
|
||||
/**
|
||||
* 头部
|
||||
* @var array
|
||||
*/
|
||||
protected static $header = [
|
||||
'alg' => 'HS256', // 加密算法
|
||||
'typ' => 'JWT' // 类型
|
||||
];
|
||||
|
||||
/**
|
||||
* 创建JWT令牌
|
||||
* @param array $payload 载荷信息
|
||||
* @param int $expire 过期时间(秒),默认2小时
|
||||
* @return string
|
||||
*/
|
||||
public static function createToken($payload, $expire = 7200)
|
||||
{
|
||||
$header = self::base64UrlEncode(json_encode(self::$header, JSON_UNESCAPED_UNICODE));
|
||||
|
||||
// 附加过期时间
|
||||
$payload['exp'] = time() + $expire;
|
||||
$payload['iat'] = time(); // 签发时间
|
||||
|
||||
$payload = self::base64UrlEncode(json_encode($payload, JSON_UNESCAPED_UNICODE));
|
||||
$signature = self::signature($header . '.' . $payload, self::$secret);
|
||||
|
||||
return $header . '.' . $payload . '.' . $signature;
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证令牌
|
||||
* @param string $token 令牌
|
||||
* @return array|bool 验证通过返回载荷信息,失败返回false
|
||||
*/
|
||||
public static function verifyToken($token)
|
||||
{
|
||||
if (empty($token)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$tokenArray = explode('.', $token);
|
||||
if (count($tokenArray) != 3) {
|
||||
return false;
|
||||
}
|
||||
|
||||
list($header, $payload, $signature) = $tokenArray;
|
||||
|
||||
// 验证签名
|
||||
if (self::signature($header . '.' . $payload, self::$secret) !== $signature) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 解码载荷
|
||||
$payload = json_decode(self::base64UrlDecode($payload), true);
|
||||
|
||||
// 验证是否过期
|
||||
if (isset($payload['exp']) && $payload['exp'] < time()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return $payload;
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成签名
|
||||
* @param string $input 输入
|
||||
* @param string $key 密钥
|
||||
* @return string
|
||||
*/
|
||||
private static function signature($input, $key)
|
||||
{
|
||||
return self::base64UrlEncode(hash_hmac('sha256', $input, $key, true));
|
||||
}
|
||||
|
||||
/**
|
||||
* URL安全的Base64编码
|
||||
* @param string $input
|
||||
* @return string
|
||||
*/
|
||||
private static function base64UrlEncode($input)
|
||||
{
|
||||
return str_replace(['+', '/', '='], ['-', '_', ''], base64_encode($input));
|
||||
}
|
||||
|
||||
/**
|
||||
* URL安全的Base64解码
|
||||
* @param string $input
|
||||
* @return string
|
||||
*/
|
||||
private static function base64UrlDecode($input)
|
||||
{
|
||||
$remainder = strlen($input) % 4;
|
||||
if ($remainder) {
|
||||
$input .= str_repeat('=', 4 - $remainder);
|
||||
}
|
||||
return base64_decode(str_replace(['-', '_'], ['+', '/'], $input));
|
||||
}
|
||||
|
||||
/**
|
||||
* 从请求头中获取Token
|
||||
* @return string|null
|
||||
*/
|
||||
public static function getRequestToken()
|
||||
{
|
||||
$authorization = Request::header('Authorization');
|
||||
if (!$authorization) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// 检查Bearer前缀
|
||||
if (strpos($authorization, 'Bearer ') !== 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return substr($authorization, 7);
|
||||
}
|
||||
}
|
||||
46
Server/application/common/validate/Auth.php
Normal file
46
Server/application/common/validate/Auth.php
Normal file
@@ -0,0 +1,46 @@
|
||||
<?php
|
||||
namespace app\common\validate;
|
||||
|
||||
use think\Validate;
|
||||
|
||||
class Auth extends Validate
|
||||
{
|
||||
/**
|
||||
* 验证规则
|
||||
* @var array
|
||||
*/
|
||||
protected $rule = [
|
||||
'username' => 'require|length:3,32',
|
||||
'password' => 'require|length:6,32',
|
||||
'mobile' => 'require|mobile',
|
||||
'code' => 'require|length:6',
|
||||
'type' => 'require|in:login,register',
|
||||
];
|
||||
|
||||
/**
|
||||
* 错误信息
|
||||
* @var array
|
||||
*/
|
||||
protected $message = [
|
||||
'username.require' => '用户名不能为空',
|
||||
'username.length' => '用户名长度必须在3-32个字符之间',
|
||||
'password.require' => '密码不能为空',
|
||||
'password.length' => '密码长度必须在6-32个字符之间',
|
||||
'mobile.require' => '手机号不能为空',
|
||||
'mobile.mobile' => '手机号格式不正确',
|
||||
'code.require' => '验证码不能为空',
|
||||
'code.length' => '验证码必须是6位数字',
|
||||
'type.require' => '验证码类型不能为空',
|
||||
'type.in' => '验证码类型不正确',
|
||||
];
|
||||
|
||||
/**
|
||||
* 验证场景
|
||||
* @var array
|
||||
*/
|
||||
protected $scene = [
|
||||
'login' => ['username', 'password'],
|
||||
'mobile_login' => ['mobile', 'code'],
|
||||
'send_code' => ['mobile', 'type'],
|
||||
];
|
||||
}
|
||||
49
Server/application/http/middleware/JwtAuth.php
Normal file
49
Server/application/http/middleware/JwtAuth.php
Normal file
@@ -0,0 +1,49 @@
|
||||
<?php
|
||||
namespace app\http\middleware;
|
||||
|
||||
use app\common\util\JwtUtil;
|
||||
use think\facade\Log;
|
||||
|
||||
/**
|
||||
* JWT认证中间件
|
||||
*/
|
||||
class JwtAuth
|
||||
{
|
||||
/**
|
||||
* 处理请求
|
||||
* @param \think\Request $request
|
||||
* @param \Closure $next
|
||||
* @return mixed
|
||||
*/
|
||||
public function handle($request, \Closure $next)
|
||||
{
|
||||
// 获取Token
|
||||
$token = JwtUtil::getRequestToken();
|
||||
|
||||
// 验证Token
|
||||
if (!$token) {
|
||||
return json([
|
||||
'code' => 401,
|
||||
'msg' => '未授权访问,缺少有效的身份凭证',
|
||||
'data' => null
|
||||
])->header(['Content-Type' => 'application/json; charset=utf-8']);
|
||||
}
|
||||
|
||||
$payload = JwtUtil::verifyToken($token);
|
||||
if (!$payload) {
|
||||
return json([
|
||||
'code' => 401,
|
||||
'msg' => '授权已过期或无效',
|
||||
'data' => null
|
||||
])->header(['Content-Type' => 'application/json; charset=utf-8']);
|
||||
}
|
||||
|
||||
// 将用户信息附加到请求中
|
||||
$request->userInfo = $payload;
|
||||
|
||||
// 写入日志
|
||||
Log::info('JWT认证通过', ['user_id' => $payload['id'] ?? 0, 'username' => $payload['username'] ?? '']);
|
||||
|
||||
return $next($request);
|
||||
}
|
||||
}
|
||||
@@ -45,7 +45,7 @@ return [
|
||||
// 默认语言
|
||||
'default_lang' => 'zh-cn',
|
||||
// 应用类库后缀
|
||||
'class_suffix' => true,
|
||||
'class_suffix' => false,
|
||||
// 控制器类后缀
|
||||
'controller_suffix' => false,
|
||||
|
||||
@@ -54,9 +54,9 @@ return [
|
||||
// +----------------------------------------------------------------------
|
||||
|
||||
// 默认模块名
|
||||
'default_module' => 'frontend',
|
||||
'default_module' => 'index',
|
||||
// 禁止访问模块
|
||||
'deny_module_list' => ['common'],
|
||||
'deny_module_list' => [],
|
||||
// 默认控制器名
|
||||
'default_controller' => 'Index',
|
||||
// 默认操作名
|
||||
@@ -89,9 +89,9 @@ return [
|
||||
// IP代理获取标识
|
||||
'http_agent_ip' => 'X-REAL-IP',
|
||||
// URL伪静态后缀
|
||||
'url_html_suffix' => '',
|
||||
'url_html_suffix' => 'html',
|
||||
// URL普通方式参数 用于自动生成
|
||||
'url_common_param' => true,
|
||||
'url_common_param' => false,
|
||||
// URL参数方式 0 按名称成对解析 1 按顺序解析
|
||||
'url_param_type' => 0,
|
||||
// 是否开启路由延迟解析
|
||||
|
||||
@@ -11,27 +11,27 @@
|
||||
|
||||
return [
|
||||
// 数据库类型
|
||||
'type' => 'mysql',
|
||||
'type' => env('database.type', 'mysql'),
|
||||
// 服务器地址
|
||||
'hostname' => '127.0.0.1',
|
||||
'hostname' => env('database.hostname', '127.0.0.1'),
|
||||
// 数据库名
|
||||
'database' => 'yishi',
|
||||
'database' => env('database.database', 'yishi'),
|
||||
// 用户名
|
||||
'username' => 'yishi',
|
||||
'username' => env('database.username', 'root'),
|
||||
// 密码
|
||||
'password' => 'KcankSjjdZ5CsTC7',
|
||||
'password' => env('database.password', '123456'),
|
||||
// 端口
|
||||
'hostport' => '',
|
||||
'hostport' => env('database.hostport', '3306'),
|
||||
// 连接dsn
|
||||
'dsn' => '',
|
||||
// 数据库连接参数
|
||||
'params' => [],
|
||||
// 数据库编码默认采用utf8
|
||||
'charset' => 'utf8mb4',
|
||||
'charset' => env('database.charset', 'utf8mb4'),
|
||||
// 数据库表前缀
|
||||
'prefix' => 'tk_',
|
||||
'prefix' => env('database.prefix', 'tk_'),
|
||||
// 数据库调试模式
|
||||
'debug' => true,
|
||||
'debug' => env('database.debug', true),
|
||||
// 数据库部署方式:0 集中式(单一服务器),1 分布式(主从服务器)
|
||||
'deploy' => 0,
|
||||
// 数据库读写是否分离 主从式有效
|
||||
|
||||
@@ -15,4 +15,12 @@
|
||||
return [
|
||||
// 默认中间件命名空间
|
||||
'default_namespace' => 'app\\http\\middleware\\',
|
||||
|
||||
// 别名或分组
|
||||
'alias' => [
|
||||
'jwt' => 'JwtAuth',
|
||||
],
|
||||
|
||||
// 优先级设置,此数组中的中间件会按照数组中的顺序优先执行
|
||||
'priority' => [],
|
||||
];
|
||||
|
||||
@@ -1,8 +0,0 @@
|
||||
<IfModule mod_rewrite.c>
|
||||
Options +FollowSymlinks -Multiviews
|
||||
RewriteEngine On
|
||||
|
||||
RewriteCond %{REQUEST_FILENAME} !-d
|
||||
RewriteCond %{REQUEST_FILENAME} !-f
|
||||
RewriteRule ^(.*)$ index.php [L]
|
||||
</IfModule>
|
||||
|
||||
76
Server/public/api_test.php
Normal file
76
Server/public/api_test.php
Normal file
@@ -0,0 +1,76 @@
|
||||
<?php
|
||||
// API测试入口文件
|
||||
|
||||
// 检查URL参数
|
||||
$url = isset($_GET['url']) ? $_GET['url'] : '';
|
||||
$method = isset($_GET['method']) ? strtoupper($_GET['method']) : 'GET';
|
||||
$data = isset($_GET['data']) ? $_GET['data'] : '{}';
|
||||
|
||||
// 将JSON字符串转为PHP数组
|
||||
$jsonData = json_decode($data, true);
|
||||
if (json_last_error() !== JSON_ERROR_NONE) {
|
||||
$jsonData = [];
|
||||
}
|
||||
|
||||
// 获取token参数
|
||||
$token = isset($_GET['token']) ? $_GET['token'] : '';
|
||||
|
||||
echo '<h1>API测试工具</h1>';
|
||||
echo '<form method="get">';
|
||||
echo '<p>API路径: <input type="text" name="url" value="' . htmlspecialchars($url) . '" style="width:300px" placeholder="例如: api/auth/login"/></p>';
|
||||
echo '<p>请求方法: <select name="method">
|
||||
<option value="GET"' . ($method == 'GET' ? ' selected' : '') . '>GET</option>
|
||||
<option value="POST"' . ($method == 'POST' ? ' selected' : '') . '>POST</option>
|
||||
</select></p>';
|
||||
echo '<p>请求数据 (JSON): <textarea name="data" style="width:400px;height:100px">' . htmlspecialchars($data) . '</textarea></p>';
|
||||
echo '<p>Authorization Token: <input type="text" name="token" value="' . htmlspecialchars($token) . '" style="width:400px" placeholder="Bearer token..."/></p>';
|
||||
echo '<p><input type="submit" value="发送请求"/></p>';
|
||||
echo '</form>';
|
||||
|
||||
// 如果有URL参数,发送API请求
|
||||
if (!empty($url)) {
|
||||
// 构建完整URL
|
||||
$fullUrl = 'http://' . $_SERVER['HTTP_HOST'] . '/' . $url;
|
||||
|
||||
// 初始化cURL
|
||||
$ch = curl_init($fullUrl);
|
||||
|
||||
// 设置cURL选项
|
||||
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
||||
|
||||
// 设置请求方法
|
||||
if ($method == 'POST') {
|
||||
curl_setopt($ch, CURLOPT_POST, true);
|
||||
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($jsonData));
|
||||
}
|
||||
|
||||
// 设置请求头
|
||||
$headers = ['Content-Type: application/json'];
|
||||
if (!empty($token)) {
|
||||
$headers[] = 'Authorization: ' . $token;
|
||||
}
|
||||
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
|
||||
|
||||
// 执行请求
|
||||
$result = curl_exec($ch);
|
||||
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||||
|
||||
// 检查是否有错误
|
||||
if (curl_errno($ch)) {
|
||||
echo '<h2>请求错误</h2>';
|
||||
echo '<pre>' . htmlspecialchars(curl_error($ch)) . '</pre>';
|
||||
} else {
|
||||
echo '<h2>响应结果 (HTTP状态码: ' . $httpCode . ')</h2>';
|
||||
echo '<pre>' . htmlspecialchars($result) . '</pre>';
|
||||
|
||||
// 尝试解析JSON
|
||||
$jsonResult = json_decode($result, true);
|
||||
if (json_last_error() === JSON_ERROR_NONE) {
|
||||
echo '<h2>格式化JSON响应</h2>';
|
||||
echo '<pre>' . htmlspecialchars(json_encode($jsonResult, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE)) . '</pre>';
|
||||
}
|
||||
}
|
||||
|
||||
// 关闭cURL资源
|
||||
curl_close($ch);
|
||||
}
|
||||
123
Server/public/login.html
Normal file
123
Server/public/login.html
Normal file
@@ -0,0 +1,123 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>登录测试</title>
|
||||
<style>
|
||||
body {
|
||||
font-family: Arial, sans-serif;
|
||||
line-height: 1.6;
|
||||
max-width: 600px;
|
||||
margin: 0 auto;
|
||||
padding: 20px;
|
||||
}
|
||||
.form-group {
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
label {
|
||||
display: block;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
input[type="text"],
|
||||
input[type="password"] {
|
||||
width: 100%;
|
||||
padding: 8px;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 4px;
|
||||
}
|
||||
button {
|
||||
background-color: #4CAF50;
|
||||
color: white;
|
||||
padding: 10px 15px;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
}
|
||||
button:hover {
|
||||
background-color: #45a049;
|
||||
}
|
||||
#result {
|
||||
margin-top: 20px;
|
||||
padding: 15px;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 4px;
|
||||
background-color: #f9f9f9;
|
||||
min-height: 100px;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h1>JWT登录测试</h1>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="username">用户名:</label>
|
||||
<input type="text" id="username" value="admin">
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="password">密码:</label>
|
||||
<input type="password" id="password" value="123456">
|
||||
</div>
|
||||
|
||||
<button id="loginBtn">登录</button>
|
||||
<button id="infoBtn">获取用户信息</button>
|
||||
|
||||
<div id="result">
|
||||
<p>响应结果将显示在这里</p>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
document.getElementById('loginBtn').addEventListener('click', login);
|
||||
document.getElementById('infoBtn').addEventListener('click', getUserInfo);
|
||||
|
||||
let token = '';
|
||||
|
||||
async function login() {
|
||||
const username = document.getElementById('username').value;
|
||||
const password = document.getElementById('password').value;
|
||||
|
||||
try {
|
||||
const response = await fetch('/api/auth/login', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify({ username, password })
|
||||
});
|
||||
|
||||
const data = await response.json();
|
||||
document.getElementById('result').innerHTML = `<pre>${JSON.stringify(data, null, 2)}</pre>`;
|
||||
|
||||
if (data.code === 200 && data.data.token) {
|
||||
token = data.data.token;
|
||||
console.log('Token stored:', token);
|
||||
}
|
||||
} catch (error) {
|
||||
document.getElementById('result').innerHTML = `<p>Error: ${error.message}</p>`;
|
||||
}
|
||||
}
|
||||
|
||||
async function getUserInfo() {
|
||||
if (!token) {
|
||||
document.getElementById('result').innerHTML = '<p>请先登录获取Token</p>';
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await fetch('/api/auth/info', {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'Authorization': `Bearer ${token}`
|
||||
}
|
||||
});
|
||||
|
||||
const data = await response.json();
|
||||
document.getElementById('result').innerHTML = `<pre>${JSON.stringify(data, null, 2)}</pre>`;
|
||||
} catch (error) {
|
||||
document.getElementById('result').innerHTML = `<p>Error: ${error.message}</p>`;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
8
Server/public/nginx.htaccess
Normal file
8
Server/public/nginx.htaccess
Normal file
@@ -0,0 +1,8 @@
|
||||
location ~* (runtime|application)/{
|
||||
return 403;
|
||||
}
|
||||
location / {
|
||||
if (!-e $request_filename){
|
||||
rewrite ^(.*)$ /index.php?s=$1 last; break;
|
||||
}
|
||||
}
|
||||
36
Server/public/test.php
Normal file
36
Server/public/test.php
Normal file
@@ -0,0 +1,36 @@
|
||||
<?php
|
||||
// 测试登录API
|
||||
|
||||
$url = 'http://localhost/api/auth/login';
|
||||
$data = json_encode([
|
||||
'username' => 'admin',
|
||||
'password' => '123456'
|
||||
]);
|
||||
|
||||
// 初始化cURL
|
||||
$ch = curl_init($url);
|
||||
|
||||
// 设置cURL选项
|
||||
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
||||
curl_setopt($ch, CURLOPT_POST, true);
|
||||
curl_setopt($ch, CURLOPT_POSTFIELDS, $data);
|
||||
curl_setopt($ch, CURLOPT_HTTPHEADER, [
|
||||
'Content-Type: application/json',
|
||||
'Content-Length: ' . strlen($data)
|
||||
]);
|
||||
|
||||
// 执行请求
|
||||
$result = curl_exec($ch);
|
||||
|
||||
// 检查是否有错误
|
||||
if (curl_errno($ch)) {
|
||||
echo '请求错误: ' . curl_error($ch);
|
||||
} else {
|
||||
// 输出结果
|
||||
echo '<pre>';
|
||||
print_r(json_decode($result, true));
|
||||
echo '</pre>';
|
||||
}
|
||||
|
||||
// 关闭cURL资源
|
||||
curl_close($ch);
|
||||
@@ -9,12 +9,15 @@
|
||||
// | Author: liu21st <liu21st@gmail.com>
|
||||
// +----------------------------------------------------------------------
|
||||
|
||||
use think\facade\Route;
|
||||
|
||||
Route::get('think', function () {
|
||||
return 'hello,ThinkPHP5!';
|
||||
});
|
||||
|
||||
Route::get('hello/:name', 'index/hello');
|
||||
|
||||
return [
|
||||
// 加载Common模块路由配置
|
||||
include __DIR__ . '/../application/common/config/route.php';
|
||||
|
||||
];
|
||||
return [];
|
||||
|
||||
Reference in New Issue
Block a user