zzb 2 жил өмнө
parent
commit
134831a8c3

+ 5 - 0
app/admin/controller/Webconfig.php

@@ -57,4 +57,9 @@ class Webconfig extends Permissions
             }
         }
     }
+
+    public function appointmentConfig()
+    {
+
+    }
 }

+ 206 - 0
app/admin/view/webconfig/appointment_config.html

@@ -0,0 +1,206 @@
+<!DOCTYPE html>
+<html>
+<head>
+    <meta charset="utf-8">
+    <title>layui</title>
+    <meta name="renderer" content="webkit">
+    <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
+    <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
+    <link rel="stylesheet" href="__PUBLIC__/layui/css/layui.css" media="all">
+    <link rel="stylesheet" href="__PUBLIC__/font-awesome/css/font-awesome.min.css" media="all"/>
+    <link rel="stylesheet" href="__CSS__/admin.css" media="all">
+</head>
+<body style="padding:10px;">
+<div class="tplay-body-div">
+
+    {include file="webconfig/tab"}
+
+    <div style="margin-top: 20px;">
+    </div>
+    <form class="layui-form" id="admin">
+
+        <div class="layui-col-md12">
+            <div class="layui-form-item">
+                <label class="layui-form-label">系统别名</label>
+                <div class="layui-input-inline" style="max-width: 400px">
+                    <input name="name" placeholder="请输入" autocomplete="off" class="layui-input"
+                           type="text" value="{:systemName()}">
+                </div>
+                <div class="layui-form-mid layui-word-aux">给后台取个别名</div>
+            </div>
+
+            <div class="layui-form-item">
+                <label class="layui-form-label">安全入口</label>
+                <div class="layui-input-inline">
+                    <input placeholder="请输入" autocomplete="off" class="layui-input"
+                           type="text" disabled value="{$backend_pass}">
+                </div>
+                <div class="layui-input-inline" style="width: 35px;">
+                    <button type="button" class="layui-btn layui-btn-xs" style="margin-top: 8px;"
+                            onclick="set_pass()">设置
+                    </button>
+                </div>
+                <div class="layui-form-mid layui-word-aux">设置后只能通过安全入口登录后台</div>
+            </div>
+
+            <div class="layui-form-item">
+                <label class="layui-form-label">操作日志</label>
+                <div class="layui-input-inline" style="width: 73px;">
+                    <input name="is_log" lay-skin="switch" lay-filter="switchTest" lay-text="开启|关闭" type="checkbox"
+                           {if condition="$web_config.is_log eq 1" }checked="" {/if} value="1">
+                </div>
+                <div class="layui-input-inline">
+                    <div class="layui-form-mid layui-word-aux">当前日志数:{$admin_log_num}</div>
+                </div>
+            </div>
+
+            <div class="layui-form-item">
+                <label class="layui-form-label">网站维护</label>
+                <div class="layui-input-inline" style="width:73px">
+                    <input name="is_close_site" lay-skin="switch" lay-text="开启|关闭" type="checkbox"
+                           {if condition="$web_config.is_close_site eq 1" }checked="" {/if} value="1">
+                </div>
+                <div class="layui-form-mid layui-word-aux">开启后可使用维护入口访问:{:url('/', '', false,
+                    true)}?key={$is_close_site_key}
+                </div>
+            </div>
+
+
+            <div class="layui-form-item">
+                <label class="layui-form-label">上传类型</label>
+                <div class="layui-input-inline" style="max-width: 400px">
+                    <input name="file_type" lay-verify="pass" placeholder="多个类型请用(英文,逗号)隔开" autocomplete="off" class="layui-input"
+                           type="text" value="{$web_config.file_type}">
+                </div>
+                <div class="layui-form-mid layui-word-aux">限制上传类型(注意:不限制ueditor中上传)</div>
+            </div>
+
+            <div class="layui-form-item">
+                <label class="layui-form-label">最大上传值</label>
+                <div class="layui-input-inline" style="max-width: 600px">
+                    <input name="file_size" lay-verify="pass" placeholder="单位kb" autocomplete="off" class="layui-input"
+                           type="number" value="{$web_config.file_size}">
+                </div>
+                <div class="layui-form-mid layui-word-aux">单位KB ( php.ini 的 post_max_size、upload_max_filesize
+                    也会限制上传大小 )
+                </div>
+            </div>
+            <div class="layui-form-item">
+                <label class="layui-form-label">文章编辑器</label>
+                <div class="layui-input-inline" style="width: 500px">
+                    <input type="radio" name="article_editor" value="wangEditor" title="wangEditor" {if condition="$web_config.article_editor eq 'wangEditor'"}checked{/if}>
+                    <input type="radio" name="article_editor" value="tinymce" title="tinymce" {if condition="$web_config.article_editor eq 'tinymce'"}checked{/if}>
+                    <input type="radio" name="article_editor" value="ueditor" title="ueditor" {if condition="$web_config.article_editor eq 'ueditor'"}checked{/if}>
+                    <input type="radio" name="article_editor" value="markdown" title="markdown" {if condition="$web_config.article_editor eq 'markdown'"}checked{/if}>
+                </div>
+            </div>
+        </div>
+
+        <div class="layui-form-item">
+            <div class="layui-input-block">
+                <button class="layui-btn" lay-submit lay-filter="admin">立即提交</button>
+                <button type="reset" class="layui-btn layui-btn-primary">重置</button>
+            </div>
+        </div>
+    </form>
+
+
+    {include file="public/foot"}
+
+    <form id="setForm" style="display:none;margin: 10px 30px;" class="layui-form alert-form">
+        <div class="layui-form-item">
+            <label class="layui-form-label">入口密码</label>
+            <div class="layui-input-inline">
+                <input id="login_pass" autocomplete="off" placeholder="请输入" class="layui-input" type="text">
+            </div>
+            <div class="layui-input-inline" style="width: 60px;">
+                <button type="button" class="layui-btn layui-btn-xs" style="margin-top: 8px;"
+                        onclick="$('#login_pass').val(randomPassword(20))">随机
+                </button>
+            </div>
+        </div>
+    </form>
+
+    <script>
+        layui.use(['layer', 'form'], function () {
+            var layer = layui.layer,
+                $ = layui.jquery,
+                form = layui.form;
+            $(window).on('load', function () {
+                form.on('submit(admin)', function (data) {
+                    $.ajax({
+                        url: "{:url('admin/webconfig/publish')}",
+                        data: $('#admin').serialize(),
+                        type: 'post',
+                        dataType: 'json',
+                        async: false,
+                        success: function (res) {
+                            layer.msg(res.msg);
+                            if (res.code == 1) {
+                                setTimeout(function () {
+                                    location.reload();
+                                }, 1500)
+                            }
+                        }
+                    })
+                    return false;
+                });
+            });
+        });
+
+    </script>
+    <script>
+        function set_pass() {
+            var fromobj = $('#setForm');
+            layer.open({
+                type: 1,
+                title: '设置安全入口',
+                area: ['450px', '180px'],
+                id: 'layerDemo', //防止重复弹出
+                content: fromobj,
+                btn: ['提交', '关闭'],
+                btnAlign: 'r', //按钮居中
+                yes: function (index, layero) {
+                    save_pass();
+                }, btn2: function (index) {
+                }, zIndex: 999 //重点1
+            });
+        }
+
+        function save_pass() {
+            var login_pass = $("#login_pass").val();
+            var aliases = login_pass == "" ? "admin_login" : "admin_login/" + login_pass + "$";
+            layer.confirm("确定修改?", function (index) {
+                $.ajax({
+                    url: "{:url('admin/urlsconfig/publish')}",
+                    data: {
+                        id: 1,
+                        status: 1,
+                        url: "admin/common/login",
+                        desc: "后台登录地址",
+                        aliases: aliases
+                    },
+                    dataType: 'json',
+                    type: 'post',
+                    success: function (res) {
+                        if (res.code == 1) {
+                            var text = "设置成功,请保存新的登录地址:<br/> {:url('/', '', false, true)}admin_login/" + login_pass;
+                            layer.alert(text, {icon: 1}, function (index) {
+                                layer.confirm('立即重新登录?', function (index) {
+                                    window.parent.document.getElementById('logout').click();
+                                }, function (index) {
+                                    location.reload()
+                                })
+                            })
+                        }else{
+                            layer.msg(res.msg);
+                        }
+                    }
+                })
+            })
+            return false;
+        }
+    </script>
+</div>
+</body>
+</html>

