Merge remote-tracking branch 'origin/cyh666666/consumer'

This commit is contained in:
not-like-juvenile
2026-03-18 20:34:55 +08:00
2321 changed files with 130733 additions and 36674 deletions

View File

@@ -81,11 +81,11 @@ export class AkSupaQueryBuilder {
like(field : string, value : any) : AkSupaQueryBuilder { return this._addCond(field, 'like', value); }
ilike(field : string, value : any) : AkSupaQueryBuilder { return this._addCond(field, 'ilike', value); }
in(field : string, value : any[]) : AkSupaQueryBuilder { return this._addCond(field, 'in', value); }
is(field : string, value : any) : AkSupaQueryBuilder { return this._addCond(field, 'is', value); }
is(field : string, value : any | null) : AkSupaQueryBuilder { return this._addCond(field, 'is', value); }
contains(field : string, value : any) : AkSupaQueryBuilder { return this._addCond(field, 'cs', value); }
containedBy(field : string, value : any) : AkSupaQueryBuilder { return this._addCond(field, 'cd', value); }
not(field : string, opOrValue : any, value?: any) : AkSupaQueryBuilder {
if (value !== undefined) {
not(field : string, opOrValue : any, value: any | null = null) : AkSupaQueryBuilder {
if (value != null) {
// 三元形式field, operator, value
// 例如 not('badge', 'is', null) -> badge=not.is.null
const combinedOp = 'not.' + opOrValue;
@@ -115,15 +115,31 @@ export class AkSupaQueryBuilder {
return this;
}
private _addCond(afield : string, op : string, value : any) : AkSupaQueryBuilder {
private _addCond(afield : string, op : string, value : any | null) : AkSupaQueryBuilder {
//console.log('add cond:', op, afield, value)
const field = encodeURIComponent(afield)!!
// 将 null 转换为字符串 'null',避免构造对象时缺少 value 属性
let safeValue = value;
// 将值安全存储,避免安卓端类型转换问题
let safeValue: any | null = value;
if (value === null) {
safeValue = 'null';
} else if (Array.isArray(value)) {
// 数组类型保持原样,用于 in 操作符
safeValue = value;
} else if (typeof value === 'number') {
// 数字类型保持原样
safeValue = value;
} else if (typeof value === 'boolean') {
// 布尔类型保持原样
safeValue = value;
} else if (typeof value !== 'string') {
// 其他类型尝试转换为字符串
try {
safeValue = value.toString();
} catch (e) {
safeValue = '';
}
}
this._conditions.push({ field, op, value: safeValue, logic: this._nextLogic });
this._conditions.push({ field, op, value: safeValue ?? '', logic: this._nextLogic });
//console.log(this._conditions)
this._nextLogic = 'and';
return this;
@@ -211,6 +227,23 @@ export class AkSupaQueryBuilder {
//console.log('设置 range:', from, 'to', to);
return this;
}
// 辅助函数:安全地将值转换为字符串
private _valToStr(val: any): string {
if (val == null) return '';
try {
// 尝试直接调用 toString
return val.toString();
} catch (e) {
try {
// 尝试 JSON 序列化
return JSON.stringify(val);
} catch (e2) {
return '';
}
}
}
// 将 _conditions 强类型直接转换为 Supabase/PostgREST 查询字符串(不再用 UTSJSONObject 做中转)
private _buildFilter() : string | null {
if (this._conditions.length == 0 && (this._orString==null || this._orString == "")) {
@@ -238,12 +271,13 @@ export class AkSupaQueryBuilder {
const op = cond.op;
const val = cond.value;
if ((op == 'in' || op == 'not.in') && Array.isArray(val)) {
params.push(`${k}=${op}.(${val.map(x => typeof x == 'object' ? encodeURIComponent(JSON.stringify(x)) : encodeURIComponent(x.toString())).join(',')})`);
params.push(`${k}=${op}.(${val.map(x => this._valToStr(x)).map(x => encodeURIComponent(x)).join(',')})`);
} else if ((op == 'is' || op == 'not.is') && (val == null || val == 'null')) {
params.push(`${k}=${op}.null`);
} else if (op == 'like' || op == 'ilike') {
params.push(`${k}=${op}.${this._valToStr(val)}`);
} else {
const opvalstr: string = (typeof val == 'object') ? JSON.stringify(val) : (val as string);
params.push(`${k}=${op}.${encodeURIComponent(opvalstr)}`);
params.push(`${k}=${op}.${encodeURIComponent(this._valToStr(val))}`);
}
}
// 处理 or 条件
@@ -253,22 +287,26 @@ export class AkSupaQueryBuilder {
const op = o.op;
const val = o.value;
if (op == "in" && Array.isArray(val)) {
return `${k}.in.(${val.map(x => encodeURIComponent(x as string)).join(",")})`;
return `${k}.in.(${val.map(x => encodeURIComponent(this._valToStr(x))).join(",")})`;
}
if (op == "is" && (val == null)) {
return `${k}.is.null`;
}
return `${k}.${op}.${encodeURIComponent(val as string)}`;
if (op == "like" || op == "ilike") {
return `${k}.${op}.${this._valToStr(val)}`;
}
return `${k}.${op}.${encodeURIComponent(this._valToStr(val))}`;
}).join(",");
params.push(`or=(${orStr})`);
}
if (this._orString!=null && this._orString !== "") {
params.push(`or=(${encodeURIComponent(this._orString!!)})`);
console.log('[AkSupaQueryBuilder] or字符串:', this._orString)
params.push(`or=(${this._orString!!})`);
}
return params.length > 0 ? params.join('&') : null;
}
select(columns ?: string, opt ?: UTSJSONObject) : AkSupaQueryBuilder {
select(columns : string = "*", opt : UTSJSONObject | null = null) : AkSupaQueryBuilder {
this._action = 'select';
if (columns != null) {
this._options.columns = columns;
@@ -318,7 +356,7 @@ export class AkSupaQueryBuilder {
async execute() : Promise<AkReqResponse<any>> {
//console.log('execute')
const filter = this._buildFilter();
//console.log('execute', filter)
console.log('[AkSupaQueryBuilder] execute - 表:', this._table, 'filter:', filter)
let res : any;
switch (this._action) {
case 'select': {
@@ -366,12 +404,23 @@ export class AkSupaQueryBuilder {
}
}
if (total == 0) {
if (typeof res['count'] == 'number') {
total = res['count'] as number ?? 0;
// 使用 JSON 序列化访问 res 对象
const resStr = JSON.stringify(res)
const resParsed = JSON.parse(resStr)
if (resParsed != null) {
const resObj = resParsed as UTSJSONObject
const countVal = resObj.getNumber('count')
if (countVal != null) {
total = countVal
} else if (Array.isArray(resdata)) {
total = resdata.length
} else {
total = 0
}
} else if (Array.isArray(resdata)) {
total = resdata.length;
total = resdata.length
} else {
total = 0;
total = 0
}
}
if (!hasmore) hasmore = (page * limit) < total; // 如果是 head 模式,只返回 count 信息
@@ -433,41 +482,24 @@ export class AkSupaQueryBuilder {
if (res["data"] == null) res["data"] = {};
return res;
} // 新增:支持类型转换的执行方法
async executeAs<T = any>() : Promise<AkReqResponse<T | Array<T>>> {
async executeAs<T>() : Promise<AkReqResponse<T>> {
const result = await this.execute();
// 如果原始 data 是 null直接返回 null
if (result.data == null) {
const aaa = {
status: result.status,
data: null,
headers: result.headers,
error: result.error,
total: result.total,
page: result.page,
limit: result.limit,
hasmore: result.hasmore,
origin: result.origin
}
return aaa;
return result as AkReqResponse<T>;
}
// 尝试类型转换
let convertedData : T | Array<T> | null = null;
let convertedData : any | null = null;
try {
if (Array.isArray(result.data)) {
// 处理数组数据
const dataArray = result.data;
const convertedArray : Array<T> = [];
//console.log(convertedArray)
const convertedArray : Array<any> = [];
for (let i = 0; i < dataArray.length; i++) {
const item = dataArray[i];
if (item instanceof UTSJSONObject) {
// #ifdef APP-ANDROID
// //console.log(item)
const parsed = item.parse<T>();
// //console.log('ak parsed')
// #endif
// #ifndef APP-ANDROID
const parsed = item as T;
@@ -476,10 +508,9 @@ export class AkSupaQueryBuilder {
convertedArray.push(parsed);
} else {
console.warn('转换失败,使用原始对象:', item);
convertedArray.push(item as T);
convertedArray.push(item);
}
} else {
// 将普通对象转换为 UTSJSONObject 后再 parse
const jsonObj = new UTSJSONObject(item);
// #ifdef APP-ANDROID
const parsed = jsonObj.parse<T>();
@@ -492,15 +523,14 @@ export class AkSupaQueryBuilder {
}
else {
console.warn('转换失败,使用原始对象:', item);
convertedArray.push(item as T);
convertedArray.push(item);
}
}
}
convertedData = convertedArray;
} else {
// 处理单个对象
const convertedArray : Array<T> = [];
const convertedArray : Array<any> = [];
if (result.data instanceof UTSJSONObject) {
const parsed = result.data.parse<T>();
@@ -525,8 +555,7 @@ export class AkSupaQueryBuilder {
} catch (e) {
console.warn('数据类型转换失败,使用原始数据:', e);
console.log(result.data)
// 转换失败时,使用原始数据
convertedData = result.data as T | Array<T>;
convertedData = result.data as any;
}
result.data = convertedData
const aaa = result as AkReqResponse<T | Array<T>>
@@ -543,6 +572,8 @@ export class AkSupaQueryBuilder {
// }
return aaa;
//cyh
//return result as AkReqResponse<T>;
}
}
@@ -761,7 +792,7 @@ export class AkSupa {
token_type: 'bearer',
expires_in: 0,
raw: user
} as any;
} as AkSupaSignInResult;
}
return true;
} catch (e) {
@@ -793,14 +824,17 @@ export class AkSupa {
if (this.apikey == null || this.apikey.trim() === '' || this.apikey === 'your-anon-key') {
throw new Error('Supabase 配置错误:请在 ak/config.uts 中设置 SUPA_KEY当前为占位符');
}
const headers = new UTSJSONObject()
headers.set('apikey', this.apikey)
headers.set('Content-Type', 'application/json')
const reqData = new UTSJSONObject()
reqData.set('email', email)
reqData.set('password', password)
const res = await AkReq.request({
url: this.baseUrl + '/auth/v1/token?grant_type=password',
method: 'POST',
headers: {
apikey: this.apikey,
'Content-Type': 'application/json'
} as UTSJSONObject,
data: { email, password } as UTSJSONObject,
headers: headers,
data: reqData,
contentType: 'application/json'
}, false);
// 如果响应不是 2xx例如 401提取后端错误信息并抛出便于上层显示具体原因
@@ -810,7 +844,14 @@ export class AkSupa {
try {
if (res.data != null) {
const obj = new UTSJSONObject(res.data);
msg = obj.getString('message') ?? obj.getString('error') ?? obj.getString('msg') ?? obj.getString('description') ?? obj.getString('error_description') ?? msg;
const rawMsg = obj.getString('message') ?? obj.getString('error') ?? obj.getString('msg') ?? obj.getString('description') ?? obj.getString('error_description') ?? '';
// 核心修复:在这里拦截英文错误并转换为中文
if (rawMsg.includes('Invalid login credentials')) {
msg = '用户名或密码错误';
} else if (rawMsg != '') {
msg = rawMsg;
}
}
} catch (e) {
// ignore
@@ -1094,7 +1135,7 @@ async delete(table : string, filter : string | null) : Promise<AkReqResponse<any
url,
method: 'POST',
headers,
data: params ?? {},
data: params ?? new UTSJSONObject(),
contentType: 'application/json'
};
return await this.requestWithAutoRefresh(reqOptions);
@@ -1106,18 +1147,36 @@ async delete(table : string, filter : string | null) : Promise<AkReqResponse<any
from(tableName : string) : AkSupaQueryBuilder {
return new AkSupaQueryBuilder(this, tableName);
}
/**
* 创建实时订阅通道 (兼容 Supabase Realtime 接口,目前使用轮询模拟)
* @param topic 通道名称,如 public:table
*/
channel(topic: string): AkSupaRealtimeChannel {
return new AkSupaRealtimeChannel(this, topic);
}
/**
* 移除通道
*/
removeChannel(channel: AkSupaRealtimeChannel): Promise<string> {
channel.unsubscribe();
return Promise.resolve('ok');
}
// AkSupa类内新增自动刷新session
async refreshSession() : Promise<boolean> {
if (this.session == null || this.session?.refresh_token == null) return false;
try {
const headers = new UTSJSONObject()
headers.set('apikey', this.apikey)
headers.set('Content-Type', 'application/json')
const data = new UTSJSONObject()
data.set('refresh_token', this.session?.refresh_token)
const res = await AkReq.request({
url: this.baseUrl + '/auth/v1/token?grant_type=refresh_token',
method: 'POST',
headers: {
apikey: this.apikey,
'Content-Type': 'application/json'
} as UTSJSONObject,
data: { refresh_token: this.session?.refresh_token } as UTSJSONObject,
headers: headers,
data: data,
contentType: 'application/json'
}, true);
if (res.status == 200 && (res.data != null)) {
@@ -1146,6 +1205,23 @@ async delete(table : string, filter : string | null) : Promise<AkReqResponse<any
}
}
async updateUserMetadata(metadata: UTSJSONObject): Promise<UTSJSONObject> {
const headers = new UTSJSONObject()
headers.set('apikey', this.apikey)
headers.set('Content-Type', 'application/json')
headers.set('Authorization', `Bearer ${AkReq.getToken() ?? ''}`)
const data = new UTSJSONObject()
data.set('data', metadata)
const res = await AkReq.request({
url: this.baseUrl + '/auth/v1/user',
method: 'PUT',
headers: headers,
data: data,
contentType: 'application/json'
}, false);
return res.data as UTSJSONObject;
}
// AkSupa类内新增自动刷新封装
async requestWithAutoRefresh(reqOptions : AkReqOptions, isRetry = false) : Promise<AkReqResponse<any>> {
let res = await AkReq.request(reqOptions, false);