1
0
mirror of https://gitee.com/koogua/course-tencent-cloud.git synced 2025-07-22 07:56:39 +08:00

整理索引和部分字段顺序

This commit is contained in:
xiaochong0302 2020-08-23 19:39:31 +08:00
parent 56c1c851fe
commit 335adbb050
33 changed files with 1332 additions and 1991 deletions

View File

@ -4,9 +4,8 @@
<div class="layui-tab layui-tab-brief"> <div class="layui-tab layui-tab-brief">
<ul class="layui-tab-title kg-tab-title"> <ul class="layui-tab-title kg-tab-title">
<li class="layui-this">基本信息</li> <li class="layui-this">基本设置</li>
<li>在线客服</li> <li>在线客服</li>
<li>聊天机器人</li>
</ul> </ul>
<div class="layui-tab-content"> <div class="layui-tab-content">
<div class="layui-tab-item layui-show"> <div class="layui-tab-item layui-show">
@ -15,9 +14,6 @@
<div class="layui-tab-item"> <div class="layui-tab-item">
{{ partial('setting/im_cs') }} {{ partial('setting/im_cs') }}
</div> </div>
<div class="layui-tab-item">
{{ partial('setting/im_robot') }}
</div>
</div> </div>
</div> </div>

View File

@ -1,34 +0,0 @@
<form class="layui-form kg-form" method="POST" action="{{ url({'for':'admin.setting.im'}) }}">
<div class="layui-form-item">
<label class="layui-form-label">开启服务</label>
<div class="layui-input-block">
<input type="radio" name="robot_enabled" value="1" title="是" lay-filter="status" {% if im.robot_enabled == 1 %}checked{% endif %}>
<input type="radio" name="robot_enabled" value="0" title="否" lay-filter="status" {% if im.robot_enabled == 0 %}checked{% endif %}>
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">客服1用户编号</label>
<div class="layui-input-block">
<input class="layui-input" type="text" name="cs_user1_id" value="{{ im.cs_user1_id }}" layui-verify="required">
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">客服2用户编号</label>
<div class="layui-input-block">
<input class="layui-input" type="text" name="cs_user2_id" value="{{ im.cs_user2_id }}">
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">客服3用户编号</label>
<div class="layui-input-block">
<input class="layui-input" type="text" name="cs_user3_id" value="{{ im.cs_user3_id }}">
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label"></label>
<div class="layui-input-block">
<button class="layui-btn" lay-submit="true" lay-filter="go">提交</button>
<button type="button" class="kg-back layui-btn layui-btn-primary">返回</button>
</div>
</div>
</form>

View File

@ -24,7 +24,7 @@ class UserController extends Controller
$user = $service->handle($id); $user = $service->handle($id);
$this->seo->prependTitle("{$user['name']}的个人主页"); $this->seo->prependTitle([$user['name'], '个人主页']);
$this->view->setVar('user', $user); $this->view->setVar('user', $user);
} }

View File

@ -2,7 +2,7 @@
{% block content %} {% block content %}
{% set chapter_full_url = full_url({'for':'desktop.chapter.show','id':chapter.id}) %} {% set full_chapter_url = full_url({'for':'desktop.chapter.show','id':chapter.id}) %}
{% set course_url = url({'for':'desktop.course.show','id':chapter.course.id}) %} {% set course_url = url({'for':'desktop.course.show','id':chapter.course.id}) %}
{% set learning_url = url({'for':'desktop.chapter.learning','id':chapter.id}) %} {% set learning_url = url({'for':'desktop.chapter.learning','id':chapter.id}) %}
{% set live_chats_url = url({'for':'desktop.live.chats','id':chapter.id}) %} {% set live_chats_url = url({'for':'desktop.live.chats','id':chapter.id}) %}
@ -10,7 +10,7 @@
{% set send_msg_url = url({'for':'desktop.live.send_msg','id':chapter.id}) %} {% set send_msg_url = url({'for':'desktop.live.send_msg','id':chapter.id}) %}
{% set bind_user_url = url({'for':'desktop.live.bind_user','id':chapter.id}) %} {% set bind_user_url = url({'for':'desktop.live.bind_user','id':chapter.id}) %}
{% set like_url = url({'for':'desktop.chapter.like','id':chapter.id}) %} {% set like_url = url({'for':'desktop.chapter.like','id':chapter.id}) %}
{% set qrcode_url = url({'for':'desktop.qrcode'},{'text':chapter_full_url}) %} {% set qrcode_url = url({'for':'desktop.qrcode'},{'text':full_chapter_url}) %}
<div class="breadcrumb"> <div class="breadcrumb">
<span class="layui-breadcrumb"> <span class="layui-breadcrumb">
@ -62,7 +62,7 @@
<div class="layui-hide"> <div class="layui-hide">
<input type="hidden" name="share.title" value="{{ chapter.course.title }}"> <input type="hidden" name="share.title" value="{{ chapter.course.title }}">
<input type="hidden" name="share.pic" value="{{ chapter.course.cover }}"> <input type="hidden" name="share.pic" value="{{ chapter.course.cover }}">
<input type="hidden" name="share.url" value="{{ chapter_full_url }}"> <input type="hidden" name="share.url" value="{{ full_chapter_url }}">
<input type="hidden" name="share.qrcode" value="{{ qrcode_url }}"> <input type="hidden" name="share.qrcode" value="{{ qrcode_url }}">
</div> </div>

View File

