This commit is contained in:
2025-03-25 13:47:17 +08:00
parent 81042c384c
commit f40c0ef6e7
18 changed files with 377 additions and 32 deletions

View File

@@ -10,6 +10,11 @@ if (!function_exists('build_select')) {
/**
* 生成下拉列表
*
* 示例:
* {:build_select('row[type]', $business_type, null, ['class'=>'form-control', 'required'=>''])}
*
* 从左到右 name,数组数据,是否选择,扩展数据(class或者验证等)
* @param string $name
* @param mixed $options
* @param mixed $selected

View File

@@ -4,6 +4,7 @@ namespace app\admin\controller\auth;
use app\admin\model\AuthGroup;
use app\admin\model\AuthGroupAccess;
use app\api\logic\GewechatFriendsLogic;
use app\common\controller\Backend;
use fast\Random;
use fast\Tree;
@@ -294,4 +295,10 @@ class Admin extends Backend
$this->dataLimitField = 'id';
return parent::selectpage();
}
function getRelWxids(){
$friends = (new GewechatFriendsLogic())->getFriendDetailList();
return json(['data'=>$friends,'total'=>count($friends)]);
}
}

View File

@@ -42,6 +42,26 @@
{:build_radios('row[status]', ['normal'=>__('Normal'), 'hidden'=>__('Hidden')])}
</div>
</div>
<div class="form-group">
<label class="control-label col-xs-12 col-sm-2">{:__('关联微信')}:</label>
<div class="col-xs-12 col-sm-8">
<input id="c-rel_wxid" min="0" data-rule="required"
data-source="Gewechatfriends/getFriendList" class="form-control selectpage"
name="row[rel_wxid]" type="text" >
</div>
</div>
<div class="form-group">
<label class="control-label col-xs-12 col-sm-2">{:__('类型')}:</label>
<div class="col-xs-12 col-sm-8">
{:build_radios('row[employee_type]', ['1'=>'企业微信', '2'=>'个人微信'])}
</div>
</div>
<div class="form-group">
<label class="control-label col-xs-12 col-sm-2">{:__('角色')}:</label>
<div class="col-xs-12 col-sm-8">
{:build_radios('row[role]', ['admin'=>'系统管理员', 'firm'=>'企业员工'])}
</div>
</div>
<div class="form-group">
<label class="control-label col-xs-12 col-sm-2">{:__('选择企业')}:</label>
@@ -61,6 +81,14 @@
name="row[firmstore_id]" type="text" value="{:session_admin_firmstore_id()}">
</div>
</div>
<div class="form-group">
<label class="control-label col-xs-12 col-sm-2">{:__('职务')}:</label>
<div class="col-xs-12 col-sm-8">
<input id="c-firmduty_id" min="0" data-rule="required"
data-source="firmduty/index" class="form-control selectpage"
name="row[firmduty_id]" type="text" value="">
</div>
</div>
<div class="form-group hidden layer-footer">
<label class="control-label col-xs-12 col-sm-2"></label>
<div class="col-xs-12 col-sm-8">

View File

@@ -36,6 +36,26 @@
<input type="password" class="form-control" id="password" name="row[password]" autocomplete="new-password" value="" data-rule="password" />
</div>
</div>
<div class="form-group">
<label class="control-label col-xs-12 col-sm-2">{:__('关联微信')}:</label>
<div class="col-xs-12 col-sm-8">
<input id="c-rel_wxid" min="0" data-rule="required"
data-source="Gewechatfriends/getFriendList" class="form-control selectpage"
name="row[rel_wxid]" type="text" value="{$row.rel_wxid}">
</div>
</div>
<div class="form-group">
<label class="control-label col-xs-12 col-sm-2">{:__('类型')}:</label>
<div class="col-xs-12 col-sm-8">
{:build_radios('row[employee_type]', ['1'=>'企业微信', '2'=>'个人微信'], $row.employee_type)}
</div>
</div>
<div class="form-group">
<label class="control-label col-xs-12 col-sm-2">{:__('角色')}:</label>
<div class="col-xs-12 col-sm-8">
{:build_radios('row[role]', ['admin'=>'系统管理员', 'firm'=>'企业管理员'],$row.role)}
</div>
</div>
<div class="form-group">
<label class="control-label col-xs-12 col-sm-2">{:__('选择企业')}:</label>
<div class="col-xs-12 col-sm-8">
@@ -55,17 +75,26 @@
</div>
</div>
<div class="form-group">
<label for="loginfailure" class="control-label col-xs-12 col-sm-2">{:__('Loginfailure')}:</label>
<label class="control-label col-xs-12 col-sm-2">{:__('职务')}:</label>
<div class="col-xs-12 col-sm-8">
<input type="number" class="form-control" id="loginfailure" name="row[loginfailure]" value="{$row.loginfailure}" data-rule="required" />
<input id="c-firmduty_id" min="0" data-rule="required"
data-source="firmduty/index" class="form-control selectpage"
name="row[firmduty_id]" type="text" value="">
</div>
</div>
<!-- <div class="form-group">-->
<!-- <label for="loginfailure" class="control-label col-xs-12 col-sm-2">{:__('Loginfailure')}:</label>-->
<!-- <div class="col-xs-12 col-sm-8">-->
<!-- <input type="number" class="form-control" id="loginfailure" name="row[loginfailure]" value="{$row.loginfailure}" data-rule="required" />-->
<!-- </div>-->
<!-- </div>-->
<div class="form-group">
<label class="control-label col-xs-12 col-sm-2">{:__('Status')}:</label>
<div class="col-xs-12 col-sm-8">
{:build_radios('row[status]', ['normal'=>__('Normal'), 'hidden'=>__('Hidden')], $row['status'])}
</div>
</div>
<div class="form-group hidden layer-footer">
<label class="control-label col-xs-12 col-sm-2"></label>
<div class="col-xs-12 col-sm-8">

View File

@@ -18,18 +18,7 @@
<input id="c-address" class="form-control" name="row[address]" type="text" value="">
</div>
</div>
<div class="form-group">
<label class="control-label col-xs-12 col-sm-2">{:__('Phone')}:</label>
<div class="col-xs-12 col-sm-8">
<input id="c-phone" class="form-control" name="row[phone]" type="text" value="">
</div>
</div>
<div class="form-group">
<label class="control-label col-xs-12 col-sm-2">{:__('Person')}:</label>
<div class="col-xs-12 col-sm-8">
<input id="c-person" class="form-control" name="row[person]" type="text" value="">
</div>
</div>
<div class="form-group">
<label class="control-label col-xs-12 col-sm-2">{:__('Create_time')}:</label>
<div class="col-xs-12 col-sm-8">

View File

@@ -18,18 +18,7 @@
<input id="c-address" class="form-control" name="row[address]" type="text" value="{$row.address|htmlentities}">
</div>
</div>
<div class="form-group">
<label class="control-label col-xs-12 col-sm-2">{:__('Phone')}:</label>
<div class="col-xs-12 col-sm-8">
<input id="c-phone" class="form-control" name="row[phone]" type="text" value="{$row.phone|htmlentities}">
</div>
</div>
<div class="form-group">
<label class="control-label col-xs-12 col-sm-2">{:__('Person')}:</label>
<div class="col-xs-12 col-sm-8">
<input id="c-person" class="form-control" name="row[person]" type="text" value="{$row.person|htmlentities}">
</div>
</div>
<div class="form-group">
<label class="control-label col-xs-12 col-sm-2">{:__('Create_time')}:</label>
<div class="col-xs-12 col-sm-8">

View File

@@ -76,13 +76,18 @@
<div class="form-group">
<label class="control-label col-xs-12 col-sm-2">{:__('Firmemployee_id')}:</label>
<div class="col-xs-12 col-sm-8">
<input id="c-firmemployee_id" data-rule="required" data-source="firmemployee/index" class="form-control selectpage" name="row[firmemployee_id]" type="text" value="">
<input id="c-firmemployee_id" data-rule="required" data-source="admin/index"
data-field="nickname"
class="form-control selectpage" name="row[firmemployee_id]" type="text" value="">
</div>
</div>
<div class="form-group">
<label class="control-label col-xs-12 col-sm-2">{:__('Responsible_pm')}:</label>
<div class="col-xs-12 col-sm-8">
<input id="c-responsible_pm" data-rule="required" data-source="firmemployee/index" class="form-control selectpage" name="row[responsible_pm]" type="text" value="">
<input id="c-responsible_pm" data-rule="required"
data-source="admin/index" class="form-control selectpage"
data-field="nickname"
name="row[responsible_pm]" type="text" value="">
</div>
</div>
<div class="form-group">

View File

@@ -66,13 +66,19 @@
<div class="form-group">
<label class="control-label col-xs-12 col-sm-2">{:__('Firmemployee_id')}:</label>
<div class="col-xs-12 col-sm-8">
<input id="c-firmemployee_id" data-rule="required" data-source="firmemployee/index" class="form-control selectpage" name="row[firmemployee_id]" type="text" value="{$row.firmemployee_id|htmlentities}">
<input id="c-firmemployee_id" data-rule="required"
data-source="admin/index" class="form-control selectpage"
data-field="nickname"
name="row[firmemployee_id]" type="text" value="{$row.firmemployee_id|htmlentities}">
</div>
</div>
<div class="form-group">
<label class="control-label col-xs-12 col-sm-2">{:__('Responsible_pm')}:</label>
<div class="col-xs-12 col-sm-8">
<input id="c-responsible_pm" data-rule="required" data-source="firmemployee/index" class="form-control selectpage" name="row[responsible_pm]" type="text" value="{$row.responsible_pm|htmlentities}">
<input id="c-responsible_pm" data-rule="required" data-source="admin/index"
data-field="nickname"
class="form-control selectpage" name="row[responsible_pm]" type="text"
value="{$row.responsible_pm|htmlentities}">
</div>
</div>
<div class="form-group">

View File

@@ -0,0 +1,81 @@
<?php
/*
* description
* authorwh
* email
* createTime{2025/3/25} {10:19}
*/
namespace app\api\controller;
use app\common\model\ApiKey;
use app\common\service\AuthService;
use think\Controller;
use think\Request;
use wanghua\general_utility_tools_php\tool\Tools;
class BaseApiAuthController extends Controller
{
public function __construct(Request $request = null)
{
parent::__construct($request);
$r = $this->requestAuth($request);
if(false === $r){
echo json_encode(['code'=>500,'msg'=>'认证失败,请重新登录']);die;
}
//if(false == $this->defaultAuth()){
// echo json_encode(['code'=>500,'msg'=>'鉴权失败,缺失必要参数']);die;
//}
}
//请求认证
function requestAuth($request){
return true;
// 获取Authorization头
$authHeader = $request->header('authorization');
if (!$authHeader) {
echo json_encode(['code' => 401, 'error' => 'Missing Authorization header']);die;
}
// 解析Bearer Token
if (!preg_match('/Bearer\s(\S+)/', $authHeader, $matches)) {
//return json(['code' => 401, 'error' => 'Invalid token format'], 401);
echo json_encode(['code' => 401, 'error' => 'Invalid token format']);die;
}
$apiKey = $matches[1];
return (new AuthService($apiKey))->verifyApiKey();
}
/**
* desc默认鉴权
* authorwh
* @return bool
*/
function defaultAuth(){
$params = input();
if(empty($params['nonce'])){
//Tools::log_to_write_txt(['服务被拒绝,鉴权参数缺失:nonce。params'=>input()]);
return false;
}
if(empty($params['timestamp'])){
//Tools::log_to_write_txt(['服务被拒绝,鉴权参数缺失:timestamp。params'=>input()]);
return false;
}
if(empty($params['sign'])){
//Tools::log_to_write_txt(['服务被拒绝,鉴权参数缺失:sign。params'=>input()]);
return false;
}
$sign = $params['sign'];
unset($params['sign']);
if(Tools::signature($params) != $sign){
//Tools::log_to_write_txt(['签名失败,服务被拒绝.'=>input()]);
return false;
}
return true;
}
}

View File

@@ -0,0 +1,17 @@
<?php
/*
* description
* authorwh
* email
* createTime{2025/3/25} {10:19}
*/
namespace app\api\controller;
use think\Controller;
class BaseApiPublicController extends Controller
{
}

View File

@@ -0,0 +1,43 @@
<?php
/*
* description
* authorwh
* email
* createTime{2025/3/25} {10:00}
*/
namespace app\api\controller;
use think\Controller;
use think\Db;
use wanghua\general_utility_tools_php\Mmodel;
use wanghua\general_utility_tools_php\tool\Tools;
class Firmemployee extends BaseApiAuthController
{
/**
* desc获取工作人员列表
*
* /api/firmemployee/getList
* authorwh
*/
function getList(){
return Mmodel::catchJson(function (){
$firm_sign = input('firm_sign');//企业标识
if(empty($firm_sign)){
return Tools::set_fail('企业标识不能为空');
}
$info = Db::table('fa_admin')
->where('role','firm')
->where('firm_id',$firm_sign)
->select();
return Tools::set_ok('ok',[
'staff_list'=>array_column($info,'rel_wxid')
]);
});
}
}

View File

@@ -26,6 +26,9 @@ class TokenLogic extends BaseLogic
{
$url = 'https://wechat-api-test.excn.vip/vip_groups/auth_info';
$res = \wanghua\general_utility_tools_php\http\Curl::curl_post($url, []);
if(empty($res['data'])){
throw new \Exception('获取token失败');
}
$res_data = json_decode($res['data'], true);
return [
'token' => $res_data['gewe-token'],

View File

@@ -0,0 +1,24 @@
<?php
namespace app\common\model;
use think\Model;
class ApiKey extends Model
{
protected $name = 'api_keys';
// 自动时间戳
//protected $autoWriteTimestamp = true;
//protected $updateTime = false;
// 读取器处理JSON字段
public function getPermissionsAttr($value)
{
return json_decode($value, true);
}
public function getIpWhitelistAttr($value)
{
return json_decode($value, true);
}
}

View File

@@ -0,0 +1,47 @@
<?php
namespace app\common\service;
use think\Db;
use app\common\model\ApiKey;
class AuthService
{
protected $keyInfo;
public function __construct($apiKey)
{
$this->keyInfo = ApiKey::where('api_key', $apiKey)
->cache("api_key_{$apiKey}", 300) // 缓存5分钟
->find();
}
public function verifyApiKey()
{
// 查询数据库(带缓存)
$keyInfo = $this->keyInfo;
if (!$keyInfo) {
return false;
}
// 检查密钥状态
if (!$keyInfo->is_active || $keyInfo->expires_at < time()) {
return false;
}
// 记录最后使用时间
Db::name('api_keys')
->where('id', $keyInfo->id)
->update(['last_used_at' => time()]);
return true;
}
public function getDeveloperInfo()
{
// 根据业务需求返回开发者信息
return [
'developer_id' => $this->keyInfo->app_name,
'app_id' => $this->keyInfo->id,
//'permissions' => json_decode($this->keyInfo->permissions, true)
];
}
}

View File

@@ -0,0 +1,26 @@
<?php
/*
* description
* authorwh
* email
* createTime{2025/3/24} {17:23}
*/
namespace app\index\controller;
use think\Controller;
use wanghua\general_utility_tools_php\Mmodel;
//定时任务
class Tasktimer extends Controller
{
//按分钟执行
function runMinutes(){
Mmodel::catchJson(function (){
});
}
}

View File

@@ -33,6 +33,7 @@ define(['jquery', 'bootstrap', 'backend', 'table', 'form'], function ($, undefin
{field: 'id', title: 'ID'},
{field: 'username', title: __('Username')},
{field: 'nickname', title: __('Nickname')},
{field: 'role', title: __('角色'), searchList: {"admin":__('系统管理员'),"firm":__('企业管理员')}, formatter: Table.api.formatter.label },
{field: 'groups_text', title: __('Group'), operate:false, formatter: Table.api.formatter.label},
{field: 'email', title: __('Email')},
{field: 'mobile', title: __('Mobile')},

View File

@@ -29,8 +29,6 @@ define(['jquery', 'bootstrap', 'backend', 'table', 'form'], function ($, undefin
{field: 'name', title: __('Name'), operate: 'LIKE', table: table, class: 'autocontent', formatter: Table.api.formatter.content},
{field: 'full_name', title: __('Full_name'), operate: 'LIKE', table: table, class: 'autocontent', formatter: Table.api.formatter.content},
{field: 'address', title: __('Address'), operate: 'LIKE', table: table, class: 'autocontent', formatter: Table.api.formatter.content},
{field: 'phone', title: __('Phone'), operate: 'LIKE', table: table, class: 'autocontent', formatter: Table.api.formatter.content},
{field: 'person', title: __('Person'), operate: 'LIKE', table: table, class: 'autocontent', formatter: Table.api.formatter.content},
{field: 'create_time', title: __('Create_time'), operate:'RANGE', addclass:'datetimerange', autocomplete:false, formatter: Table.api.formatter.datetime},
{field: 'operate', title: __('Operate'), table: table, events: Table.api.events.operate, formatter: Table.api.formatter.operate}
]

View File

@@ -0,0 +1,47 @@
<?php
namespace app\api\middleware;
use app\common\service\AuthService;
use think\Db;
class AuthMiddleware
{
public function handle($request, \Closure $next)
{
// 获取Authorization头
$authHeader = $request->header('authorization');
if (!$authHeader) {
return json(['code' => 401, 'error' => 'Missing Authorization header'], 401);
}
// 解析Bearer Token
if (!preg_match('/Bearer\s(\S+)/', $authHeader, $matches)) {
return json(['code' => 401, 'error' => 'Invalid token format'], 401);
}
$apiKey = $matches[1];
$authService = new AuthService($apiKey);
// 验证密钥有效性
if (!$authService->verifyApiKey()) {
return json(['code' => 403, 'error' => 'Invalid API key'], 403);
}
// 将开发者信息注入请求对象
$request->developer = $authService->getDeveloperInfo();
//默认不限流
// 在中间件最后记录
Db::name('api_logs')->insert([
'api_key' => $apiKey,
'endpoint' => $request->url(),
'ip' => $request->ip(),
//'created_at' => time()
]);
return $next($request);
}
}