Deprecated: parse_url(): Passing null to parameter #1 ($url) of type string is deprecated in /www/wwwroot/blog_qqvbc_com/usr/plugins/Access/Access_Core.php on line 339

Deprecated: parse_url(): Passing null to parameter #1 ($url) of type string is deprecated in /www/wwwroot/blog_qqvbc_com/usr/plugins/Access/Access_Core.php on line 392

Deprecated: parse_url(): Passing null to parameter #1 ($url) of type string is deprecated in /www/wwwroot/blog_qqvbc_com/usr/plugins/Access/Access_Core.php on line 394
Joyber 的博客

MySQL 设计 JSON_UNQUOTE() 函数的核心作用是去除 JSON 字符串值的双引号,将其转换为普通的 SQL 文本字符串。 [1] (https://blog.51cto.com/xiongod/13537324)

设计背景与主要作用

  1. 解决 JSON_EXTRACT 带引号的问题
    • 当你使用 JSON_EXTRACT() 从 JSON 字段中提取某个字符串类型的值时(例如 JSON_EXTRACT(data, '$.name')),MySQL 返回的结果默认会带有双引号(如 "张三")。
    • 这在直接用于 SQL 比较、拼接到普通文本或作为常规 VARCHAR 字段展示时会产生多余的引号。JSON_UNQUOTE() 专门用来剥离这层双引号,还原为纯文本 张三。
    • 你也可以使用简写操作符 ->>,它等价于 JSON_UNQUOTE(JSON_EXTRACT(...))。 [1] (https://www.cnblogs.com/gbase/articles/18429594), [2] (https://blog.csdn.net/codedadi/article/details/139401935), [3] (https://blog.51cto.com/xiongod/13537324)
  2. 正确解析 JSON 中的转义字符
    • 除了去除首尾双引号外,该函数还会将 JSON 字符串中的转义字符还原为实际字符。
    • 例如:将 \n(转义的换行符)、\t(制表符)或 \" 转换为真正的换行、制表符和普通引号。 [1] (https://doris.apache.org/zh-CN/docs/4.x/sql-manual/sql-functions/scalar-functions/json-functions/json-unquote/)
  3. 处理 NULL 值
    • 如果传入的参数是 NULL,该函数会安全地返回 NULL,不会报错。 [1] (https://doris.apache.org/zh-CN/docs/4.x/sql-manual/sql-functions/scalar-functions/json-functions/json-unquote/)

简单示例对比

假设表中有字段 info 存储了 {"name": "张三"}:
• 使用 JSON_EXTRACT:sql
SELECT JSON_EXTRACT(info, '$.name') FROM users;
-- 输出结果(带有双引号):"张三"
请谨慎使用此类代码。
• 使用 JSON_UNQUOTE(或 ->> 操作符):sql
SELECT JSON_UNQUOTE(JSON_EXTRACT(info, '$.name')) FROM users;
-- 或者简写:
SELECT info->>'$.name' FROM users;
-- 输出结果(纯文本):张三
请谨慎使用此类代码。

核心前提

Qoder CN、TRAE IDE 都支持 OpenAI兼容接口,你本地起 Ollama / vLLM 服务,填入自定义endpoint就能接入,适合IDEA插件模式;优先推荐两套主力开源代码模型,适配PHP/Java后端开发,中文友好、license宽松:

最佳主选:Qwen2.5-Coder(通义千问代码系列)
备选:DeepSeek-Coder V2 / V3
轻量补全小模型:Qwen2.5-Coder-1.5B-base(行内tab补全,FIM填充)

1、Qwen2.5-Coder(最推荐,适配你的PHP/Java业务)

  • 许可:Apache2.0,商用无限制,国内模型,中文注释、框架(Spring、Yii2)理解很强,长上下文128K
  • 规格选型:

    • 日常主力对话、重构、查bug:qwen2.5-coder:7b-instruct(16G显存可跑,INT4量化)
    • 强推理、复杂多文件改造、梳理项目逻辑:qwen2.5-coder:14b / 32b-instruct(24G+显存)
    • 行内实时自动补全(tab补全):qwen2.5-coder:1.5b-base,专门支持FIM fill-in-middle,延迟很低
  • 适配Qoder / TRAE:Ollama一键拉起,天然openai兼容接口,对PHP、MySQL位运算、雪花、Composer这类后端细节理解到位

2、DeepSeek-Coder V2

  • 许可:MIT,商用友好,2T代码token训练,多语言强,长上下文128K,SWE-bench成绩优秀
  • 规格:deepseek-coder:6.7b-instruct / 33b;适合算法、复杂逻辑、底层代码
  • 短板:中文框架理解略弱于Qwen,国内资料少一点

备选(按需)

  1. Codestral 22B:Mistral,FIM很强,补全顺滑;⚠️许可限制,不适合商用项目,只适合个人学习
  2. StarCoder2 15B:BigCode,训练数据全部开源透明,合规敏感团队优先;性能略弱Qwen/Deepseek
  3. CodeLlama:老牌,现在性价比不如上面两个

硬件搭配方案(本地Ollama对接Qoder/TRAE)

  1. 消费卡12G显存(3060/4060):

    • 主对话:Qwen2.5-Coder 7B INT4
    • tab补全:1.5B base
  2. 24G显存(4090):直接上 14B / 32B INT4,体验接近云端
  3. 无独显(CPU/内存):7B量化gguf也能跑,响应慢,适合偶尔查代码

接入方式(Qoder CN IDEA插件 + TRAE通用)

  1. 本地安装 Ollama,ollama pull qwen2.5-coder:7b-instruct
  2. Ollama默认地址:[http://127.0.0.1:11434/v1](http://127.0.0.1:11434/v1),openai兼容
  3. Qoder / TRAE 设置 → 添加自定义模型,类型选OpenAI Compatible,填base_url,api_key随便填一个字符串,model id写 qwen2.5-coder:7b-instruct
  4. 推荐分工(性能最优):

    • 智能体对话、重构、查bug:7B/14B instruct
    • 行内实时tab自动补全:单独挂 1.5B base 小模型

不推荐的坑点

  • 不要用通用大模型(Qwen通用、Llama3通用)做代码助手,代码专项模型差距很大
  • 不要把32B以上大模型拿来做tab实时补全,延迟太高
  • Codestral license有生产限制,企业业务项目避开

一句话总结

你做PHP/Java后端开发、IDEA+Qoder+TRAE这套栈:首选 Qwen2.5-Coder 全家,7B instruct做对话,1.5B base做补全;显卡充足再上32B。


    /**
     * 生成 uuid v4
     * @return string
     */
    public static function uuidv4()
    {
        try {
            $data = random_bytes(16);
        } catch (\Exception $e) {
            AppHelper::systemLog($e);
            // 兜底:固定前缀+mt_rand,保持和uuid一样格式
            return sprintf('00000000-%04x-%04x-%04x-%08x%04x',
                mt_rand(0, 0xffff),
                mt_rand(0, 0xffff),
                mt_rand(0, 0xffff),
                mt_rand(0, 0xffffffff),
                mt_rand(0, 0xffff)
            );
        }
        $data[6] = chr(ord($data[6]) & 0x0f | 0x40);
        $data[8] = chr(ord($data[8]) & 0x3f | 0x80);
        return vsprintf('%s%s-%s-%s-%s-%s%s%s', str_split(bin2hex($data), 4));
    }

    /**
     * 生成随机的批次码
     * @param $prefix
     * @param $mode
     * @return string
     */
    public static function randomBatchCode($prefix = '', $mode = 1)
    {
        // UUID v4,标准唯一,无小数点
        $prefix = $prefix != '' ? $prefix . '-' : '';
        $str    = $prefix . self::uuidv4();
        if ($mode != 1) {
            return str_replace('-', '', $str);
        }
        return $str;
    }

    /**
     * 纯数字订单号: 雪花算法,时钟回拨时自动降级到时间随机方案兜底
     * @param string $prefix 订单号前缀
     * @param int $machineId 机器编号 0~31
     * @return string
     */
    public static function createOrderSn($prefix='', int $machineId = 1): string
    {
        static $lastTimestamp = 0;
        static $sequence = 0;
        $epoch = 1609459200000;

        $timestamp = (int)(microtime(true) * 1000);

        // 时钟回拨,直接降级简易方案,不报错
        if ($timestamp < $lastTimestamp) {
            return self::createOrderSnSimple($prefix);
        }

        if ($timestamp === $lastTimestamp) {
            $sequence = ($sequence + 1) & 4095;
            if ($sequence === 0) {
                usleep(1000);
                $timestamp = (int)(microtime(true) * 1000);
                // 再次判断回拨兜底
                if ($timestamp < $lastTimestamp) {
                    return self::createOrderSnSimple($prefix);
                }
            }
        } else {
            $sequence = 0;
        }
        $lastTimestamp = $timestamp;

        $timePart  = $timestamp - $epoch;
        $snowflake = ($timePart << 22) | ($machineId << 17) | $sequence;
        $snowflake = (string)$snowflake;
        return $prefix . $snowflake;
    }

    /**
     * 纯数字订单号生成函数(兜底方案):毫秒时间 + 6位随机,纯数字,永远不会抛异常
     * @param string $prefix 订单号前缀
     * @return string
     */
    public static function createOrderSnSimple($prefix = ''): string
    {
        $ms = (string)(microtime(true) * 1000);
        $ms = explode('.', $ms)[0];
        try {
            $rand_number = random_int(0, 999999);
        } catch (\Exception $e) {
            //AppHelper::systemLog($e);
            $rand_number = mt_rand(0, 999999);
        }
        $rand = str_pad($rand_number, 6, '0', STR_PAD_LEFT);
        return $prefix . $ms . $rand;
    }

Cocos Creator 3.x 引擎系统进阶学习路线(12周)

前提:已掌握 TS 基础、Node/Component 生命周期、能独立做简单小游戏。本路线聚焦工程化、性能、内存、框架封装,不覆盖入门操作。
版本:Cocos Creator 3.8.x,目标平台微信小游戏。

第1周:资源系统进阶(生命周期与分包)

学习目标

  • 吃透 assetManager 的加载、缓存、释放链路,能定位内存泄漏
  • 掌握引用计数 addRef / decRef 的手动管理时机
  • 掌握分包加载与远程资源热更前置能力

核心知识点

  • resources.load / assetManager.loadAny 的加载管线与版本配置
  • 自动释放 vs 手动释放:场景切换后哪些资源会被自动回收
  • Asset.addRef() / decRef() 与内存泄漏的关系
  • 图集 SpriteAtlas 的依赖资源(SpriteFrame)引用链
  • 分包配置:主包 / 分包 / 远程包的加载策略
  • loadRemote 远程图片与缓存清理

练习 Demo:资源加载器封装

  • 写一个 ResManager 单例,封装 load / loadDir / release / releaseAll
  • 支持按模块(UI / 音效 / 场景)分组释放
  • 加载一个预制体 → 实例化 → 延迟销毁 → 主动释放资源,在微信开发者工具 Memory 面板观察纹理是否被回收

验收标准

  • 能解释为什么直接 destroy(node) 后纹理还在内存中
  • 能写出正确释放一个 Prefab 及其依赖纹理的代码
  • 配置一个分包,主包启动时不加载分包资源,进入对应界面时才加载
  • Demo 连续进出场景 10 次,内存曲线不持续上涨

第2周:节点组件架构与对象池

学习目标

  • 摆脱"一个脚本写到底",建立可维护的组件化架构
  • 掌握对象池解决频繁实例化开销

核心知识点

  • 节点树查找的性能代价:find() vs 缓存引用
  • 组件间通信的三种方式:直接 getComponent / 节点事件 / 全局事件总线
  • NodePool 对象池:创建、获取、回收、预热
  • 预制体实例化 instantiate 的开销与适用场景
  • 节点 active 切换 vs destroy 的性能差异

练习 Demo:子弹/掉落物对象池

  • 实现一个 ObjPool<T> 通用对象池组件
  • 生成 200 个移动的子弹,用池化方式复用,禁止每帧 instantiate
  • 对比池化前后帧率(在微信开发者工具 Performance 面板查看)

验收标准

  • 1 秒内生成 100 个对象不卡顿,GC 次数明显下降
  • 对象回收时正确重置状态(位置、旋转、透明度、事件监听)
  • 能说出直接 instantiate 在小游戏低端机上的问题

第3周:UI 框架与列表复用

学习目标

  • 搭出可复用的 UI 框架:弹窗栈、界面基类、自动适配
  • 掌握 ScrollView 高效复用(这是小游戏列表性能关键)

核心知识点

  • UI 基类设计:onOpen / onClose / onShow / onHide 生命周期
  • 弹窗管理器:弹窗栈、遮罩、模态队列、关闭回调
  • Widget 锚点适配与安全区(刘海屏)
  • ScrollView + Layout 普通模式 vs 列表虚拟滚动
  • GridLayout 与列表项预制体复用
  • Label 文字排版、RichText 性能开销

练习 Demo:完整 UI 框架 + 长列表

  • 实现 UIManager:open(prefabPath) / close(uiName) / closeAll
  • 实现一个 1000 条数据的排行榜列表,滚动不卡顿(用复用而非全量创建)
  • 做一个多层弹窗叠加:主界面 → 弹窗A → 弹窗B → 关闭B回到A

验收标准

  • 列表滚动 60fps(中低端机),节点数量保持在屏幕可见项附近
  • 弹窗打开关闭有统一动画和回调,不出现层级错乱
  • 切换不同分辨率设计稿,UI 不错位、不被刘海遮挡

第4周:动画系统进阶

学习目标

  • 掌握 Tween 链式编排与动画状态机,不依赖手动写帧动画
  • 理解 Animation 组件的状态控制与事件

核心知识点

  • tween 链式 API:sequence / parallel / repeat / delay / call
  • Tween 的 stop / pause / resume 与 target 引用管理
  • Animation 组件:play / crossFade / stop,动画状态切换
  • 动画事件(Animation Event)回调到脚本
  • Spine 骨骼动画基础(如角色需要):动画混合、插槽控制
  • 动效性能:避免每帧修改 color / position 触发过度渲染

练习 Demo:游戏开场动效编排

  • 用 Tween 编排:Logo 缩放入场 → 文字淡入 → 按钮弹起 → 可跳过
  • 实现一个角色待机/移动/受伤三个 AnimationClip 的状态切换
  • 给按钮做统一的按压缩放动效(可复用组件)

验收标准

  • 动效时序精确,能在中途跳过且不残留状态
  • 动画切换无突兀跳变(用 crossFade)
  • 关闭界面时正确停止 Tween,防止回调操作已销毁节点

第5周:输入系统与手势

学习目标

  • 脱离 Button 组件,处理复杂触摸手势与多点触控
  • 掌握坐标转换,适配不同分辨率

核心知识点

  • 触摸事件:TOUCH_START / MOVE / END / CANCEL
  • 事件冒泡与捕获,swallowTouches 的作用
  • 屏幕坐标 → UI 坐标 → 世界坐标的转换
  • 手势识别:点击、长按、滑动方向、双指缩放
  • 多点触控:touch id 跟踪
  • 微信小游戏:触摸事件与鼠标事件的差异

练习 Demo:拖拽拼图 / 摇杆控制

  • 实现一个可拖拽的物体,跟随手指,松手有回弹
  • 实现一个虚拟摇杆:控制角色移动方向和速度
  • 实现双指缩放一个图片

验收标准

  • 拖拽跟手,无明显延迟,快速滑动不丢失 touch
  • 摇杆有死区和最大半径,输出方向向量正确
  • 在不同分辨率手机上,触摸坐标映射正确

第6周:事件总线与架构模式

学习目标

  • 建立模块间解耦通信,避免节点互相引用导致的强耦合
  • 掌握常用游戏架构模式在 Cocos 中的落地

核心知识点

  • EventTarget 全局事件总线:on / off / emit / targetOff
  • 事件监听的生命周期:onLoad 监听,onDestroy 必须移除
  • 命令模式 / 消息队列:UI 事件 → 命令 → 逻辑处理
  • 单例管理器设计:GameManager / DataManager / AudioManager
  • MVC 思想:数据、视图、逻辑分离
  • 防止事件泄漏:组件销毁后仍收到事件导致报错

练习 Demo:全局事件通信

  • 实现一个 EventBus 单例
  • 战斗逻辑层发出 SCORE_CHANGE 事件,UI 层监听并刷新分数,两者不直接引用
  • 实现一个简单的命令队列:点击按钮 → 执行命令 → 广播结果

验收标准

  • 关闭任意界面后,该界面注册的事件全部移除(无残留监听)
  • 模块之间不出现 getComponent 跨层硬引用
  • 能解释为什么节点销毁后不移除监听会报错

第7周:音频系统与微信适配

学习目标

  • 掌握音频池与动态加载,解决微信小游戏音频常见问题
  • BGM 与音效分离管理

核心知识点

  • AudioSource 组件 vs audioManager 接口
  • 音效池:多个相同音效同时播放(如连续射击)
  • BGM 循环、淡入淡出、切换
  • 微信小游戏音频限制:必须由用户手势触发才能播放
  • 音频格式:mp3 vs wav 的包体与性能权衡
  • 静音开关与音量持久化

练习 Demo:音频管理器

  • 实现 AudioManager:playMusic / playSound / stopAll / setVolume
  • 音效对象池:同时播放多个射击音效不互相打断
  • 处理微信:首次点击屏幕后才解锁音频

验收标准

  • 连续快速点击按钮,音效叠加播放不卡顿
  • 切后台 BGM 暂停,回前台恢复
  • 重启游戏后音量设置保留

第8周:数据驱动与存档系统

学习目标

  • 用配置表驱动游戏数值,不硬编码
  • 掌握存档读写、版本迁移、数据保护

核心知识点

  • JSON 配置表加载:resources.load 加载 JsonAsset
  • 数据管理器:全局单例管理玩家数据
  • sys.localStorage 读写与序列化
  • 存档版本号:版本升级时旧存档迁移
  • 数据加密(可选):防止简单篡改
  • 脏数据标记:哪些数据变化了需要写盘(避免频繁 IO)

练习 Demo:玩家数据系统

  • 设计一个 PlayerData:金币、最高分、关卡进度、设置项
  • 实现 DataManager:load / save / reset / 自动存档
  • 做一个关卡配置表(JSON),从配置读取关卡参数,改配置不改代码

验收标准

  • 杀进程重进游戏,数据完整恢复
  • 修改配置表后,不改动脚本即可改变关卡数值
  • 存档损坏时有兜底(读默认值),不白屏

第9周:渲染性能优化

学习目标

  • 能定位并解决 DrawCall、过度绘制、纹理内存问题
  • 这是微信小游戏包体和帧率达标的关键

核心知识点

  • DrawCall 概念:为什么合图能减少 DrawCall
  • SpriteAtlas 图集规划:按界面/模块打包,避免全量图集
  • 动态合图 vs 静态图集的适用场景
  • 过度绘制:半透明遮罩叠加层数
  • 纹理格式:ETC2 / ASTC 压缩,包体与内存
  • 节点数量优化:不可见节点及时 active = false
  • 微信开发者工具:DrawCall、FPS、内存面板使用

练习 Demo:性能对比优化

  • 做一个有 50 个 Sprite 的界面,先散图后合图,对比 DrawCall
  • 找出一个过度绘制严重的界面(多层半透明),优化层数
  • 用 ASTC 压缩纹理,对比包体大小

验收标准

  • 能在开发者工具中读出当前场景 DrawCall 数
  • 同屏 100 个同图集 Sprite,DrawCall 保持低位
  • 主包纹理内存控制在微信限制内(通常 ≤ 150MB 运行内存)

第10周:物理系统进阶(2D)

学习目标

  • 掌握 2D 物理的碰撞过滤、传感器、物理调试
  • 理解物理与游戏逻辑的解耦

核心知识点

  • RigidBody2D 三种类型:dynamic / static / kinematic
  • Collider2D:Box / Circle / Polygon,传感器(isSensor)
  • 碰撞回调:onBeginContact / onEndContact,碰撞对象信息
  • 碰撞分组与掩码:group / category,避免不必要碰撞
  • 物理材质:摩擦、弹性
  • 物理调试绘制:PhysicsSystem2D.instance.debugDrawFlags
  • 物理更新时机:fixedUpdate vs update

练习 Demo:碰撞检测游戏

  • 实现一个角色(kinematic)与多个障碍物(static)碰撞
  • 用传感器触发"进入区域"事件,而非物理阻挡
  • 配置碰撞分组:玩家只与敌人碰撞,不与背景碰撞

验收标准

  • 碰撞回调能正确拿到双方节点并执行逻辑
  • 传感器触发不产生物理反弹
  • 开启调试绘制,碰撞体形状与视觉一致

第11周:网络层与实时通信

学习目标

  • 封装统一请求层,处理超时、重试、错误码
  • 了解 WebSocket 长连接基础

核心知识点

  • fetch / wx.request 的 Promise 封装
  • 请求拦截:统一加 token、loading、错误提示
  • 错误码处理:登录失效、网络断开、超时
  • WebSocket:连接、重连、心跳、消息分发
  • 协议选择:JSON vs protobuf(小游戏包体敏感)
  • 微信小游戏:必须 https / wss,域名白名单配置

练习 Demo:网络管理器

  • 封装 HttpManager:get / post,统一错误处理
  • 实现一个简单的 WebSocket 连接:断线自动重连 + 心跳
  • 请求一个远程配置接口,失败时有重试

验收标准

  • 断网时请求有友好提示,不卡死
  • WebSocket 断开后能自动重连
  • 所有请求走统一入口,不散落 wx.request

第12周:微信小游戏专项与发布

学习目标

  • 完成从开发到上传的全流程,处理平台特有限制
  • 掌握分包、广告、性能监控

核心知识点

  • 构建发布:微信小游戏构建选项、资源服务器配置
  • 主包 4M 限制:分包策略、远程资源
  • 微信登录:wx.login → code → 后端换 session
  • 激励视频广告:加载、播放、关闭回调、失败兜底
  • 插屏广告、Banner 广告
  • 开放数据域:好友排行榜(独立子域)
  • 性能监控:wx.getPerformance、错误上报
  • 审核注意事项:隐私协议、类目、内容合规

练习 Demo:可上传的完整小游戏

  • 选一个前面做过的 demo,配置分包:主包只放启动和首页,其余进分包
  • 接入一个激励视频广告:看广告复活 / 得奖励
  • 配置微信登录,获取 openid(可模拟)
  • 上传体验版,在真机测试

验收标准

  • 主包体积 ≤ 4M,分包按需加载
  • 广告加载失败时有兜底逻辑(不卡死游戏)
  • 真机测试:启动时间、帧率、内存无明显异常
  • 能走通上传 → 提审流程(不要求真过审)

配套交付物清单(每周产出)

周次产出物
1ResManager 封装 + 内存对比截图
2通用对象池 + 帧率对比
3UIManager + 可复用列表组件
4动效编排脚本 + 状态切换
5拖拽/摇杆组件
6EventBus + 模块解耦 demo
7AudioManager
8DataManager + 配置表
9优化前后 DrawCall 对比
10物理碰撞 demo
11HttpManager + WebSocket 封装
12可上传的微信小游戏工程

学习建议

  1. 每周一个可运行 demo,不要只看文档;每周末用微信开发者工具真机自测一次。
  2. 第 1、3、9、12 周是微信小游戏上线的硬骨头,建议多花时间。
  3. 遇到问题优先查 Cocos 官方 API 文档和微信小游戏官方文档,第三方博客版本可能滞后。

=AVERAGEIF(C:C,"餐后",D:D)

作用

单条件求平均值:在C列中,找到单元格内容等于餐后的行,对应D列的数值,计算这些数字的算术平均值。

语法

AVERAGEIF(条件区域, 条件, 求平均区域)
  • C:C:条件区域,用来判断的列(这里是C整列)
  • "餐后":判断条件,单元格文字必须完全匹配“餐后”才参与计算,文本条件要加英文双引号
  • D:D:要计算平均值的数据列

示例

C列是类型(餐前/餐后),D列是血糖数值

CD
餐后12.3
餐前8.1
餐后14.2

公式会筛选两行餐后,计算 (12.3+14.2)/2 = 13.25

注意点

  1. 条件文字区分空格,"餐后 "(后面带空格)和"餐后"判定为不相同
  2. D列中非数字内容会自动忽略,不参与平均
  3. 找不到满足条件的单元格,公式返回#DIV/0!除零错误