chore: 首次提交 - 关联 GitHub fnvtk/MBTI_wang
Made-with: Cursor
This commit is contained in:
109
api/app/model/AiProvider.php
Normal file
109
api/app/model/AiProvider.php
Normal file
@@ -0,0 +1,109 @@
|
||||
<?php
|
||||
namespace app\model;
|
||||
|
||||
use think\Model;
|
||||
use think\model\concern\SoftDelete;
|
||||
|
||||
/**
|
||||
* AI服务商配置模型
|
||||
*/
|
||||
class AiProvider extends Model
|
||||
{
|
||||
use SoftDelete;
|
||||
|
||||
// 设置表名(不带前缀,前缀在数据库配置中设置)
|
||||
protected $name = 'ai_providers';
|
||||
|
||||
// 软删除字段(驼峰命名,时间戳格式)
|
||||
protected $deleteTime = 'deletedAt';
|
||||
|
||||
// 设置字段信息(匹配数据库字段命名:驼峰命名)
|
||||
protected $schema = [
|
||||
'id' => 'int',
|
||||
'providerId' => 'string',
|
||||
'name' => 'string',
|
||||
'enabled' => 'int',
|
||||
'visible' => 'int',
|
||||
'apiKey' => 'string',
|
||||
'apiEndpoint' => 'string',
|
||||
'model' => 'string',
|
||||
'organizationId' => 'string',
|
||||
'maxTokens' => 'int',
|
||||
'balanceAlertEnabled' => 'int',
|
||||
'balanceAlertThreshold' => 'float',
|
||||
'notes' => 'string',
|
||||
'docUrl' => 'string',
|
||||
'isFree' => 'int',
|
||||
'supportsBalance' => 'int',
|
||||
'lastBalance' => 'float',
|
||||
'lastBalanceCurrency' => 'string',
|
||||
'lastBalanceCheckedAt' => 'int',
|
||||
'createdAt' => 'int',
|
||||
'updatedAt' => 'int',
|
||||
'deletedAt' => 'int',
|
||||
'extraConfig' => 'string',
|
||||
];
|
||||
|
||||
// 自动时间戳(使用驼峰命名,时间戳格式)
|
||||
protected $autoWriteTimestamp = 'int';
|
||||
|
||||
// 时间戳字段名(驼峰命名,匹配数据库)
|
||||
protected $createTime = 'createdAt';
|
||||
protected $updateTime = 'updatedAt';
|
||||
|
||||
// 时间字段类型(时间戳格式)
|
||||
protected $type = [
|
||||
'lastBalanceCheckedAt' => 'integer',
|
||||
'createdAt' => 'integer',
|
||||
'updatedAt' => 'integer',
|
||||
'deletedAt' => 'integer',
|
||||
'extraConfig' => 'json',
|
||||
];
|
||||
|
||||
// 注意:API Key需要可逆读取用于API调用,所以不隐藏,但在获取器中脱敏
|
||||
|
||||
/**
|
||||
* API Key 修改器(存储原始值,用于API调用)
|
||||
*/
|
||||
public function setApiKeyAttr($value)
|
||||
{
|
||||
if (empty($value)) {
|
||||
return null;
|
||||
}
|
||||
// 如果输入的是脱敏格式(包含****),不更新
|
||||
if (strpos($value, '****') !== false) {
|
||||
return null; // 返回null表示不更新此字段
|
||||
}
|
||||
// 直接存储原始值(实际生产环境建议使用AES加密)
|
||||
return $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* API Key 获取器(返回脱敏后的密钥)
|
||||
* 注意:如果需要原始密钥用于API调用,使用 getRawApiKey() 方法
|
||||
*/
|
||||
public function getApiKeyAttr($value)
|
||||
{
|
||||
if (empty($value)) {
|
||||
return '';
|
||||
}
|
||||
// 返回脱敏后的密钥(显示前6位和后4位)
|
||||
if (strlen($value) > 10) {
|
||||
return substr($value, 0, 6) . '****' . substr($value, -4);
|
||||
}
|
||||
return '****';
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取原始API Key(用于API调用)
|
||||
* @return string
|
||||
*/
|
||||
public function getRawApiKey()
|
||||
{
|
||||
// 直接从数据库读取原始值,绕过获取器
|
||||
return \think\facade\Db::name('ai_providers')
|
||||
->where('id', $this->id)
|
||||
->value('apiKey') ?: '';
|
||||
}
|
||||
}
|
||||
|
||||
48
api/app/model/BackupRecord.php
Normal file
48
api/app/model/BackupRecord.php
Normal file
@@ -0,0 +1,48 @@
|
||||
<?php
|
||||
namespace app\model;
|
||||
|
||||
use think\Model;
|
||||
use think\model\concern\SoftDelete;
|
||||
|
||||
/**
|
||||
* 数据库备份记录模型
|
||||
*/
|
||||
class BackupRecord extends Model
|
||||
{
|
||||
use SoftDelete;
|
||||
|
||||
// 设置表名(不带前缀,前缀在数据库配置中设置)
|
||||
protected $name = 'backup_records';
|
||||
|
||||
// 软删除字段(驼峰命名,时间戳格式)
|
||||
protected $deleteTime = 'deletedAt';
|
||||
|
||||
// 设置字段信息(匹配数据库字段命名:驼峰命名)
|
||||
protected $schema = [
|
||||
'id' => 'int',
|
||||
'filename' => 'string',
|
||||
'filepath' => 'string',
|
||||
'fileSize' => 'int',
|
||||
'ossUrl' => 'string',
|
||||
'ossPath' => 'string',
|
||||
'status' => 'string',
|
||||
'deletedAt' => 'int',
|
||||
'createdAt' => 'int',
|
||||
'updatedAt' => 'int',
|
||||
];
|
||||
|
||||
// 自动时间戳(使用驼峰命名,时间戳格式)
|
||||
protected $autoWriteTimestamp = 'int';
|
||||
|
||||
// 时间戳字段名(驼峰命名,匹配数据库)
|
||||
protected $createTime = 'createdAt';
|
||||
protected $updateTime = 'updatedAt';
|
||||
|
||||
// 时间字段类型(时间戳格式)
|
||||
protected $type = [
|
||||
'deletedAt' => 'integer',
|
||||
'createdAt' => 'integer',
|
||||
'updatedAt' => 'integer',
|
||||
];
|
||||
}
|
||||
|
||||
51
api/app/model/Enterprise.php
Normal file
51
api/app/model/Enterprise.php
Normal file
@@ -0,0 +1,51 @@
|
||||
<?php
|
||||
namespace app\model;
|
||||
|
||||
use think\Model;
|
||||
use think\model\concern\SoftDelete;
|
||||
|
||||
/**
|
||||
* 企业模型
|
||||
*/
|
||||
class Enterprise extends Model
|
||||
{
|
||||
use SoftDelete;
|
||||
|
||||
// 设置表名(不带前缀,前缀在数据库配置中设置)
|
||||
protected $name = 'enterprises';
|
||||
|
||||
// 软删除字段(驼峰命名,时间戳格式)
|
||||
protected $deleteTime = 'deletedAt';
|
||||
|
||||
// 设置字段信息(匹配数据库字段命名:驼峰命名)
|
||||
protected $schema = [
|
||||
'id' => 'int',
|
||||
'name' => 'string',
|
||||
'code' => 'string',
|
||||
'contactName' => 'string',
|
||||
'contactPhone' => 'string',
|
||||
'contactEmail' => 'string',
|
||||
'balance' => 'float',
|
||||
'status' => 'string',
|
||||
'trialExpireAt' => 'int',
|
||||
'deletedAt' => 'int',
|
||||
'createdAt' => 'int',
|
||||
'updatedAt' => 'int',
|
||||
];
|
||||
|
||||
// 自动时间戳(使用驼峰命名,时间戳格式)
|
||||
protected $autoWriteTimestamp = 'int';
|
||||
|
||||
// 时间戳字段名(驼峰命名,匹配数据库)
|
||||
protected $createTime = 'createdAt';
|
||||
protected $updateTime = 'updatedAt';
|
||||
|
||||
// 时间字段类型(时间戳格式)
|
||||
protected $type = [
|
||||
'trialExpireAt' => 'integer',
|
||||
'deletedAt' => 'integer',
|
||||
'createdAt' => 'integer',
|
||||
'updatedAt' => 'integer',
|
||||
];
|
||||
}
|
||||
|
||||
25
api/app/model/EnterpriseResumeUpload.php
Normal file
25
api/app/model/EnterpriseResumeUpload.php
Normal file
@@ -0,0 +1,25 @@
|
||||
<?php
|
||||
namespace app\model;
|
||||
|
||||
use think\Model;
|
||||
|
||||
/**
|
||||
* 企业版简历上传记录(仅记录上传,支持预览)
|
||||
*/
|
||||
class EnterpriseResumeUpload extends Model
|
||||
{
|
||||
protected $name = 'enterprise_resume_uploads';
|
||||
|
||||
protected $schema = [
|
||||
'id' => 'int',
|
||||
'userId' => 'int',
|
||||
'enterpriseId' => 'int',
|
||||
'fileUrl' => 'string',
|
||||
'fileName' => 'string',
|
||||
'is_default' => 'int',
|
||||
'createdAt' => 'int',
|
||||
];
|
||||
|
||||
protected $autoWriteTimestamp = 'int';
|
||||
protected $createTime = 'createdAt';
|
||||
}
|
||||
117
api/app/model/PricingConfig.php
Normal file
117
api/app/model/PricingConfig.php
Normal file
@@ -0,0 +1,117 @@
|
||||
<?php
|
||||
namespace app\model;
|
||||
|
||||
use think\Model;
|
||||
|
||||
/**
|
||||
* 定价配置模型
|
||||
* 实际表名 = 数据库前缀 + pricing_config,例如 .env 中 DATABASE_PREFIX=mbti_ 时为 mbti_pricing_config
|
||||
*/
|
||||
class PricingConfig extends Model
|
||||
{
|
||||
// 表名(不含前缀);最终访问表 = config(database.prefix) + pricing_config
|
||||
protected $name = 'pricing_config';
|
||||
|
||||
// 设置字段信息(匹配数据库字段命名:驼峰命名)
|
||||
protected $schema = [
|
||||
'id' => 'int',
|
||||
'type' => 'string',
|
||||
'enterpriseId' => 'int',
|
||||
'config' => 'string',
|
||||
'createdAt' => 'int',
|
||||
'updatedAt' => 'int',
|
||||
];
|
||||
|
||||
// 自动时间戳(使用驼峰命名,时间戳格式)
|
||||
protected $autoWriteTimestamp = 'int';
|
||||
|
||||
// 时间戳字段名(驼峰命名,匹配数据库)
|
||||
protected $createTime = 'createdAt';
|
||||
protected $updateTime = 'updatedAt';
|
||||
|
||||
// 时间字段类型(时间戳格式)
|
||||
protected $type = [
|
||||
'createdAt' => 'integer',
|
||||
'updatedAt' => 'integer',
|
||||
];
|
||||
|
||||
// JSON字段自动转换
|
||||
protected $json = ['config'];
|
||||
|
||||
/**
|
||||
* 配置修改器(自动转换为JSON)
|
||||
*/
|
||||
public function setConfigAttr($value)
|
||||
{
|
||||
if (is_array($value)) {
|
||||
return json_encode($value, JSON_UNESCAPED_UNICODE);
|
||||
}
|
||||
return $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* 配置获取器(自动解析JSON)
|
||||
*/
|
||||
public function getConfigAttr($value)
|
||||
{
|
||||
if (is_string($value)) {
|
||||
return json_decode($value, true);
|
||||
}
|
||||
return $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* 按类型与可选企业ID取定价配置
|
||||
*
|
||||
* personal(个人版)优先级:
|
||||
* 1. admin_personal + enterpriseId(企业专属管理端配置,有 eid 时)
|
||||
* 2. admin_personal + null(通用管理端配置)
|
||||
* 3. 任意一条 admin_personal(兜底:只要管理端配过就不走超管)
|
||||
* 4. personal + null(超管全局,仅在管理端完全未配置时使用)
|
||||
*
|
||||
* enterprise(企业版)优先级:
|
||||
* 1. admin_enterprise + enterpriseId(有 eid 时)
|
||||
* 2. admin_enterprise + null(通用管理端企业配置)
|
||||
* 3. 任意一条 admin_enterprise
|
||||
* 4. enterprise + null(超管全局兜底)
|
||||
*
|
||||
* @param string $type personal|enterprise|deep
|
||||
* @param int|null $enterpriseId 有则优先读该企业专属配置
|
||||
* @return \app\model\PricingConfig|null
|
||||
*/
|
||||
public static function getByTypeAndEnterprise(string $type, ?int $enterpriseId = null): ?self
|
||||
{
|
||||
if ($type === 'personal') {
|
||||
if (!empty($enterpriseId)) {
|
||||
$row = self::where('type', 'admin_personal')->where('enterpriseId', $enterpriseId)->find();
|
||||
if ($row) return $row;
|
||||
}
|
||||
// 通用管理端个人配置(admin_personal + null)
|
||||
$row = self::where('type', 'admin_personal')->whereNull('enterpriseId')->find();
|
||||
if ($row) return $row;
|
||||
// 任意管理端个人配置(兜底:管理端配过就不走超管)
|
||||
$row = self::where('type', 'admin_personal')->order('id', 'asc')->find();
|
||||
if ($row) return $row;
|
||||
// 超管全局个人定价(最后兜底,仅管理端完全未配置时使用)
|
||||
return self::where('type', 'personal')->whereNull('enterpriseId')->find();
|
||||
}
|
||||
if ($type === 'enterprise') {
|
||||
if (!empty($enterpriseId)) {
|
||||
$row = self::where('type', 'admin_enterprise')->where('enterpriseId', $enterpriseId)->find();
|
||||
if ($row) return $row;
|
||||
}
|
||||
// 通用管理端企业配置(admin_enterprise + null)
|
||||
$row = self::where('type', 'admin_enterprise')->whereNull('enterpriseId')->find();
|
||||
if ($row) return $row;
|
||||
// 任意管理端企业配置(兜底)
|
||||
$row = self::where('type', 'admin_enterprise')->order('id', 'asc')->find();
|
||||
if ($row) return $row;
|
||||
return self::where('type', 'enterprise')->whereNull('enterpriseId')->find();
|
||||
}
|
||||
if ($type === 'deep') {
|
||||
return self::where('type', 'deep')->whereNull('enterpriseId')->find();
|
||||
}
|
||||
return self::where('type', $type)->whereNull('enterpriseId')->find();
|
||||
}
|
||||
}
|
||||
|
||||
91
api/app/model/Question.php
Normal file
91
api/app/model/Question.php
Normal file
@@ -0,0 +1,91 @@
|
||||
<?php
|
||||
namespace app\model;
|
||||
|
||||
use think\Model;
|
||||
use think\model\concern\SoftDelete;
|
||||
|
||||
/**
|
||||
* 题目模型
|
||||
*/
|
||||
class Question extends Model
|
||||
{
|
||||
use SoftDelete;
|
||||
|
||||
// 设置表名(不带前缀,前缀在数据库配置中设置)
|
||||
protected $name = 'questions';
|
||||
|
||||
// 软删除字段(驼峰命名,时间戳格式)
|
||||
protected $deleteTime = 'deletedAt';
|
||||
|
||||
// 设置字段信息(匹配数据库字段命名:驼峰命名)
|
||||
protected $schema = [
|
||||
'id' => 'int',
|
||||
'type' => 'string',
|
||||
'question' => 'string',
|
||||
'options' => 'string',
|
||||
'dimension' => 'string',
|
||||
'enterpriseId' => 'int',
|
||||
'sort' => 'int',
|
||||
'status' => 'int',
|
||||
'deletedAt' => 'int',
|
||||
'createdAt' => 'int',
|
||||
'updatedAt' => 'int',
|
||||
];
|
||||
|
||||
// 自动时间戳(使用驼峰命名,时间戳格式)
|
||||
protected $autoWriteTimestamp = 'int';
|
||||
|
||||
// 时间戳字段名(驼峰命名,匹配数据库)
|
||||
protected $createTime = 'createdAt';
|
||||
protected $updateTime = 'updatedAt';
|
||||
|
||||
// 时间字段类型(时间戳格式)
|
||||
protected $type = [
|
||||
'deletedAt' => 'integer',
|
||||
'createdAt' => 'integer',
|
||||
'updatedAt' => 'integer',
|
||||
];
|
||||
|
||||
// JSON字段自动转换
|
||||
protected $json = ['options'];
|
||||
|
||||
/**
|
||||
* 选项修改器(自动转换为JSON)
|
||||
*/
|
||||
public function setOptionsAttr($value)
|
||||
{
|
||||
if (is_array($value)) {
|
||||
return json_encode($value, JSON_UNESCAPED_UNICODE);
|
||||
}
|
||||
return $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* 选项获取器(自动解析JSON,确保返回数组格式)
|
||||
*/
|
||||
public function getOptionsAttr($value)
|
||||
{
|
||||
if (is_string($value)) {
|
||||
$decoded = json_decode($value, true);
|
||||
// 如果解码失败或返回null,返回空数组
|
||||
if ($decoded === null && json_last_error() !== JSON_ERROR_NONE) {
|
||||
return [];
|
||||
}
|
||||
// 如果是对象格式(关联数组),转换为索引数组
|
||||
if (is_array($decoded) && !empty($decoded) && !isset($decoded[0])) {
|
||||
return array_values($decoded);
|
||||
}
|
||||
return $decoded ?: [];
|
||||
}
|
||||
// 如果是对象(stdClass),转换为数组
|
||||
if (is_object($value)) {
|
||||
$value = json_decode(json_encode($value), true);
|
||||
}
|
||||
// 如果已经是数组,确保是索引数组
|
||||
if (is_array($value) && !empty($value) && !isset($value[0])) {
|
||||
return array_values($value);
|
||||
}
|
||||
return is_array($value) ? $value : [];
|
||||
}
|
||||
}
|
||||
|
||||
63
api/app/model/SystemConfig.php
Normal file
63
api/app/model/SystemConfig.php
Normal file
@@ -0,0 +1,63 @@
|
||||
<?php
|
||||
namespace app\model;
|
||||
|
||||
use think\Model;
|
||||
|
||||
/**
|
||||
* 系统配置模型
|
||||
*/
|
||||
class SystemConfig extends Model
|
||||
{
|
||||
// 设置表名(不带前缀,前缀在数据库配置中设置)
|
||||
protected $name = 'system_config';
|
||||
|
||||
// 设置字段信息(匹配数据库字段命名:驼峰命名)
|
||||
protected $schema = [
|
||||
'id' => 'int',
|
||||
'key' => 'string',
|
||||
'enterprise_id' => 'int',
|
||||
'value' => 'string',
|
||||
'description' => 'string',
|
||||
'createdAt' => 'int',
|
||||
'updatedAt' => 'int',
|
||||
];
|
||||
|
||||
// 自动时间戳(使用驼峰命名,时间戳格式)
|
||||
protected $autoWriteTimestamp = 'int';
|
||||
|
||||
// 时间戳字段名(驼峰命名,匹配数据库)
|
||||
protected $createTime = 'createdAt';
|
||||
protected $updateTime = 'updatedAt';
|
||||
|
||||
// 时间字段类型(时间戳格式)
|
||||
protected $type = [
|
||||
'createdAt' => 'integer',
|
||||
'updatedAt' => 'integer',
|
||||
];
|
||||
|
||||
// JSON字段自动转换
|
||||
protected $json = ['value'];
|
||||
|
||||
/**
|
||||
* 配置值修改器(自动转换为JSON)
|
||||
*/
|
||||
public function setValueAttr($value)
|
||||
{
|
||||
if (is_array($value)) {
|
||||
return json_encode($value, JSON_UNESCAPED_UNICODE);
|
||||
}
|
||||
return $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* 配置值获取器(自动解析JSON)
|
||||
*/
|
||||
public function getValueAttr($value)
|
||||
{
|
||||
if (is_string($value)) {
|
||||
return json_decode($value, true);
|
||||
}
|
||||
return $value;
|
||||
}
|
||||
}
|
||||
|
||||
29
api/app/model/UploadFile.php
Normal file
29
api/app/model/UploadFile.php
Normal file
@@ -0,0 +1,29 @@
|
||||
<?php
|
||||
namespace app\model;
|
||||
|
||||
use think\Model;
|
||||
|
||||
/**
|
||||
* 上传文件记录(本地/OSS),用于去重与 URL 查询
|
||||
*/
|
||||
class UploadFile extends Model
|
||||
{
|
||||
protected $name = 'upload_files';
|
||||
|
||||
protected $schema = [
|
||||
'id' => 'int',
|
||||
'path' => 'string',
|
||||
'url' => 'string',
|
||||
'driver' => 'string',
|
||||
'hash' => 'string',
|
||||
'size' => 'int',
|
||||
'mimeType' => 'string',
|
||||
'extension' => 'string',
|
||||
'createdAt' => 'int',
|
||||
'updatedAt' => 'int',
|
||||
];
|
||||
|
||||
protected $autoWriteTimestamp = 'int';
|
||||
protected $createTime = 'createdAt';
|
||||
protected $updateTime = 'updatedAt';
|
||||
}
|
||||
76
api/app/model/User.php
Normal file
76
api/app/model/User.php
Normal file
@@ -0,0 +1,76 @@
|
||||
<?php
|
||||
namespace app\model;
|
||||
|
||||
use think\Model;
|
||||
use think\model\concern\SoftDelete;
|
||||
|
||||
/**
|
||||
* 用户模型
|
||||
*/
|
||||
class User extends Model
|
||||
{
|
||||
use SoftDelete;
|
||||
|
||||
// 设置表名(不带前缀,前缀在数据库配置中设置)
|
||||
protected $name = 'users';
|
||||
|
||||
// 软删除字段(驼峰命名,时间戳格式)
|
||||
protected $deleteTime = 'deletedAt';
|
||||
|
||||
// 设置字段信息(匹配数据库字段命名:驼峰命名)
|
||||
protected $schema = [
|
||||
'id' => 'int',
|
||||
'username' => 'string',
|
||||
'password' => 'string',
|
||||
'phone' => 'string',
|
||||
'email' => 'string',
|
||||
'role' => 'string',
|
||||
'enterpriseId' => 'int',
|
||||
'mbtiType' => 'string',
|
||||
'region' => 'string',
|
||||
'industry' => 'string',
|
||||
'status' => 'int',
|
||||
'lastLoginTime' => 'int',
|
||||
'lastLoginIp' => 'string',
|
||||
'deletedAt' => 'int',
|
||||
'createdAt' => 'int',
|
||||
'updatedAt' => 'int',
|
||||
];
|
||||
|
||||
// 自动时间戳(使用驼峰命名,时间戳格式)
|
||||
protected $autoWriteTimestamp = 'int';
|
||||
|
||||
// 时间戳字段名(驼峰命名,匹配数据库)
|
||||
protected $createTime = 'createdAt';
|
||||
protected $updateTime = 'updatedAt';
|
||||
|
||||
// 隐藏字段(不返回给前端)
|
||||
protected $hidden = ['password'];
|
||||
|
||||
// 时间字段类型(时间戳格式)
|
||||
protected $type = [
|
||||
'lastLoginTime' => 'integer',
|
||||
'deletedAt' => 'integer',
|
||||
'createdAt' => 'integer',
|
||||
'updatedAt' => 'integer',
|
||||
];
|
||||
|
||||
/**
|
||||
* 密码修改器(自动加密)
|
||||
*/
|
||||
public function setPasswordAttr($value)
|
||||
{
|
||||
return password_hash($value, PASSWORD_DEFAULT);
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证密码
|
||||
* @param string $password 明文密码
|
||||
* @return bool
|
||||
*/
|
||||
public function verifyPassword($password)
|
||||
{
|
||||
return password_verify($password, $this->password);
|
||||
}
|
||||
}
|
||||
|
||||
162
api/app/model/UserProfile.php
Normal file
162
api/app/model/UserProfile.php
Normal file
@@ -0,0 +1,162 @@
|
||||
<?php
|
||||
namespace app\model;
|
||||
|
||||
use think\Model;
|
||||
use think\facade\Db;
|
||||
|
||||
/**
|
||||
* 用户画像汇总模型
|
||||
* 实际表名: 前缀 + user_profile (如 mbti_user_profile)
|
||||
*/
|
||||
class UserProfile extends Model
|
||||
{
|
||||
protected $name = 'user_profile';
|
||||
|
||||
protected $schema = [
|
||||
'id' => 'int',
|
||||
'userId' => 'int',
|
||||
'userType' => 'string',
|
||||
'enterpriseId' => 'int',
|
||||
'testsTotal' => 'int',
|
||||
'testsMbti' => 'int',
|
||||
'testsDisc' => 'int',
|
||||
'testsPdp' => 'int',
|
||||
'testsFace' => 'int',
|
||||
'ordersTotal' => 'int',
|
||||
'paidOrders' => 'int',
|
||||
'totalPaidAmount' => 'int',
|
||||
'lastTestResultId'=> 'int',
|
||||
'lastTestType' => 'string',
|
||||
'lastTestAt' => 'int',
|
||||
'lastMbtiResultId'=> 'int',
|
||||
'lastDiscResultId'=> 'int',
|
||||
'lastPdpResultId' => 'int',
|
||||
'lastFaceResultId'=> 'int',
|
||||
'createdAt' => 'int',
|
||||
'updatedAt' => 'int',
|
||||
];
|
||||
|
||||
protected $autoWriteTimestamp = 'int';
|
||||
protected $createTime = 'createdAt';
|
||||
protected $updateTime = 'updatedAt';
|
||||
|
||||
/**
|
||||
* 测试完成后更新用户画像统计与最近测试ID
|
||||
*/
|
||||
public static function recordTest(int $userId, string $testType, int $testResultId, ?int $enterpriseId = null, ?int $createdAt = null): void
|
||||
{
|
||||
if ($userId <= 0 || !$testType || $testResultId <= 0) {
|
||||
return;
|
||||
}
|
||||
$now = $createdAt ?: time();
|
||||
$userType = $enterpriseId ? 'enterprise' : 'personal';
|
||||
|
||||
[$data, $id] = self::loadOrInitRow($userId, $userType, $enterpriseId, $now);
|
||||
|
||||
$data['testsTotal']++;
|
||||
switch ($testType) {
|
||||
case 'mbti':
|
||||
$data['testsMbti']++;
|
||||
$data['lastMbtiResultId'] = $testResultId;
|
||||
break;
|
||||
case 'disc':
|
||||
$data['testsDisc']++;
|
||||
$data['lastDiscResultId'] = $testResultId;
|
||||
break;
|
||||
case 'pdp':
|
||||
$data['testsPdp']++;
|
||||
$data['lastPdpResultId'] = $testResultId;
|
||||
break;
|
||||
case 'face':
|
||||
case 'ai':
|
||||
$data['testsFace']++;
|
||||
$data['lastFaceResultId'] = $testResultId;
|
||||
break;
|
||||
}
|
||||
|
||||
$data['lastTestResultId'] = $testResultId;
|
||||
$data['lastTestType'] = $testType;
|
||||
$data['lastTestAt'] = $now;
|
||||
$data['updatedAt'] = $now;
|
||||
|
||||
self::upsertRow($data, $id);
|
||||
}
|
||||
|
||||
/**
|
||||
* 支付成功后更新订单统计与总支付金额
|
||||
*
|
||||
* @param int $userId
|
||||
* @param int|null $enterpriseId
|
||||
* @param int $amountFen 本次支付金额(分)
|
||||
*/
|
||||
public static function recordPayment(int $userId, ?int $enterpriseId, int $amountFen): void
|
||||
{
|
||||
if ($userId <= 0 || $amountFen <= 0) {
|
||||
return;
|
||||
}
|
||||
$now = time();
|
||||
$userType = $enterpriseId ? 'enterprise' : 'personal';
|
||||
|
||||
[$data, $id] = self::loadOrInitRow($userId, $userType, $enterpriseId, $now);
|
||||
|
||||
$data['ordersTotal'] = (int) ($data['ordersTotal'] ?? 0) + 1;
|
||||
$data['paidOrders'] = (int) ($data['paidOrders'] ?? 0) + 1;
|
||||
$currentTotal = (int) ($data['totalPaidAmount'] ?? 0);
|
||||
$data['totalPaidAmount'] = $currentTotal + $amountFen;
|
||||
$data['updatedAt'] = $now;
|
||||
|
||||
self::upsertRow($data, $id);
|
||||
}
|
||||
|
||||
/**
|
||||
* 读或初始化一行画像数据
|
||||
*/
|
||||
protected static function loadOrInitRow(int $userId, string $userType, ?int $enterpriseId, int $now): array
|
||||
{
|
||||
$where = [
|
||||
'userId' => $userId,
|
||||
'userType' => $userType,
|
||||
'enterpriseId' => $enterpriseId,
|
||||
];
|
||||
|
||||
$row = Db::name('user_profile')->where($where)->lock(true)->find();
|
||||
|
||||
$base = [
|
||||
'testsTotal' => 0,
|
||||
'testsMbti' => 0,
|
||||
'testsDisc' => 0,
|
||||
'testsPdp' => 0,
|
||||
'testsFace' => 0,
|
||||
'ordersTotal' => 0,
|
||||
'paidOrders' => 0,
|
||||
'totalPaidAmount' => 0,
|
||||
'lastMbtiResultId'=> null,
|
||||
'lastDiscResultId'=> null,
|
||||
'lastPdpResultId' => null,
|
||||
'lastFaceResultId'=> null,
|
||||
];
|
||||
|
||||
if ($row) {
|
||||
$data = array_merge($base, $row);
|
||||
$id = (int) $row['id'];
|
||||
} else {
|
||||
$data = array_merge($base, $where, ['createdAt' => $now]);
|
||||
$id = 0;
|
||||
}
|
||||
|
||||
return [$data, $id];
|
||||
}
|
||||
|
||||
/**
|
||||
* 写入或更新一行画像数据
|
||||
*/
|
||||
protected static function upsertRow(array $data, int $id): void
|
||||
{
|
||||
if ($id > 0) {
|
||||
Db::name('user_profile')->where('id', $id)->update($data);
|
||||
} else {
|
||||
Db::name('user_profile')->insert($data);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
56
api/app/model/WechatUser.php
Normal file
56
api/app/model/WechatUser.php
Normal file
@@ -0,0 +1,56 @@
|
||||
<?php
|
||||
namespace app\model;
|
||||
|
||||
use think\Model;
|
||||
|
||||
/**
|
||||
* 微信小程序用户模型
|
||||
*/
|
||||
class WechatUser extends Model
|
||||
{
|
||||
protected $name = 'wechat_users';
|
||||
|
||||
protected $schema = [
|
||||
'id' => 'int',
|
||||
'openid' => 'string',
|
||||
'unionid' => 'string',
|
||||
'sessionKey' => 'string',
|
||||
'nickname' => 'string',
|
||||
'avatar' => 'string',
|
||||
'phone' => 'string',
|
||||
'gender' => 'int',
|
||||
'country' => 'string',
|
||||
'province' => 'string',
|
||||
'city' => 'string',
|
||||
'birthday' => 'string',
|
||||
'status' => 'int',
|
||||
'lastLoginAt' => 'int',
|
||||
'lastLoginIp' => 'string',
|
||||
'enterpriseId' => 'int',
|
||||
'createdAt' => 'int',
|
||||
'updatedAt' => 'int',
|
||||
];
|
||||
|
||||
protected $autoWriteTimestamp = 'int';
|
||||
protected $createTime = 'createdAt';
|
||||
protected $updateTime = 'updatedAt';
|
||||
|
||||
protected $hidden = ['sessionKey', 'openid'];
|
||||
|
||||
protected $type = [
|
||||
'lastLoginAt' => 'integer',
|
||||
'createdAt' => 'integer',
|
||||
'updatedAt' => 'integer',
|
||||
];
|
||||
|
||||
/**
|
||||
* 返回给前端的用户信息(不包含敏感字段)
|
||||
*/
|
||||
public function toApiArray(): array
|
||||
{
|
||||
$row = $this->toArray();
|
||||
unset($row['sessionKey'], $row['openid']);
|
||||
$row['avatarUrl'] = $row['avatar'] ?? '';
|
||||
return $row;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user