diff --git a/.Cursorignore b/.Cursorignore
new file mode 100644
index 00000000..a78c99e4
--- /dev/null
+++ b/.Cursorignore
@@ -0,0 +1,9 @@
+Server/runtime/
+*.png
+*.jpg
+*.jpeg
+*.gif
+*.bmp
+*.webp
+*.ico
+*.svg
\ No newline at end of file
diff --git a/Backend/.env b/Backend/.env
index 41a988e0..75531f85 100755
--- a/Backend/.env
+++ b/Backend/.env
@@ -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="管理后台"
\ No newline at end of file
+VUE_APP_WEBSITE_NAME="管理后台"
+VUE_APP_TITLE=医师管理系统
+VUE_APP_API_URL=http://yishi.com
\ No newline at end of file
diff --git a/Backend/.env.development b/Backend/.env.development
index a7a8df28..f04e7d78 100755
--- a/Backend/.env.development
+++ b/Backend/.env.development
@@ -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="管理后台"
\ No newline at end of file
+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=医师管理系统
\ No newline at end of file
diff --git a/Backend/src/api/user.js b/Backend/src/api/user.js
index 2f4a44af..48582fea 100755
--- a/Backend/src/api/user.js
+++ b/Backend/src/api/user.js
@@ -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 => {
diff --git a/Backend/src/router/auth.js b/Backend/src/router/auth.js
index 729c249f..5d1b4e04 100755
--- a/Backend/src/router/auth.js
+++ b/Backend/src/router/auth.js
@@ -7,7 +7,7 @@ export default {
{
path: '/auth/login',
meta: {
- title: '账号登录?',
+ title: '账号登录',
needLogin: false,
},
component: () => import('@/views/auth/login'),
diff --git a/Backend/src/router/index.js b/Backend/src/router/index.js
index a37431d5..c73087f3 100755
--- a/Backend/src/router/index.js
+++ b/Backend/src/router/index.js
@@ -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
diff --git a/Backend/src/store/modules/user.js b/Backend/src/store/modules/user.js
index 280df559..e5c50a58 100755
--- a/Backend/src/store/modules/user.js
+++ b/Backend/src/store/modules/user.js
@@ -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)
+ })
+ })
+ },
},
}
diff --git a/Backend/src/utils/auth.js b/Backend/src/utils/auth.js
index 750065e6..ea517fd6 100755
--- a/Backend/src/utils/auth.js
+++ b/Backend/src/utils/auth.js
@@ -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()
+}
+
/**
* 设置用户信息
*
diff --git a/Backend/src/utils/request.js b/Backend/src/utils/request.js
index d808a123..daa2d112 100755
--- a/Backend/src/utils/request.js
+++ b/Backend/src/utils/request.js
@@ -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
diff --git a/Backend/src/views/auth/login.vue b/Backend/src/views/auth/login.vue
index f190bab4..532b63d2 100755
--- a/Backend/src/views/auth/login.vue
+++ b/Backend/src/views/auth/login.vue
@@ -35,7 +35,7 @@
+
+
\ No newline at end of file
diff --git a/Server/.env b/Server/.env
new file mode 100644
index 00000000..c55ec3e5
--- /dev/null
+++ b/Server/.env
@@ -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
\ No newline at end of file
diff --git a/Server/application/common/config/route.php b/Server/application/common/config/route.php
new file mode 100644
index 00000000..c2b319f9
--- /dev/null
+++ b/Server/application/common/config/route.php
@@ -0,0 +1,25 @@
+ 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 [];
\ No newline at end of file
diff --git a/Server/application/common/controller/Auth.php b/Server/application/common/controller/Auth.php
new file mode 100644
index 00000000..787f93ec
--- /dev/null
+++ b/Server/application/common/controller/Auth.php
@@ -0,0 +1,104 @@
+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());
+ }
+ }
+}
\ No newline at end of file
diff --git a/Server/application/common/helper/ResponseHelper.php b/Server/application/common/helper/ResponseHelper.php
new file mode 100644
index 00000000..2b0966e2
--- /dev/null
+++ b/Server/application/common/helper/ResponseHelper.php
@@ -0,0 +1,57 @@
+ $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);
+ }
+}
\ No newline at end of file
diff --git a/Server/application/common/model/User.php b/Server/application/common/model/User.php
new file mode 100644
index 00000000..5c995ac0
--- /dev/null
+++ b/Server/application/common/model/User.php
@@ -0,0 +1,79 @@
+ 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;
+ }
+}
\ No newline at end of file
diff --git a/Server/application/common/service/AuthService.php b/Server/application/common/service/AuthService.php
new file mode 100644
index 00000000..e86d216a
--- /dev/null
+++ b/Server/application/common/service/AuthService.php
@@ -0,0 +1,155 @@
+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
+ ];
+ }
+}
\ No newline at end of file
diff --git a/Server/application/common/service/SmsService.php b/Server/application/common/service/SmsService.php
new file mode 100644
index 00000000..35cd3f22
--- /dev/null
+++ b/Server/application/common/service/SmsService.php
@@ -0,0 +1,124 @@
+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;
+ }
+}
\ No newline at end of file
diff --git a/Server/application/common/util/JwtUtil.php b/Server/application/common/util/JwtUtil.php
new file mode 100644
index 00000000..2ba8fb5f
--- /dev/null
+++ b/Server/application/common/util/JwtUtil.php
@@ -0,0 +1,135 @@
+ '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);
+ }
+}
\ No newline at end of file
diff --git a/Server/application/common/validate/Auth.php b/Server/application/common/validate/Auth.php
new file mode 100644
index 00000000..ff381ad4
--- /dev/null
+++ b/Server/application/common/validate/Auth.php
@@ -0,0 +1,46 @@
+ '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'],
+ ];
+}
\ No newline at end of file
diff --git a/Server/application/http/middleware/JwtAuth.php b/Server/application/http/middleware/JwtAuth.php
new file mode 100644
index 00000000..7410302a
--- /dev/null
+++ b/Server/application/http/middleware/JwtAuth.php
@@ -0,0 +1,49 @@
+ 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);
+ }
+}
\ No newline at end of file
diff --git a/Server/config/app.php b/Server/config/app.php
index d8009b70..f0c83f5c 100755
--- a/Server/config/app.php
+++ b/Server/config/app.php
@@ -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,
// 是否开启路由延迟解析
diff --git a/Server/config/database.php b/Server/config/database.php
index 971ddc3c..8eb199f7 100755
--- a/Server/config/database.php
+++ b/Server/config/database.php
@@ -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,
// 数据库读写是否分离 主从式有效
diff --git a/Server/config/middleware.php b/Server/config/middleware.php
index fe15ec3d..2edfee13 100755
--- a/Server/config/middleware.php
+++ b/Server/config/middleware.php
@@ -15,4 +15,12 @@
return [
// 默认中间件命名空间
'default_namespace' => 'app\\http\\middleware\\',
+
+ // 别名或分组
+ 'alias' => [
+ 'jwt' => 'JwtAuth',
+ ],
+
+ // 优先级设置,此数组中的中间件会按照数组中的顺序优先执行
+ 'priority' => [],
];
diff --git a/Server/public/.htaccess b/Server/public/.htaccess
index f4eba086..e69de29b 100755
--- a/Server/public/.htaccess
+++ b/Server/public/.htaccess
@@ -1,8 +0,0 @@
-
' . htmlspecialchars(curl_error($ch)) . ''; + } else { + echo '
' . htmlspecialchars($result) . ''; + + // 尝试解析JSON + $jsonResult = json_decode($result, true); + if (json_last_error() === JSON_ERROR_NONE) { + echo '
' . htmlspecialchars(json_encode($jsonResult, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE)) . ''; + } + } + + // 关闭cURL资源 + curl_close($ch); +} \ No newline at end of file diff --git a/Server/public/login.html b/Server/public/login.html new file mode 100644 index 00000000..fabec674 --- /dev/null +++ b/Server/public/login.html @@ -0,0 +1,123 @@ + + + + + +
响应结果将显示在这里
+'; + print_r(json_decode($result, true)); + echo ''; +} + +// 关闭cURL资源 +curl_close($ch); \ No newline at end of file diff --git a/Server/route/route.php b/Server/route/route.php index 6f479d30..2fe062ec 100755 --- a/Server/route/route.php +++ b/Server/route/route.php @@ -9,12 +9,15 @@ // | Author: liu21st