@ -2,12 +2,12 @@
{% block content %} {% block content %}
{% set chapter_full_url = full_url({'for':'desktop.chapter.show','id':chapter.id}) %} {% set full_chapter_url = full_url({'for':'desktop.chapter.show','id':chapter.id}) %}
{% set course_url = url({'for':'desktop.course.show','id':chapter.course.id}) %} {% set course_url = url({'for':'desktop.course.show','id':chapter.course.id}) %}
{% set learning_url = url({'for':'desktop.chapter.learning','id':chapter.id}) %} {% set learning_url = url({'for':'desktop.chapter.learning','id':chapter.id}) %}
{% set like_url = url({'for':'desktop.chapter.like','id':chapter.id}) %} {% set like_url = url({'for':'desktop.chapter.like','id':chapter.id}) %}
{% set consult_url = url({'for':'desktop.consult.add'},{'chapter_id':chapter.id}) %} {% set consult_url = url({'for':'desktop.consult.add'},{'chapter_id':chapter.id}) %}
{% set qrcode_url = url({'for':'desktop.qrcode'},{'text':chapter_full_url}) %} {% set qrcode_url = url({'for':'desktop.qrcode'},{'text':full_chapter_url}) %}
<div class="breadcrumb"> <div class="breadcrumb">
<span class="layui-breadcrumb"> <span class="layui-breadcrumb">
@ -43,7 +43,7 @@
<div class="layui-hide"> <div class="layui-hide">
<input type="hidden" name="share.title" value="{{ chapter.course.title }}"> <input type="hidden" name="share.title" value="{{ chapter.course.title }}">
<input type="hidden" name="share.pic" value="{{ chapter.course.cover }}"> <input type="hidden" name="share.pic" value="{{ chapter.course.cover }}">
<input type="hidden" name="share.url" value="{{ chapter_full_url }}"> <input type="hidden" name="share.url" value="{{ full_chapter_url }}">
<input type="hidden" name="share.qrcode" value="{{ qrcode_url }}"> <input type="hidden" name="share.qrcode" value="{{ qrcode_url }}">
</div> </div>

View File

@ -2,11 +2,11 @@
{% block content %} {% block content %}
{% set chapter_full_url = full_url({'for':'desktop.chapter.show','id':chapter.id}) %} {% set full_chapter_url = full_url({'for':'desktop.chapter.show','id':chapter.id}) %}
{% set course_url = url({'for':'desktop.course.show','id':chapter.course.id}) %} {% set course_url = url({'for':'desktop.course.show','id':chapter.course.id}) %}
{% set learning_url = url({'for':'desktop.chapter.learning','id':chapter.id}) %} {% set learning_url = url({'for':'desktop.chapter.learning','id':chapter.id}) %}
{% set like_url = url({'for':'desktop.chapter.like','id':chapter.id}) %} {% set like_url = url({'for':'desktop.chapter.like','id':chapter.id}) %}
{% set qrcode_url = url({'for':'desktop.qrcode'},{'text':chapter_full_url}) %} {% set qrcode_url = url({'for':'desktop.qrcode'},{'text':full_chapter_url}) %}
{% set consult_url = url({'for':'desktop.consult.add'},{'chapter_id':chapter.id}) %} {% set consult_url = url({'for':'desktop.consult.add'},{'chapter_id':chapter.id}) %}
{% set liked_class = chapter.me.liked ? 'active' : '' %} {% set liked_class = chapter.me.liked ? 'active' : '' %}
@ -48,7 +48,7 @@
<div class="layui-hide"> <div class="layui-hide">
<input type="hidden" name="share.title" value="{{ chapter.course.title }}"> <input type="hidden" name="share.title" value="{{ chapter.course.title }}">
<input type="hidden" name="share.pic" value="{{ chapter.course.cover }}"> <input type="hidden" name="share.pic" value="{{ chapter.course.cover }}">
<input type="hidden" name="share.url" value="{{ chapter_full_url }}"> <input type="hidden" name="share.url" value="{{ full_chapter_url }}">
<input type="hidden" name="share.qrcode" value="{{ qrcode_url }}"> <input type="hidden" name="share.qrcode" value="{{ qrcode_url }}">
</div> </div>

View File

@ -2,4 +2,12 @@
{% if value == 1 %} {% if value == 1 %}
<span class="layui-badge layui-bg-orange vip">宾</span> <span class="layui-badge layui-bg-orange vip">宾</span>
{% endif %} {% endif %}
{%- endmacro %}
{%- macro gender_info(value) %}
{% if value == 1 %}
<i class="layui-icon layui-icon-male"></i>
{% elseif value == 2 %}
<i class="layui-icon layui-icon-female"></i>
{% endif %}
{%- endmacro %} {%- endmacro %}

View File

