Files
li 7d7f53b356 feat: contract uses num_lh, male avatar, captcha upgrade, full i18n
- Contract buy/close/cancel all use num_lh (contract balance) not num (spot)
- Wallet API returns contract (lh) account data block
- My.php returns user id field for frontend display
- Avatar: DiceBear avataaars male business style
- Captcha: 3x scaled text for readability
- Transfer log descriptions in English
- All billing descriptions in English
2026-04-05 13:05:37 +08:00

431 lines
15 KiB
PHP
Executable File

<?php
namespace app\api\controller;
use app\common\controller\Api;
use app\common\exception\UploadException;
use app\common\library\Upload;
use app\common\model\Area;
use app\common\model\Version;
use fast\Random;
use think\Config;
use think\Hook;
use think\Db;
/**
* 公共接口
*/
class Common extends Api
{
protected $noNeedLogin = "*";
protected $noNeedRight = '*';
/**
* 加载初始化
*
* @param string $version 版本号
* @param string $lng 经度
* @param string $lat 纬度
*/
public function init()
{
if ($version = $this->request->request('version')) {
$lng = $this->request->request('lng');
$lat = $this->request->request('lat');
//配置信息
$upload = Config::get('upload');
//如果非服务端中转模式需要修改为中转
if ($upload['storage'] != 'local' && isset($upload['uploadmode']) && $upload['uploadmode'] != 'server') {
//临时修改上传模式为服务端中转
set_addon_config($upload['storage'], ["uploadmode" => "server"], false);
$upload = \app\common\model\Config::upload();
// 上传信息配置后
Hook::listen("upload_config_init", $upload);
$upload = Config::set('upload', array_merge(Config::get('upload'), $upload));
}
$upload['cdnurl'] = $upload['cdnurl'] ? $upload['cdnurl'] : cdnurl('', true);
$upload['uploadurl'] = preg_match("/^((?:[a-z]+:)?\/\/)(.*)/i", $upload['uploadurl']) ? $upload['uploadurl'] : url($upload['storage'] == 'local' ? '/api/common/upload' : $upload['uploadurl'], '', false, true);
$content = [
'citydata' => Area::getCityFromLngLat($lng, $lat),
'versiondata' => Version::check($version),
'uploaddata' => $upload,
'coverdata' => Config::get("cover"),
];
$this->success('', $content);
} else {
$this->error(__('Invalid parameters'));
}
}
/**
* 上传文件
* @ApiMethod (POST)
* @param File $file 文件流
*/
public function upload()
{
Config::set('default_return_type', 'json');
//必须设定cdnurl为空,否则cdnurl函数计算错误
Config::set('upload.cdnurl', '');
$chunkid = $this->request->post("chunkid");
if ($chunkid) {
if (!Config::get('upload.chunking')) {
$this->error(__('Chunk file disabled'));
}
$action = $this->request->post("action");
$chunkindex = $this->request->post("chunkindex/d");
$chunkcount = $this->request->post("chunkcount/d");
$filename = $this->request->post("filename");
$method = $this->request->method(true);
if ($action == 'merge') {
$attachment = null;
//合并分片文件
try {
$upload = new Upload();
$attachment = $upload->merge($chunkid, $chunkcount, $filename);
} catch (UploadException $e) {
$this->error($e->getMessage());
}
$this->success(__('Uploaded successful'), ['url' => $attachment->url, 'fullurl' => cdnurl($attachment->url, true)]);
} elseif ($method == 'clean') {
//删除冗余的分片文件
try {
$upload = new Upload();
$upload->clean($chunkid);
} catch (UploadException $e) {
$this->error($e->getMessage());
}
$this->success();
} else {
//上传分片文件
//默认普通上传文件
$file = $this->request->file('file');
try {
$upload = new Upload($file);
$upload->chunk($chunkid, $chunkindex, $chunkcount);
} catch (UploadException $e) {
$this->error($e->getMessage());
}
$this->success();
}
} else {
$attachment = null;
//默认普通上传文件
$file = $this->request->file('file');
try {
$upload = new Upload($file);
$attachment = $upload->upload();
} catch (UploadException $e) {
$this->error($e->getMessage());
}
$this->success(__('Uploaded successful'), ['url' => $attachment->url, 'fullurl' => cdnurl($attachment->url, true)]);
}
}
/**
* 单文件上传公共
* @param [type] $file_url [保存地址]
* @return [type] [description]
*/
public function iamge_upload_single($file_url)
{
$file = $this->request->file('file');
if (empty($file)) {
$this->error("请选择文件");
}
$url = $file_url;
$dst_img = $this->upload_image($file, $url);
if ($dst_img) {
if(mb_substr($dst_img, 0,1) != "/"){
$dst_img = "/".$dst_img;
}
$this->success("Upload successful", [
'iamge_url' => Config::get('site.image_url').$dst_img,
'url' => $dst_img,
]);
} else {
$this->error("Upload failed", [
'info' => $this->error('error'),
]);
}
}
/**
* 公共上传图片
* $file 上传对象
* by sen
*/
public function upload_image($file, $category) {
//判断是否已经存在附件
$sha1 = $file->hash();
$upload = Config::get('upload');
preg_match('/(\d+)(\w+)/', $upload['maxsize'], $matches);
$type = strtolower($matches[2]);
$typeDict = ['b' => 0, 'k' => 1, 'kb' => 1, 'm' => 2, 'mb' => 2, 'gb' => 3, 'g' => 3];
$size = (int) $upload['maxsize'] * pow(1024, isset($typeDict[$type]) ? $typeDict[$type] : 0);
$fileInfo = $file->getInfo();
$suffix = strtolower(pathinfo($fileInfo['name'], PATHINFO_EXTENSION));
$suffix = $suffix ? $suffix : 'file';
$mimetypeArr = explode(',', strtolower($upload['mimetype']));
$typeArr = explode('/', $fileInfo['type']);
//验证文件后缀
if ($upload['mimetype'] !== '*' &&
(
!in_array($suffix, $mimetypeArr) || (stripos($typeArr[0] . '/', $upload['mimetype']) !== false && (!in_array($fileInfo['type'], $mimetypeArr) && !in_array($typeArr[0] . '/*', $mimetypeArr)))
)
) {
$this->error(__('Uploaded file format is limited'));
}
$replaceArr = [
'{year}' => date("Y"),
'{mon}' => date("m"),
'{day}' => date("d"),
'{hour}' => date("H"),
'{min}' => date("i"),
'{sec}' => date("s"),
'{random}' => Random::alnum(16),
'{random32}' => Random::alnum(32),
'{filename}' => $suffix ? substr($fileInfo['name'], 0, strripos($fileInfo['name'], '.')) : $fileInfo['name'],
'{suffix}' => $suffix,
'{.suffix}' => $suffix ? '.' . $suffix : '',
'{filemd5}' => md5_file($fileInfo['tmp_name']),
];
$savekey = $upload['savekey'];
// $savekey = $upload['savekey'];
$savekey = str_replace(array_keys($replaceArr), array_values($replaceArr), $savekey);
$uploadDir = $category . substr($savekey, 0, strripos($savekey, '/') + 1);
$fileName = substr($savekey, strripos($savekey, '/') + 1);
$splInfo = $file->validate(['size' => $size])->move(ROOT_PATH . '/public' . $uploadDir, $fileName);
if ($splInfo) {
$imagewidth = $imageheight = 0;
if (in_array($suffix, ['gif', 'jpg', 'jpeg', 'bmp', 'png', 'swf'])) {
$imgInfo = getimagesize($splInfo->getPathname());
$imagewidth = isset($imgInfo[0]) ? $imgInfo[0] : $imagewidth;
$imageheight = isset($imgInfo[1]) ? $imgInfo[1] : $imageheight;
}
$params = array(
'admin_id' => 0,
'user_id' => (int) $this->auth->id,
'filesize' => $fileInfo['size'],
'imagewidth' => $imagewidth,
'imageheight' => $imageheight,
'imagetype' => $suffix,
'imageframes' => 0,
'mimetype' => $fileInfo['type'],
'url' => $uploadDir . $splInfo->getSaveName(),
'uploadtime' => time(),
'storage' => 'local',
'sha1' => $sha1,
);
$attachment = model("attachment");
$attachment->data(array_filter($params));
$attachment->save();
\think\Hook::listen("upload_after", $attachment);
//-----压缩
import('lib.imgcompress', EXTEND_PATH , '.class.php');
$source = $uploadDir . $splInfo->getSaveName();
$dst_img = substr($uploadDir . 'compress_' . $fileName, 1); //可加存放路径
$percent = 1; #原图压缩,不缩放
$new_resource = substr($source, 1);
$imgcompress = new \imgcompress($new_resource, $percent);
$image = $imgcompress->compressImg($dst_img);
unset($splInfo);
@unlink($new_resource);
return $dst_img;
} else {
// 上传失败获取错误信息
return $this->error($file->getError());
}
}
/**
* 生成二维码
* $param 生成参数
* $filename 文件名
*/
public function qrcode_s($param, $file_name , $matrixPointSize=8) {
header("Content-type: text/html; charset=utf-8");
import('lib.qrcode', EXTEND_PATH , '.php');
$QRcode = new \QRcode();
$errorCorrectionLevel = 'L'; //容错级别
// $matrixPointSize = 8; //生成图片大小
//生成二维码图片
$filename = $file_name . '.png';
$res = $QRcode->png($param, $filename, $errorCorrectionLevel, $matrixPointSize, 2);
$info = '/' . $filename;
return $info;
}
/**
*
* @param type $type 1=初级认证,2=高级认证
* @return boolean
*/
public function get_auth($type = 1){
if($type == 1){
$auth = Db::name("app_auth")->where("user_id",$this->auth->id)->where("status",1)->find();
if(empty($auth)){
return false;
}else{
return true;
}
}else{
$auth = Db::name("app_auth")->where("user_id",$this->auth->id)->where("status",1)->find();
if(empty($auth)){
return false;
}else{
return true;
}
}
}
/**
* TRC加密
*/
public function trc_encryption($key)
{
$str = "123123456789456789qwertyuioplkjhgfdsazxcvbnm123456789";
$keys = substr(str_shuffle($str), 0,8).$key.substr(str_shuffle($str), 0,9);
$string = base64_encode($keys);
return $string;
}
/**
* TRC解密
*/
public function trc_decrypt($key)
{
$string = base64_decode($key);
$key = substr($string, 8,-9);
return $key;
}
/**
* 注册验证码图片 - 生成算术验证码并返回base64图片
* GET /api/common/captcha
*/
public function captcha()
{
// 生成4位随机大写英文字母
$chars = 'ABCDEFGHJKLMNPQRSTUVWXYZ'; // 去掉易混淆的 I O
$answer = '';
for ($i = 0; $i < 4; $i++) {
$answer .= $chars[mt_rand(0, strlen($chars) - 1)];
}
// 生成唯一key标识本次验证码
$captcha_key = md5(uniqid(mt_rand(), true));
cache('captcha_' . $captcha_key, strtoupper($answer), 300);
// 先在小画布绘制文字,再等比放大 → 让内置字体看起来更大
$smallW = 100;
$smallH = 32;
$small = imagecreatetruecolor($smallW, $smallH);
$smallBg = imagecolorallocate($small, 240, 244, 250);
imagefilledrectangle($small, 0, 0, $smallW, $smallH, $smallBg);
// 干扰线(小画布上)
for ($i = 0; $i < 3; $i++) {
$lc = imagecolorallocate($small, mt_rand(160, 210), mt_rand(160, 210), mt_rand(160, 210));
imageline($small, mt_rand(0, $smallW), mt_rand(0, $smallH), mt_rand(0, $smallW), mt_rand(0, $smallH), $lc);
}
// 文字(内置最大字号5,字符宽9px)
$fontSize = 5;
$charW = imagefontwidth($fontSize);
$charH = imagefontheight($fontSize);
$totalW = $charW * 4;
$startX = ($smallW - $totalW) / 2;
$startY = ($smallH - $charH) / 2;
for ($i = 0; $i < 4; $i++) {
$tc = imagecolorallocate($small, mt_rand(10, 70), mt_rand(10, 70), mt_rand(10, 70));
imagestring($small, $fontSize, (int)($startX + $i * $charW), (int)$startY, $answer[$i], $tc);
}
// 放大3倍到输出画布
$scale = 3;
$width = $smallW * $scale;
$height = $smallH * $scale;
$img = imagecreatetruecolor($width, $height);
imagecopyresampled($img, $small, 0, 0, 0, 0, $width, $height, $smallW, $smallH);
imagedestroy($small);
// 干扰点(放大后再叠加,避免被放大模糊)
for ($i = 0; $i < 60; $i++) {
$dc = imagecolorallocate($img, mt_rand(150, 230), mt_rand(150, 230), mt_rand(150, 230));
imagesetpixel($img, mt_rand(0, $width), mt_rand(0, $height), $dc);
}
ob_start();
imagepng($img);
$imgData = ob_get_clean();
imagedestroy($img);
$base64 = 'data:image/png;base64,' . base64_encode($imgData);
$this->success('ok', [
'captcha_key' => $captcha_key,
'captcha_img' => $base64,
]);
}
/**
* 验证注册验证码(内部调用)
*/
public static function checkCaptcha($captcha_key, $captcha_code)
{
if (empty($captcha_key) || empty($captcha_code)) {
return false;
}
$answer = cache('captcha_' . $captcha_key);
if ($answer === false || $answer === null) {
return false;
}
$result = (strtoupper(trim($captcha_code)) === strtoupper(trim($answer)));
// 验证后立即删除,防止重放
cache('captcha_' . $captcha_key, null);
return $result;
}
/**
* 生成随机key secrit
*/
public function set_key()
{
$str = "1231234567894567891234567890000qwertyuioplkjhgfdsazxcvbnm123456789qwertyuioplkjhgfdsazxcvbnmqwertyuioplkjhgfdsazxcvbnmqwertyuioplkjhgfdsazxcvbnm123456789";
$key = substr(str_shuffle($str), 0,rand(8, 10))."-".substr(str_shuffle($str), 0,rand(8, 10))."-".substr(str_shuffle($str), 0,rand(8, 10))."-".substr(str_shuffle($str), 0,rand(4, 12));
$secret = substr(str_shuffle($str), 0,rand(8, 10))."-".substr(str_shuffle($str), 0,rand(8, 10))."-".substr(str_shuffle($str), 0,rand(8, 10))."-".substr(str_shuffle($str), 0,rand(4, 12));
$data = array(
"key" => $key,
"secret" => $secret,
);
return $data;
}
}