+ 72 - 24
app/api/controller/Common.php

@@ -10,19 +10,39 @@ namespace app\api\controller;
 
 
 use app\api\controller\base\Base;
+use app\api\controller\base\Permissions;
 use app\common\model\User;
 
 class Common extends Base
 {
-    //注册
-    public function register()
+    //登入
+    public function login()
     {
+        $return_url = $this->request->param('return_url', '');
+        session('return_url', $return_url, 'login');
+
+        $callback = urlencode(url('/api/common/loginNotify', '', false, true));
+        $state = md5('appointment' . time());
+        session('state', $state, 'login');
+
+        $url = "https://www.jucai.gov.cn/api/auth/wechat_auth?url=$callback&state=$state";
+        $this->redirect($url);
+    }
+
+    public function loginNotify()
+    {
+        $mystate = session('state', '', 'login');
+        $state = $this->request->param('state');
+        if (!$mystate || !$state || $mystate != $state) {
+            $this->json_error('登入失败,请重新登入');
+        }
+
         $post = $this->request->param();
         $validate = new \think\Validate([
             ['openid', 'max:50'],
             ['unionid', 'max:50'],
-            ['nickname|昵称', 'require|max:50'],
-            ['head_pic|头像', 'require|max:255'],
+            ['nickname|昵称', 'max:50'],
+            ['head_pic|头像', 'max:255'],
             ['sex|性别', 'in:1,2'],
             ['country|国家', 'max:50'],
             ['province|省份', 'max:50'],
@@ -31,27 +51,55 @@ class Common extends Base
         if (!$validate->check($post)) {
             $this->json_error('提交失败:' . $validate->getError());
         }
-        $user = new User();
-        $data = [
-            'openid' => $post['openid']??'',
-            'unionid' => $post['unionid']??'',
-            'passport' => $post['openid']??'',
-            'nickname' => $post['nickname'],
-            'user_type' => User::TYPE_WECHAT,
-            'user_cate' => User::CATE_USER,
-            'head_pic' => $post['head_pic'],
-            'status' => User::STATUS_PASS,
-            'ip' => $this->request->ip(),
-            'sex' => $post['sex']??0,
-            'country' => $post['country']??'',
-            'province' => $post['province']??'',
-            'city' => $post['city']??'',
-        ];
-        if (false == $user->allowField(true)->save($data)) {
-            $this->json_error('添加失败');
+
+        $unionid = $this->request->param('unionid');
+        $passport = $unionid;
+        $user = User::get(['user_type' => User::TYPE_WECHAT, 'passport' => $unionid]);
+        if (!$user) {
+            $openid = $this->request->param('openid');
+            $passport = $openid;
+            $user = User::get(['user_type' => User::TYPE_WECHAT, 'passport' => $openid, 'unionid' => $unionid]);
+            if (!$user) {
+                $user = User::get(['user_type' => User::TYPE_WECHAT, 'passport' => $openid]);
+            }
+        }
+        if (!$passport) {
+            $this->json_error('openid 不能为空');
+        }
+        if (!$user) {
+            //注册
+            $user = new User();
+            $data = [
+                'openid' => $post['openid']??'',
+                'unionid' => $post['unionid']??'',
+                'passport' => $passport,
+                'nickname' => $post['nickname']??'',
+                'user_type' => User::TYPE_WECHAT,
+                'user_cate' => User::CATE_USER,
+                'head_pic' => $post['head_pic']??'',
+                'status' => User::STATUS_PASS,
+                'ip' => $this->request->ip(),
+                'sex' => $post['sex']??0,
+                'country' => $post['country']??'',
+                'province' => $post['province']??'',
+                'city' => $post['city']??'',
+                "login_time" => time(),
+                "create_time" => time()
+            ];
+            if (false == $user->allowField(true)->save($data)) {
+                $this->json_error('添加失败');
+            }
         } else {
-            $this->json_success('添加成功', 'index');
+            $data = [
+                "login_time" => time(),
+                'openid' => $post['openid']??'',
+                'unionid' => $post['unionid']??'',
+            ];
+            $user->allowField(true)->save($data);
         }
-    }
 
+        //登入成功 ,返回 前端
+        $return_url = session('return_url', '', 'login');
+        $this->redirect($return_url . '?jwt=' . Permissions::createJwt($user->id, $user->login_time));
+    }
 }

+ 2 - 2
app/api/controller/User.php

@@ -17,7 +17,7 @@ class User extends Permissions
     //个人资料接口
     public function info()
     {
-        $this->json_success('success', $this->user);
+        $this->json_success('success', $this->getUser());
     }
 
     //提交反馈接口
@@ -33,7 +33,7 @@ class User extends Permissions
         }
 
         $model = new Feedback();
-        $post['user_id'] = $this->user->id;
+        $post['user_id'] = $this->getUserId();
         $res = $model->allowField(true)->save($post);
         if ($res) {
             $this->json_success("创建成功", $model);

+ 30 - 14
app/api/controller/base/Base.php

@@ -10,10 +10,12 @@ namespace app\api\controller\base;
 
 use app\common\service\WebService;
 use think\Controller;
+use think\exception\HttpResponseException;
+use think\Request;
 
 /**
  * 接口父类
- * Class Api
+ * Class
  * @package app\api\controller\base
  */
 class Base extends Controller
@@ -27,26 +29,40 @@ class Base extends Controller
 
     /**
      * api成功的响应
-     * 和success方法相比,没有显示跳转页面,直接响应json
-     * @param $msg string [提示消息]
-     * @param $data null [响应数据]
-     * @param int $code
+     * @param $msg string 成功消息
+     * @param $data null 响应数据
+     * @param $err_code int 消息码
      */
-    protected function json_success($msg = "success", $data = null, $code = 1)
+    protected function json_success($msg = "success", $data = null, $err_code = 0)
     {
-        //和success方法返回的code一致,这样后台ajax不用修改就能兼容
-        $this->result($data, $code, $msg, 'json');
+        //和success方法相比,没有显示跳转页面,直接响应了json
+        //和success方法返回的code要一致,这样前端ajax不用修改就能兼容
+        $result = [
+            'code' => 1,
+            'err_code' => $err_code,
+            'msg' => $msg,
+            'time' => Request::instance()->server('REQUEST_TIME'),
+            'data' => $data,
+        ];
+        throw new HttpResponseException(json($result));
     }
 
     /**
-     * api失败的响应,可定义错误代码
-     * @param $msg [错误消息]
-     * @param int $code [自定义错误码,不可为1]
-     * @param null $data [响应数据]
+     * api失败的响应
+     * @param $msg string 错误消息
+     * @param null $data 响应数据
+     * @param $err_code int 消息码
      */
-    protected function json_error($msg = "error", $data = null, $code = 0)
+    protected function json_error($msg = "error", $data = null, $err_code = 0)
     {
-        $this->result($data, $code, $msg, 'json');
+        $result = [
+            'code' => 0,
+            'err_code' => $err_code,
+            'msg' => $msg,
+            'time' => Request::instance()->server('REQUEST_TIME'),
+            'data' => $data,
+        ];
+        throw new HttpResponseException(json($result));
     }
 
 }

+ 138 - 13
app/api/controller/base/Permissions.php

@@ -8,6 +8,12 @@
 
 namespace app\api\controller\base;
 
+use app\common\model\User;
+use Exception;
+use Firebase\JWT\JWT;
+use Firebase\JWT\Key;
+use think\exception\HttpResponseException;
+
 /**
  * 登入鉴权的接口父类
  * Class Permissions
@@ -15,26 +21,145 @@ namespace app\api\controller\base;
  */
 class Permissions extends Base
 {
-    protected $user;
+    const JWT_COOKIE_NAME = 'x-token';
+    const JWT_SESSION_NAME = 'user_info';
+
+    private $userId;
+    private $user;
 
     protected function _initialize()
     {
-        $unionid = $this->request->param('unionid');
-        if (!$unionid) {
-            $openid = $this->request->param('openid');
-            if (!$openid) {
-                $this->json_error('请先登入');
+        parent::_initialize();
+        //get token from header param body
+        $jwt = $this->request->header('x-token', "");
+        if (!$jwt) {
+            $jwt = $this->request->param('x-token', "");
+            if (!$jwt) {
+                $callbackBody = file_get_contents('php://input');
+                if (!$callbackBody) {
+                    $this->json_error('请先登入');
+                }
+                $callbackJson = json_decode($callbackBody, true);
+                $jwt = isset($callbackJson['x-token']) ? $callbackJson['x-token'] : '';
+                if (!$jwt) {
+                    $this->json_error('请先登入');
+                }
             }
-            $user = \app\common\model\User::get(['openid' => $openid]);
-            if (!$user) {
-                $this->json_error('账号不存在');
+        }
+
+        //check x-token
+        try {
+            JWT::$leeway = 60;
+            $jwt_key = system_salt();
+            $decoded = JWT::decode($jwt, new Key($jwt_key, 'HS256'));
+
+            $arr = (array)$decoded;
+            if (!isset($arr['exp']) || $arr['exp'] < time()) {
+                self::clear_session();
+                throw new \think\Exception('登入超时,请重新登录');
+            } else {
+                //鉴权成功
+                $this->userId = $arr['data']->userId;
+                $loginTime = $arr['data']->loginTime;
+                $exptime = $arr['exp'];
+                //限制账号多次登入
+//                if ($this->getUser()->getData('login_time') > $loginTime) {
+//                    $this->clear_session();
+//                    throw new \think\Exception('您的账号已经在其它地方登入');
+//                }
+                // 每次刷新jwt,loginTime要保持不变
+                self::createJwt($this->userId, $loginTime, 3600, $exptime);
+                return;
             }
+        } catch (Exception $e) {
+            $this->json_error($this->getErrorMsg($e));
         }
-        $user = \app\common\model\User::get(['unionid' => $unionid]);
-        if (!$user) {
-            $this->json_error('账号不存在');
+    }
+
+    private function getErrorMsg(Exception $e)
+    {
+        if ($e instanceof HttpResponseException) {
+            return $e->getResponse()->getData()['msg'];
+        }
+        switch ($e->getMessage()) {
+            case "Expired token":
+                self::clear_session();
+                $msg = '登入超时,请重新登录';
+                break;
+            case "Wrong number of segments":
+                self::clear_session();
+                $msg = 'Token验证失败,请重新登录';
+                break;
+            default:
+                $msg = "error:" . $e->getMessage();
+        }
+        return $msg;
+    }
+
+    /**
+     * 获取登入token,并设置登入状态
+     * @param $userId int 用户id
+     * @param $loginTime int 用户登入时间
+     * @param int $expire 设置jwt过期时间,从当前时间开始计算
+     * @param null $exp 强制指定过期时间,用来刷新jwt
+     * @return string
+     */
+    public static function createJwt($userId, $loginTime, $expire = 3600, $exp = null)
+    {
+        $nowtime = time();
+        $exptime = $exp ? $exp : $nowtime + $expire;
+        //添加 exptime,后端可以判断过期并退出, 不通过session
+        $data = ['userId' => $userId, 'loginTime' => $loginTime, 'exptime' => $exptime];
+        $token = [
+            'iss' => PRODUCT_URL, //签发者
+            'aud' => PRODUCT_URL, //jwt所面向的用户
+            'iat' => $nowtime, //签发时间
+            'nbf' => $nowtime + 10, //在什么时间之后该jwt才可用
+            'exp' => $exptime, //过期时间
+            'data' => $data //不要存放隐私信息,jwt可以保证内容不被修改,但可以被解码查看内容
+        ];
+        // 得到jwt
+        $jwt_key = system_salt();
+        $jwt = JWT::encode($token, $jwt_key, 'HS256');
+
+        //设置登入状态
+        session(self::JWT_SESSION_NAME, $data);
+        cookie(self::JWT_COOKIE_NAME, $jwt, 3600 * 12);
+        return $jwt;
+    }
+
+    /**
+     * 获取user对象
+     * @return null|User
+     */
+    protected function getUser()
+    {
+        if (!$this->user) {
+            $user = User::get($this->userId);
+            if (!$user) {
+                $this->json_error('找不到用户信息,请重新登入');
+            }
+            $this->user = $user;
         }
+        return $this->user;
+    }
+
+    /**
+     * 获取userid
+     * @return null
+     */
+    protected function getUserId()
+    {
+        return $this->userId;
+    }
+
 
-        $this->user = $user;
+    /**
+     * 退出登入状态
+     */
+    public static function clear_session()
+    {
+        cookie(self::JWT_COOKIE_NAME, null);
+        session(self::JWT_SESSION_NAME, null);
     }
 }

+ 17 - 0
app/api/controller/User.md → app/api/controller/接口文档.md

@@ -1,3 +1,20 @@
+# 登入接口
+接口地址:/api/common/login
+
+请求方式:get
+
+请求数据:
+
+| 参数名 | 说明  | 备注  |
+| ---   | ---   | ---  |
+| return_url | 前端的回调地址 |  max:500 |
+
+响应数据:
+```
+登入成功将跳到地址: return_url?jwt={登入令牌}
+前端需获取jwt,用于后续部分用户权限接口
+```
+
 
 # 提交反馈接口
 接口地址:/api/user/feedback

Файлын зөрүү хэтэрхий том тул дарагдсан байна
+ 213 - 35
db.sql


Энэ ялгаанд хэт олон файл өөрчлөгдсөн тул зарим файлыг харуулаагүй болно