@ -3,15 +3,32 @@
{% block content %} {% block content %}
{{ partial('macros/course') }} {{ partial('macros/course') }}
{{ partial('macros/user') }}
{% set vip_flag = user.vip ? '<i class="layui-icon layui-icon-diamond icon-vip"></i>' : '' %} {% set full_user_url = full_url({'for':'desktop.user.show','id':user.id}) %}
{% set qrcode_url = url({'for':'desktop.qrcode'},{'text':full_user_url}) %}
<div class="breadcrumb">
<span class="layui-breadcrumb">
<a href="/">首页</a>
<a><cite>个人主页</cite></a>
<a><cite>{{ user.name }}</cite></a>
</span>
<span class="share">
<a href="javascript:" title="添加好友" class="apply-friend" data-id="{{ user.id }}" data-name="{{ user.name }}" data-avatar="{{ user.avatar }}"><i class="layui-icon layui-icon-user"></i></a>
<a href="javascript:" title="分享到微信"><i class="layui-icon layui-icon-login-wechat icon-wechat"></i></a>
<a href="javascript:" title="分享到QQ空间"><i class="layui-icon layui-icon-login-qq icon-qq"></i></a>
<a href="javascript:" title="分享到微博"><i class="layui-icon layui-icon-login-weibo icon-weibo"></i></a>
</span>
</div>
<div class="user-profile wrap clearfix"> <div class="user-profile wrap clearfix">
{{ vip_info(user.vip) }}
<div class="avatar"> <div class="avatar">
<img src="{{ user.avatar }}" alt="{{ user.name }}"> <img src="{{ user.avatar }}" alt="{{ user.name }}">
</div> </div>
<div class="info"> <div class="info">
<p><span class="name">{{ user.name }}</span> {{ vip_flag }}</p> <p><span class="name">{{ user.name }}</span><span>{{ gender_info(user.gender) }}</span></p>
<p><span><i class="layui-icon layui-icon-location"></i></span><span>{{ user.location }}</span></p> <p><span><i class="layui-icon layui-icon-location"></i></span><span>{{ user.location }}</span></p>
<p><span><i class="layui-icon layui-icon-time"></i></span><span>{{ date('Y-m-d H:i',user.active_time) }}</span></p> <p><span><i class="layui-icon layui-icon-time"></i></span><span>{{ date('Y-m-d H:i',user.active_time) }}</span></p>
</div> </div>
@ -59,11 +76,19 @@
</div> </div>
</div> </div>
<div class="layui-hide">
<input type="hidden" name="share.title" value="{{ user.name }}">
<input type="hidden" name="share.pic" value="{{ user.avatar }}">
<input type="hidden" name="share.url" value="{{ full_user_url }}">
<input type="hidden" name="share.qrcode" value="{{ qrcode_url }}">
</div>
{% endblock %} {% endblock %}
{% block include_js %} {% block include_js %}
{{ js_include('desktop/js/user.show.js') }} {{ js_include('desktop/js/user.show.js') }}
{{ js_include('desktop/js/user.share.js') }}
{{ js_include('desktop/js/im.apply.js') }} {{ js_include('desktop/js/im.apply.js') }}
{% endblock %} {% endblock %}

View File

@ -1,524 +0,0 @@
<?php
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
// +----------------------------------------------------------------------
// | Copyright (c) 2006~2018 http://thinkphp.cn All rights reserved.
// +----------------------------------------------------------------------
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
// +----------------------------------------------------------------------
// | Author: zhangyajun <448901948@qq.com>
// +----------------------------------------------------------------------
namespace App\Library;
use ArrayAccess;
use ArrayIterator;
use Countable;
use IteratorAggregate;
use JsonSerializable;
class Collection implements ArrayAccess, Countable, IteratorAggregate, JsonSerializable
{
protected $items = [];
public function __construct($items = [])
{
$this->items = $this->convertToArray($items);
}
public static function make($items = [])
{
return new static($items);
}
public function isEmpty()
{
return empty($this->items);
}
public function toArray()
{
return array_map(function ($value) {
return ($value instanceof self) ? $value->toArray() : $value;
}, $this->items);
}
public function all()
{
return $this->items;
}
/**
* 合并数组
*
* @param mixed $items
* @return static
*/
public function merge($items)
{
return new static(array_merge($this->items, $this->convertToArray($items)));
}
/**
* 交换数组中的键和值
*
* @return static
*/
public function flip()
{
return new static(array_flip($this->items));
}
/**
* 按指定键整理数据
*
* @param mixed $items 数据
* @param string $indexKey 键名
* @return array
*/
public function dictionary($items = null, &$indexKey = null)
{
if ($items instanceof self) {
$items = $items->all();
}
$items = is_null($items) ? $this->items : $items;
if (isset($indexKey) && is_string($indexKey)) {
return array_column($items, null, $indexKey);
}
return $items;
}
/**
* 比较数组,返回差集
*
* @param mixed $items 数据
* @param string $indexKey 指定比较的键名
* @return static
*/
public function diff($items, $indexKey = null)
{
if ($this->isEmpty() || is_scalar($this->items[0])) {
return new static(array_diff($this->items, $this->convertToArray($items)));
}
$diff = [];
$dictionary = $this->dictionary($items, $indexKey);
if (is_string($indexKey)) {
foreach ($this->items as $item) {
if (!isset($dictionary[$item[$indexKey]])) {
$diff[] = $item;
}
}
}
return new static($diff);
}
/**
* 比较数组,返回交集
*
* @param mixed $items 数据
* @param string $indexKey 指定比较的键名
* @return static
*/
public function intersect($items, $indexKey = null)
{
if ($this->isEmpty() || is_scalar($this->items[0])) {
return new static(array_diff($this->items, $this->convertToArray($items)));
}
$intersect = [];
$dictionary = $this->dictionary($items, $indexKey);
if (is_string($indexKey)) {
foreach ($this->items as $item) {
if (isset($dictionary[$item[$indexKey]])) {
$intersect[] = $item;
}
}
}
return new static($intersect);
}
/**
* 返回数组中所有的键名
*
* @return array
*/
public function keys()
{
$current = current($this->items);
if (is_scalar($current)) {
$array = $this->items;
} elseif (is_array($current)) {
$array = $current;
} else {
$array = $current->toArray();
}
return array_keys($array);
}
/**
* 删除数组的最后一个元素(出栈)
*
* @return mixed
*/
public function pop()
{
return array_pop($this->items);
}
/**
* 通过使用用户自定义函数,以字符串返回数组
*
* @param callable $callback
* @param mixed $initial
* @return mixed
*/
public function reduce(callable $callback, $initial = null)
{
return array_reduce($this->items, $callback, $initial);
}
/**
* 以相反的顺序返回数组。
*
* @return static
*/
public function reverse()
{
return new static(array_reverse($this->items));
}
/**
* 删除数组中首个元素,并返回被删除元素的值
*
* @return mixed
*/
public function shift()
{
return array_shift($this->items);
}
/**
* 在数组结尾插入一个元素
*
* @param mixed $value
* @param mixed $key
* @return void
*/
public function push($value, $key = null)
{
if (is_null($key)) {
$this->items[] = $value;
} else {
$this->items[$key] = $value;
}
}
/**
* 把一个数组分割为新的数组块.
*
* @param int $size
* @param bool $preserveKeys
* @return static
*/
public function chunk($size, $preserveKeys = false)
{
$chunks = [];
foreach (array_chunk($this->items, $size, $preserveKeys) as $chunk) {
$chunks[] = new static($chunk);
}
return new static($chunks);
}
/**
* 在数组开头插入一个元素
*
* @param mixed $value
* @param mixed $key
* @return void
*/
public function unshift($value, $key = null)
{
if (is_null($key)) {
array_unshift($this->items, $value);
} else {
$this->items = [$key => $value] + $this->items;
}
}
/**
* 给每个元素执行个回调
*
* @param callable $callback
* @return $this
*/
public function each(callable $callback)
{
foreach ($this->items as $key => $item) {
$result = $callback($item, $key);
if (false === $result) {
break;
} elseif (!is_object($item)) {
$this->items[$key] = $result;
}
}
return $this;
}
/**
* 用回调函数处理数组中的元素
*
* @param callable|null $callback
* @return static
*/
public function map(callable $callback)
{
return new static(array_map($callback, $this->items));
}
/**
* 用回调函数过滤数组中的元素
*
* @param callable|null $callback
* @return static
*/
public function filter(callable $callback = null)
{
if ($callback) {
return new static(array_filter($this->items, $callback));
}
return new static(array_filter($this->items));
}
/**
* 根据字段条件过滤数组中的元素
*
* @param string $field 字段名
* @param mixed $operator 操作符
* @param mixed $value 数据
* @return static
*/
public function where($field, $operator, $value = null)
{
if (is_null($value)) {
$value = $operator;
$operator = '=';
}
return $this->filter(function ($data) use ($field, $operator, $value) {
if (strpos($field, '.')) {
list($field, $relation) = explode('.', $field);
$result = isset($data[$field][$relation]) ? $data[$field][$relation] : null;
} else {
$result = isset($data[$field]) ? $data[$field] : null;
}
switch ($operator) {
case '===':
return $result === $value;
case '!==':
return $result !== $value;
case '!=':
case '<>':
return $result != $value;
case '>':
return $result > $value;
case '>=':
return $result >= $value;
case '<':
return $result < $value;
case '<=':
return $result <= $value;
case 'like':
return is_string($result) && false !== strpos($result, $value);
case 'not_like':
return is_string($result) && false === strpos($result, $value);
case 'in':
return is_scalar($result) && in_array($result, $value, true);
case 'not_in':
return is_scalar($result) && !in_array($result, $value, true);
case 'between':
list($min, $max) = is_string($value) ? explode(',', $value) : $value;
return is_scalar($result) && $result >= $min && $result <= $max;
case 'not_between':
list($min, $max) = is_string($value) ? explode(',', $value) : $value;
return is_scalar($result) && $result > $max || $result < $min;
case '==':
case '=':
default:
return $result == $value;
}
});
}
/**
* 返回数据中指定的一列
*
* @param mixed $columnKey 键名
* @param mixed $indexKey 作为索引值的列
* @return array
*/
public function column($columnKey, $indexKey = null)
{
return array_column($this->items, $columnKey, $indexKey);
}
/**
* 对数组排序
*
* @access public
* @param callable|null $callback
* @return static
*/
public function sort(callable $callback = null)
{
$items = $this->items;
$callback = $callback ?: function ($a, $b) {
return $a == $b ? 0 : (($a < $b) ? -1 : 1);
};
uasort($items, $callback);
return new static($items);
}
/**
* 指定字段排序
*
* @param string $field 排序字段
* @param string $order 排序
* @param bool $intSort 是否为数字排序
* @return $this
*/
public function order($field, $order = null, $intSort = true)
{
return $this->sort(function ($a, $b) use ($field, $order, $intSort) {
$fieldA = isset($a[$field]) ? $a[$field] : null;
$fieldB = isset($b[$field]) ? $b[$field] : null;
if ($intSort) {
return 'desc' == strtolower($order) ? $fieldB >= $fieldA : $fieldA >= $fieldB;
} else {
return 'desc' == strtolower($order) ? strcmp($fieldB, $fieldA) : strcmp($fieldA, $fieldB);
}
});
}
/**
* 将数组打乱
*
* @return static
*/
public function shuffle()
{
$items = $this->items;
shuffle($items);
return new static($items);
}
/**
* 截取数组
*
* @param int $offset
* @param int $length
* @param bool $preserveKeys
* @return static
*/
public function slice($offset, $length = null, $preserveKeys = false)
{
return new static(array_slice($this->items, $offset, $length, $preserveKeys));
}
public function offsetExists($offset)
{
return array_key_exists($offset, $this->items);
}
public function offsetGet($offset)
{
return $this->items[$offset];
}
public function offsetSet($offset, $value)
{
if (is_null($offset)) {
$this->items[] = $value;
} else {
$this->items[$offset] = $value;
}
}
public function offsetUnset($offset)
{
unset($this->items[$offset]);
}
public function count()
{
return count($this->items);
}
public function getIterator()
{
return new ArrayIterator($this->items);
}
public function jsonSerialize()
{
return $this->toArray();
}
/**
* 转换当前数据集为JSON字符串
*
* @param integer $options json参数
* @return string
*/
public function toJson($options = JSON_UNESCAPED_UNICODE)
{
return json_encode($this->toArray(), $options);
}
public function __toString()
{
return $this->toJson();
}
/**
* 转换成数组
*
* @param mixed $items
* @return array
*/
protected function convertToArray($items)
{
if ($items instanceof self) {
return $items->all();
}
return (array)$items;
}
}

View File

@ -1,72 +0,0 @@
<?php
namespace App\Models;
class AccessToken extends Model
{
/**
* 主键编号
*
* @var string
*/
public $id;
/**
* 用户编号
*
* @var int
*/
public $user_id;
/**
* 回收标识
*
* @var int
*/
public $revoked;
/**
* 过期时间
*
* @var int
*/
public $expiry_time;
/**
* 创建时间
*
* @var int
*/
public $create_time;
/**
* 更新时间
*
* @var int
*/
public $update_time;
public function getSource(): string
{
return 'kg_access_token';
}
public function beforeCreate()
{
$this->id = $this->getRandId($this->user_id);
$this->create_time = time();
}
public function beforeUpdate()
{
$this->update_time = time();
}
protected function getRandId($userId, $prefix = 'AT')
{
return md5("{$prefix}-{$userId}" . time() . rand(1000, 9999));
}
}

View File

@ -8,9 +8,9 @@ class Area extends Model
/** /**
* 区域类型 * 区域类型
*/ */
const TYPE_PROVINCE = 'province'; const TYPE_PROVINCE = 1; // 省
const TYPE_CITY = 'city'; const TYPE_CITY = 2; // 市
const TYPE_COUNTY = 'county'; const TYPE_COUNTY = 3; // 区
/** /**
* 主键 * 主键
@ -22,7 +22,7 @@ class Area extends Model
/** /**
* 类型 * 类型
* *
* @var string * @var int
*/ */
public $type; public $type;

View File

@ -111,6 +111,20 @@ class Chapter extends Model
*/ */
public $attrs; public $attrs;
/**
* 发布标识
*
* @var int
*/
public $published;
/**
* 删除标识
*
* @var int
*/
public $deleted;
/** /**
* 课时数 * 课时数
* *
@ -139,20 +153,6 @@ class Chapter extends Model
*/ */
public $like_count; public $like_count;
/**
* 发布标识
*
* @var int
*/
public $published;
/**
* 删除标识
*
* @var int
*/
public $deleted;
/** /**
* 创建时间 * 创建时间
* *

View File

@ -56,13 +56,6 @@ class Consult extends Model
*/ */
public $answer; public $answer;
/**
* 赞成数
*
* @var int
*/
public $like_count;
/** /**
* 优先级 * 优先级
* *
@ -91,6 +84,13 @@ class Consult extends Model
*/ */
public $deleted; public $deleted;
/**
* 赞成数
*
* @var int
*/
public $like_count;
/** /**
* 回复时间 * 回复时间
* *

View File

@ -165,6 +165,20 @@ class Course extends Model
*/ */
public $attrs; public $attrs;
/**
* 发布标识
*
* @var int
*/
public $published;
/**
* 删除标识
*
* @var int
*/
public $deleted;
/** /**
* 学员数 * 学员数
* *
@ -207,20 +221,6 @@ class Course extends Model
*/ */
public $favorite_count; public $favorite_count;
/**
* 发布标识
*
* @var int
*/
public $published;
/**
* 删除标识
*
* @var int
*/
public $deleted;
/** /**
* 创建时间 * 创建时间
* *

View File

@ -69,6 +69,13 @@ class Learning extends Model
*/ */
public $position; public $position;
/**
* 删除标识
*
* @var int
*/
public $deleted;
/** /**
* 客户端类型 * 客户端类型
* *
@ -83,13 +90,6 @@ class Learning extends Model
*/ */
public $client_ip; public $client_ip;
/**
* 删除标识
*
* @var int
*/
public $deleted;
/** /**
* 活跃时间 * 活跃时间
* *

View File

@ -166,8 +166,6 @@ class Order extends Model
if (is_array($this->item_info)) { if (is_array($this->item_info)) {
$this->item_info = kg_json_encode($this->item_info); $this->item_info = kg_json_encode($this->item_info);
} else {
$this->item_info = ''; // text类型不会自动填充默认值
} }
} }

View File

@ -1,72 +0,0 @@
<?php
namespace App\Models;
class RefreshToken extends Model
{
/**
* 主键编号
*
* @var string
*/
public $id;
/**
* 用户编号
*
* @var int
*/
public $user_id;
/**
* 回收标识
*
* @var int
*/
public $revoked;
/**
* 过期时间
*
* @var int
*/
public $expiry_time;
/**
* 创建时间
*
* @var int
*/
public $create_time;
/**
* 更新时间
*
* @var int
*/
public $update_time;
public function getSource(): string
{
return 'kg_refresh_token';
}
public function beforeCreate()
{
$this->id = $this->getRandId($this->user_id);
$this->create_time = time();
}
public function beforeUpdate()
{
$this->update_time = time();
}
protected function getRandId($userId, $prefix = 'RT')
{
return md5("{$prefix}-{$userId}" . time() . rand(1000, 9999));
}
}

View File

@ -70,13 +70,6 @@ class Review extends Model
*/ */
public $rating3; public $rating3;
/**
* 点赞数量
*
* @var int
*/
public $like_count;
/** /**
* 匿名标识 * 匿名标识
* *
@ -98,6 +91,13 @@ class Review extends Model
*/ */
public $deleted; public $deleted;
/**
* 点赞数量
*
* @var int
*/
public $like_count;
/** /**
* 创建时间 * 创建时间
* *

View File

@ -102,8 +102,6 @@ class Task extends Model
if (!empty($this->item_info)) { if (!empty($this->item_info)) {
$this->item_info = kg_json_encode($this->item_info); $this->item_info = kg_json_encode($this->item_info);
} else {
$this->item_info = ''; // text类型不会自动填充默认值
} }
} }

View File

@ -1,45 +0,0 @@
<?php
namespace App\Repos;
use App\Models\AccessToken as AccessTokenModel;
use Phalcon\Mvc\Model;
use Phalcon\Mvc\Model\Resultset;
use Phalcon\Mvc\Model\ResultsetInterface;
class AccessToken extends Repository
{
/**
* @param string $id
* @return AccessTokenModel|Model|bool
*/
public function findById($id)
{
return AccessTokenModel::findFirst([
'conditions' => 'id = :id:',
'bind' => ['id' => $id],
]);
}
/**
* @param int $userId
* @return ResultsetInterface|Resultset|AccessTokenModel[]
*/
public function findByUserId($userId)
{
return AccessTokenModel::query()
->where('user_id = :user_id:', ['user_id' => $userId])
->andWhere('deleted = 0')
->execute();
}
public function countByUserId($userId)
{
return AccessTokenModel::count([
'conditions' => 'user_id = :user_id: AND deleted = 0',
'bind' => ['user_id' => $userId],
]);
}
}

View File

@ -1,45 +0,0 @@
<?php
namespace App\Repos;
use App\Models\RefreshToken as RefreshTokenModel;
use Phalcon\Mvc\Model;
use Phalcon\Mvc\Model\Resultset;
use Phalcon\Mvc\Model\ResultsetInterface;
class RefreshToken extends Repository
{
/**
* @param string $id
* @return RefreshTokenModel|Model|bool
*/
public function findById($id)
{
return RefreshTokenModel::findFirst([
'conditions' => 'id = :id:',
'bind' => ['id' => $id],
]);
}
/**
* @param int $userId
* @return ResultsetInterface|Resultset|RefreshTokenModel[]
*/
public function findByUserId($userId)
{
return RefreshTokenModel::query()
->where('user_id = :user_id:', ['user_id' => $userId])
->andWhere('deleted = 0')
->execute();
}
public function countByUserId($userId)
{
return (int)RefreshTokenModel::count([
'conditions' => 'user_id = :user_id: AND deleted = 0',
'bind' => ['user_id' => $userId],
]);
}
}

View File

@ -9,9 +9,6 @@ abstract class Auth extends Service
abstract function saveAuthInfo(UserModel $user); abstract function saveAuthInfo(UserModel $user);
/**
* @return array|null
*/
abstract function getAuthInfo(); abstract function getAuthInfo();
abstract function clearAuthInfo(); abstract function clearAuthInfo();

View File

@ -2,56 +2,41 @@
namespace App\Services\Auth; namespace App\Services\Auth;
use App\Library\Cache\Backend\Redis as RedisCache;
use App\Models\AccessToken as AccessTokenModel;
use App\Models\RefreshToken as RefreshTokenModel;
use App\Models\User as UserModel; use App\Models\User as UserModel;
use App\Services\Auth as AuthService; use App\Services\Auth as AuthService;
use Lcobucci\JWT\Builder as JwtBuilder;
use Lcobucci\JWT\Parser as JwtParser;
use Lcobucci\JWT\Signer\Hmac\Sha256 as JwtSingerSha256;
use Lcobucci\JWT\Signer\Key as JwtSingerKey;
use Lcobucci\JWT\ValidationData as JwtValidationData;
class Api extends AuthService class Api extends AuthService
{ {
public function saveAuthInfo(UserModel $user) public function saveAuthInfo(UserModel $user)
{ {
$config = $this->getDI()->get('config'); $builder = new JwtBuilder();
$accessToken = new AccessTokenModel(); $config = $this->getConfig();
$accessToken->user_id = $user->id;
$accessToken->expiry_time = time() + $config->access_token->lifetime;
$accessToken->create();
$refreshToken = new RefreshTokenModel(); $expireTime = time() + $config->jwt->lifetime;
$refreshToken->user_id = $user->id;
$refreshToken->expiry_time = time() + $config->refresh_token->lifetime;
$refreshToken->create();
$authInfo = [ $builder->expiresAt($expireTime);
'id' => $user->id, $builder->withClaim('user_id', $user->id);
'name' => $user->name, $builder->withClaim('user_name', $user->name);
];
$cache = $this->getCache(); $singer = new JwtSingerSha256();
$key = $this->getCacheKey($accessToken->id); $key = new JwtSingerKey($config->jwt->key);
$cache->save($key, $authInfo, $config->access_token->lifetime); $token = $builder->getToken($singer, $key);
return [ return $token->__toString();
'access_token' => $accessToken->id,
'refresh_token' => $refreshToken->id,
'expiry_time' => $accessToken->expiry_time,
];
} }
public function clearAuthInfo() public function clearAuthInfo()
{ {
$authToken = $this->getAuthToken();
$cache = $this->getCache();
$key = $this->getCacheKey($authToken);
$cache->delete($key);
} }
public function getAuthInfo() public function getAuthInfo()
@ -60,31 +45,40 @@ class Api extends AuthService
if (!$authToken) return null; if (!$authToken) return null;
$cache = $this->getCache(); $config = $this->getConfig();
$key = $this->getCacheKey($authToken); $parser = new JWTParser();
$authInfo = $cache->get($key); $token = $parser->parse($authToken);
return $authInfo ?: null; $data = new JWTValidationData(time(), $config->jwt->leeway);
}
/** if (!$token->validate($data)) {
* @return RedisCache return null;
*/ }
protected function getCache()
{ $singer = new JwtSingerSha256();
return $this->getDI()->get('cache');
if (!$token->verify($singer, $config->jwt->key)) {
return null;
}
return [
'id' => $token->getClaim('user_id'),
'name' => $token->getClaim('user_name'),
];
} }
protected function getAuthToken() protected function getAuthToken()
{ {
return $this->request->getHeader('Authorization'); $authorization = $this->request->getHeader('Authorization');
return trim(str_ireplace('Bearer', '', $authorization));
} }
protected function getCacheKey($token) protected function getConfig()
{ {
return "access_token:{$token}"; return $this->getDI()->get('config');
} }
} }

View File

@ -58,7 +58,6 @@ trait Auth
$user->id = 0; $user->id = 0;
$user->name = 'guest'; $user->name = 'guest';
$user->avatar = kg_ci_avatar_img_url(null);
return $user; return $user;
} }

View File

@ -19,7 +19,8 @@
"hightman/xunsearch": "^1.4.14", "hightman/xunsearch": "^1.4.14",
"aferrandini/phpqrcode": "1.0.1", "aferrandini/phpqrcode": "1.0.1",
"xiaochong0302/ip2region": "^1.0", "xiaochong0302/ip2region": "^1.0",
"robmorgan/phinx": "^0.12" "robmorgan/phinx": "^0.12",
"lcobucci/jwt": "^3.3"
}, },
"require-dev": { "require-dev": {
"phalcon/ide-stubs": "^3.4.3", "phalcon/ide-stubs": "^3.4.3",

2129
composer.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -103,14 +103,19 @@ $config['session']['db'] = 0;
$config['session']['lifetime'] = 24 * 3600; $config['session']['lifetime'] = 24 * 3600;
/** /**
* 访问令牌有效期(秒) * 加密密钥
*/ */
$config['access_token']['lifetime'] = 2 * 3600; $config['jwt']['key'] = 'fu6ckEc8pv8k5K7m';
/** /**
* 刷新令牌有效期(秒) * 有效期(秒)
*/ */
$config['refresh_token']['lifetime'] = 30 * 86400; $config['jwt']['lifetime'] = 7 * 86400;
/**
* 回旋时间(秒)
*/
$config['jwt']['leeway'] = 30;
/** /**
* 限流开启 * 限流开启

View File

@ -61,11 +61,15 @@
.kg-login-copyright { .kg-login-copyright {
margin-top: -80px; margin-top: -80px;
color: #999; color: #666;
font-size: 12px; font-size: 12px;
text-align: center; text-align: center;
} }
.kg-login-copyright a {
color: #666;
}
.kg-input-inline { .kg-input-inline {
float: left; float: left;
width: 250px; width: 250px;

View File

@ -1206,12 +1206,18 @@ body {
border-radius: 100px; border-radius: 100px;
} }
.user-profile .icon-vip { .user-profile .vip {
color: orange; position: absolute;
top: 10px;
left: 10px;
height: 16px;
padding: 0 3px;
font-size: 10px;
} }
.user-profile .info { .user-profile .info {
float: left; float: left;
padding-top: 10px;
} }
.user-profile .info h3 { .user-profile .info h3 {

View File

@ -1,7 +1,7 @@
layui.use(['jquery', 'layer'], function () { layui.use(['jquery', 'helper'], function () {
var $ = layui.jquery; var $ = layui.jquery;
var layer = layui.layer; var helper = layui.helper;
var myShare = { var myShare = {
title: $('input[name="share.title"]').val(), title: $('input[name="share.title"]').val(),
@ -11,40 +11,17 @@ layui.use(['jquery', 'layer'], function () {
}; };
$('.icon-wechat').on('click', function () { $('.icon-wechat').on('click', function () {
var content = '<div class="qrcode"><img src="' + myShare.qrcode + '" alt="分享到微信"></div>'; helper.wechatShare(myShare.qrcode);
layer.open({
type: 1,
title: false,
closeBtn: 0,
shadeClose: true,
content: content
});
}); });
$('.icon-qq').on('click', function () { $('.icon-qq').on('click', function () {
var title = '推荐一门好课:' + myShare.title + ',快来和我一起学习吧!'; var title = '推荐一门好课:' + myShare.title + ',快来和我一起学习吧!';
qqShare(title, myShare.url, myShare.pic); helper.qqShare(title, myShare.url, myShare.pic);
}); });
$('.icon-weibo').on('click', function () { $('.icon-weibo').on('click', function () {
var title = '推荐一门好课:' + myShare.title + ',快来和我一起学习吧!'; var title = '推荐一门好课:' + myShare.title + ',快来和我一起学习吧!';
weiboShare(title, myShare.url, myShare.pic); helper.weiboShare(title, myShare.url, myShare.pic);
}); });
function qqShare(title, url, pic) {
var shareUrl = 'http://sns.qzone.qq.com/cgi-bin/qzshare/cgi_qzshare_onekey?';
shareUrl += 'title=' + encodeURIComponent(title || document.title);
shareUrl += '&url=' + encodeURIComponent(url || document.location);
shareUrl += '&pics=' + pic;
window.open(shareUrl, '_blank');
}
function weiboShare(title, url, pic) {
var shareUrl = 'http://v.t.sina.com.cn/share/share.php?';
shareUrl += 'title=' + encodeURIComponent(title || document.title);
shareUrl += '&url=' + encodeURIComponent(url || document.location);
shareUrl += '&pic=' + encodeURIComponent(pic || '');
window.open(shareUrl, '_blank');
}
}); });

View File

@ -0,0 +1,27 @@
layui.use(['jquery', 'helper'], function () {
var $ = layui.jquery;
var helper = layui.helper;
var myShare = {
title: $('input[name="share.title"]').val(),
pic: $('input[name="share.pic"]').val(),
url: $('input[name="share.url"]').val(),
qrcode: $('input[name="share.qrcode"]').val()
};
$('.icon-wechat').on('click', function () {
helper.wechatShare(myShare.qrcode);
});
$('.icon-qq').on('click', function () {
var title = '推荐一个有趣的朋友:' + myShare.title + '快来和Ta一起学习吧';
helper.qqShare(title, myShare.url, myShare.pic);
});
$('.icon-weibo').on('click', function () {
var title = '推荐一个有趣的朋友:' + myShare.title + '快来和Ta一起学习吧';
helper.weiboShare(title, myShare.url, myShare.pic);
});
});

View File

@ -6,6 +6,12 @@ layui.define(['jquery', 'layer'], function (exports) {
var helper = {}; var helper = {};
helper.getRequestId = function () {
var id = Date.now().toString(36);
id += Math.random().toString(36).substr(3);
return id;
};
helper.ajaxLoadHtml = function (url, target) { helper.ajaxLoadHtml = function (url, target) {
var $target = $('#' + target); var $target = $('#' + target);
var html = '<div class="loading"><i class="layui-icon layui-icon-loading layui-anim layui-anim-rotate layui-anim-loop"></i></div>'; var html = '<div class="loading"><i class="layui-icon layui-icon-loading layui-anim layui-anim-rotate layui-anim-loop"></i></div>';
@ -32,10 +38,31 @@ layui.define(['jquery', 'layer'], function (exports) {
}); });
}; };
helper.getRequestId = function () { helper.wechatShare = function (qrcode) {
var id = Date.now().toString(36); var content = '<div class="qrcode"><img src="' + qrcode + '" alt="分享到微信"></div>';
id += Math.random().toString(36).substr(3); layer.open({
return id; type: 1,
title: false,
closeBtn: 0,
shadeClose: true,
content: content
});
};
helper.qqShare = function (title, url, pic) {
var shareUrl = 'http://sns.qzone.qq.com/cgi-bin/qzshare/cgi_qzshare_onekey?';
shareUrl += 'title=' + encodeURIComponent(title || document.title);
shareUrl += '&url=' + encodeURIComponent(url || document.location);
shareUrl += '&pics=' + pic;
window.open(shareUrl, '_blank');
};
helper.weiboShare = function (title, url, pic) {
var shareUrl = 'http://v.t.sina.com.cn/share/share.php?';
shareUrl += 'title=' + encodeURIComponent(title || document.title);
shareUrl += '&url=' + encodeURIComponent(url || document.location);
shareUrl += '&pic=' + encodeURIComponent(pic || '');
window.open(shareUrl, '_blank');
}; };
exports(MOD_NAME, helper); exports(MOD_NAME, helper);

View File

@ -36,7 +36,7 @@ layui.define(['layer', 'form', 'laytpl'], function (exports) {
120000: '天津市', 120000: '天津市',
130000: '河北省', 130000: '河北省',
140000: '山西省', 140000: '山西省',
150000: '内蒙古自治区', 150000: '内蒙古',
210000: '辽宁省', 210000: '辽宁省',
220000: '吉林省', 220000: '吉林省',
230000: '黑龙江省', 230000: '黑龙江省',
@ -51,21 +51,21 @@ layui.define(['layer', 'form', 'laytpl'], function (exports) {
420000: '湖北省', 420000: '湖北省',
430000: '湖南省', 430000: '湖南省',
440000: '广东省', 440000: '广东省',
450000: '广西壮族自治区', 450000: '广西',
460000: '海南省', 460000: '海南省',
500000: '重庆市', 500000: '重庆市',
510000: '四川省', 510000: '四川省',
520000: '贵州省', 520000: '贵州省',
530000: '云南省', 530000: '云南省',
540000: '西藏自治区', 540000: '西藏',
610000: '陕西省', 610000: '陕西省',
620000: '甘肃省', 620000: '甘肃省',
630000: '青海省', 630000: '青海省',
640000: '宁夏回族自治区', 640000: '宁夏',
650000: '新疆维吾尔自治区', 650000: '新疆',
710000: '台湾', 710000: '台湾',
810000: '香港特别行政区', 810000: '香港',
820000: '澳门特别行政区', 820000: '澳门',
900000: '海外' 900000: '海外'
}, },
city_list: { city_list: {