<?xml version="1.0" encoding="UTF-8"?>
<rdf:RDF
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns="http://purl.org/rss/1.0/"
xmlns:dc="http://purl.org/dc/elements/1.1/">
<channel rdf:about="https://blog.anlucky.cn/index.php/feed/rss/programming">
<title>LuckyDu - 编程技术</title>
<link>https://blog.anlucky.cn/index.php/programming</link>
<description>Java技术，后端技术，Spring全家桶,Vue,Mysql,SQLServer,Python,HTML,CSS,JS,JavaScript,TS,TypeScript</description>
<items>
<rdf:Seq>
<rdf:li resource="https://blog.anlucky.cn/index.php/programming/239"/>
<rdf:li resource="https://blog.anlucky.cn/index.php/programming/web/236"/>
<rdf:li resource="https://blog.anlucky.cn/index.php/programming/tools/223"/>
<rdf:li resource="https://blog.anlucky.cn/index.php/programming/java/215"/>
<rdf:li resource="https://blog.anlucky.cn/index.php/programming/java/213"/>
<rdf:li resource="https://blog.anlucky.cn/index.php/programming/database/168"/>
<rdf:li resource="https://blog.anlucky.cn/index.php/programming/java/167"/>
<rdf:li resource="https://blog.anlucky.cn/index.php/programming/database/166"/>
<rdf:li resource="https://blog.anlucky.cn/index.php/programming/java/165"/>
<rdf:li resource="https://blog.anlucky.cn/index.php/programming/tools/164"/>
</rdf:Seq>
</items>
</channel>
<item rdf:about="https://blog.anlucky.cn/index.php/programming/239">
<title>微信小程序检查更新</title>
<link>https://blog.anlucky.cn/index.php/programming/239</link>
<dc:date>2025-04-29T11:07:44+08:00</dc:date>
<description>官方文档：小程序运行时 / 更新机制小程序运行时 /更新机制小程序更新机制开发者在管理后台发布新版本的小程序之后，微信客户端会有若干个时机去检查本地缓存的小程序有没有新版本，并进行小程序的代码包更新。但如果用户本地有小程序的历史版本，此时打开的可能还是旧版本。1. 启动时同步更新在以下情况下，小程序启动时会同步更新代码包。同步更新会阻塞小程序的启动流程，影响小程序的启动耗时。如果更新失败或超时，为了保障小程序的可用性，还是会使用本地版本打开。定期检查发现版本更新微信运行时，会定期检查最近使用的小程序是否有更新。如果有更新，下次小程序启动时会同步进行更新，更新到最新版本后再打开小程序，尽可能保证用户能够尽快使用小程序的最新版本。用户长时间未使用小程序用户长时间未使用小程序时，为保障小程序版本的实时性，会强制同步检查版本更新，更新到最新版本后再打开小程序。若用户处于弱网环境、下载最新版本失败等情况下，仍会启动本地的较低版本。2. 启动时异步更新即使启动前未发现更新，小程序每次冷启动时，都会异步检查是否有更新版本。如果发现有新版本，将会异步下载新版本的代码包。但当次启动仍会使用客户端本地的旧版本代码，即新版本的小程序需要等下一次冷启动才会使用。开发者手动触发更新在启动时异步更新的情况下，如果开发者希望立刻进行版本更新，可以使用 wx.getUpdateManager API 进行处理。在有新版本时提示用户重启小程序更新新版本。const updateManager = wx.getUpdateManager()

updateManager.onCheckForUpdate(function (res) {
  // 请求完新版本信息的回调
  console.log(res.hasUpdate)
})

updateManager.onUpdateReady(function () {
  wx.showModal({
    title: &#039;更新提示&#039;,
    content: &#039;新版本已经准备好，是否重启应用？&#039;,
    success(res) {
      if (res.confirm) {
        // 新的版本已经下载好，调用 applyUpdate 应用新版本并重启
        updateManager.applyUpdate()
      }
    }
  })
})

updateManager.onUpdateFailed(function () {
  // 新版本下载失败
})3. 小程序管理后台的相关设置小程序开发者可以通过在小程序管理后台进行设置，影响更新逻辑。优先使用本地版本设置若开发者判断某些较新的小程序版本无需强制用户同步更新到最新版本，可以在小程序管理后台「设置」-「版本设置」-「优先使用本地版本设置」中进行设置，设置后若同步更新时检查本地版本不低于该版本，则优先使用本地版本，同时将会异步下载最新版本的代码包。小程序最低可用版本设置若开发者判断某些较旧的小程序版本服务不再可用，可以在小程序管理后台「设置」-「版本设置」-「小程序最低可用版本设置」中进行设置。设置后若同步更新时检查本地版本低于该版本，则无法打开，并继续尝试下载最新版本、若异步更新，则会在检查到更新后提示用户重启小程序更新新版本。注意开发者在后台发布新版本之后，无法立刻影响到所有现网用户，正常情况下，在全量发布 24 小时之后，新版本可以覆盖 99% 以上的用户。小程序管理后台的「优先使用本地版本设置」和「小程序最低可用版本设置」不会影响同步更新与异步更新的选择。</description>
</item>
<item rdf:about="https://blog.anlucky.cn/index.php/programming/web/236">
<title>Uni-APP 组件-抽奖轮盘</title>
<link>https://blog.anlucky.cn/index.php/programming/web/236</link>
<dc:date>2025-04-15T17:57:59+08:00</dc:date>
<description>组件预览创建commponents/lottery/index.vue 文件输入如下代码轮盘抽检组件&lt;template&gt;
    &lt;view class=&quot;main&quot;&gt;
        &lt;view v-if=&quot;options.length &gt; 0&quot; class=&quot;canvas-container&quot;&gt;
            &lt;canvas canvas-id=&quot;canvas&quot; id=&quot;canvas&quot; :style=&quot;canvasStyle&quot; /&gt;

            &lt;canvas canvas-id=&quot;canvasBtn&quot; id=&quot;canvasBtn&quot; class=&quot;canvasBtn&quot; :style=&quot;canvasStyle&quot; /&gt;

            &lt;cover-view @tap=&quot;playReward&quot; :class=&quot;[&#039;canvas-btn&#039;, { &#039;isLottery&#039;: isLottery }]&quot;
                :style=&quot;{ backgroundColor: centerBtnColor }&quot;&gt;
                {{ centerBtnText }}
            &lt;/cover-view&gt;
        &lt;/view&gt;
        &lt;view v-else class=&quot;empty-tip&quot;&gt;
            &lt;text&gt;暂无抽奖选项&lt;/text&gt;
        &lt;/view&gt;
        &lt;view v-if=&quot;showBtn&quot; class=&quot;btn-container&quot;&gt;
            &lt;button type=&quot;primary&quot; :disabled=&quot;isLottery&quot; @click=&quot;playReward&quot;&gt;{{ btnTitle }}&lt;/button&gt;
        &lt;/view&gt;
    &lt;/view&gt;
&lt;/template&gt;
&lt;script&gt;
var ctx = null;
export default {

    props: {
        // 弹窗内容 ，当 turnModalContent=【】时，默认弹窗内容为抽奖结果，并且不展示标题
        turnModalContent: {
            type: Array,
            default: () =&gt; []
        },
        // 减速，值越小，减速效果越明显 turnReduceSpeed
        turnReduceSpeed: {
            type: Number,
            default: 50
        },
        // 表示转速：表示再转多少圈再进行抽奖
        turnCircle: {
            type: Number,
            default: 0
        },
        // 画布宽度
        width: {
            type: Number,
            default: 100,
        },
        // 画布高度
        height: {
            type: Number,
            default: 100
        },
        // 画布内字体大小
        fontSize: {
            type: Number,
            default: 18
        },
        // 钩子函数，抽奖开始前执行，返回值为选项列表下标，若返回值为-1，则进行随机抽奖
        setWinnerFn: {
            type: Function,
            default: null,
        },
        // // 钩子函数，抽奖开始前执行
        beforePlay: {
            type: Function,
            default: null,
        },
        // // 钩子函数，抽奖结束后执行
        afterPlay: {
            type: Function,
            default: null,
        },
        // 如果 true，则 使用组件默认的选项数据
        isUseDefaultOptions: {
            type: Boolean,
            default: false
        },
        // 选项列表
        data: {
            type: Array,
            default: () =&gt; []
        },
        // 是否要展示开始按钮
        showBtn: {
            type: Boolean,
            default: false,
        },
        // 按钮文本
        btnTitle: {
            type: String,
            default: &quot;开始&quot;
        },
        // 是否显示结果弹窗
        showResultModal: {
            type: Boolean,
            default: true
        },
        // 中心按钮文本
        centerBtnText: {
            type: String,
            default: &quot;GO&quot;
        },
        // 中心按钮颜色
        centerBtnColor: {
            type: String,
            default: &quot;#ffffff&quot;
        }
    },
    data() {
        return {
            options: [{
                id: 1, // 唯一id
                name: &#039;水饺&#039;, // 名称
                // &quot;weight&quot;: 50, //  中奖权重，0-100
                img: &#039;&#039;, // 展示图片
                color: &quot;#f6e174&quot; // 轮盘区域底色
            },
            {
                id: 2,
                name: &#039;火锅&#039;,
                img: &#039;&#039;,
                color: &quot;#94494d&quot;
            },
            {
                id: 3,
                name: &#039;川菜&#039;,
                img: &#039;&#039;,
                color: &quot;#ffaa7f&quot;
            },
            {
                id: 4,
                name: &#039;麻辣烫&#039;,
                img: &#039;&#039;,
                color: &quot;#a48342&quot;
            },
            {
                id: 5,
                name: &#039;炸鸡汉堡&#039;,
                img: &#039;&#039;,
                color: &quot;#a25f81&quot;
            },
            {
                id: 6,
                name: &#039;重庆小面&#039;,
                img: &#039;&#039;,
                color: &quot;#98e2e2&quot;
            },
            {
                id: 7,
                name: &#039;酸辣粉&#039;,
                img: &#039;&#039;,
                color: &quot;#aaaa00&quot;
            },
            {
                id: 8,
                name: &#039;面条&#039;,
                img: &#039;&#039;,
                color: &quot;#648f76&quot;
            },
            {
                id: 9,
                name: &#039;汤河粉&#039;,
                img: &#039;&#039;,
                color: &quot;#40b3ff&quot;
            },
            {
                id: 10,
                name: &#039;猪脚饭&#039;,
                img: &#039;&#039;,
                color: &quot;#ffaaff&quot;
            },
            {
                id: 11,
                name: &#039;烧鸭饭&#039;,
                img: &#039;&#039;,
                color: &quot;#5555ff&quot;
            },
            {
                id: 12,
                name: &#039;白切鸡&#039;,
                img: &#039;&#039;,
                color: &quot;#ff90cf&quot;
            },
            {
                id: 13,
                name: &#039;烧鸡饭&#039;,
                img: &#039;&#039;,
                color: &quot;#55aa7f&quot;
            },
            {
                id: 14,
                name: &#039;肥肠面&#039;,
                img: &#039;&#039;,
                color: &quot;#4f95ff&quot;
            },
            {
                id: 15,
                name: &#039;炒饭&#039;,
                img: &#039;&#039;,
                color: &quot;#a509aa&quot;
            },
            {
                id: 16,
                name: &#039;黄焖鸡米饭&#039;,
                img: &#039;&#039;,
                color: &quot;#aaaa00&quot;
            },
            {
                id: 17,
                name: &#039;皮蛋瘦肉粥&#039;,
                img: &#039;&#039;,
                color: &quot;#ff52d1&quot;
            },
            {
                id: 18,
                name: &#039;沙县小吃&#039;,
                img: &#039;&#039;,
                color: &quot;#944ade&quot;
            },
            {
                id: 19,
                name: &#039;焖锅&#039;,
                img: &#039;&#039;,
                color: &quot;#43c900&quot;
            },
            {
                id: 20,
                name: &#039;披萨&#039;,
                img: &#039;&#039;,
                color: &quot;#7cbaba&quot;
            }
            ],
            isLottery: false, // 是否正在抽奖
            winnerIndex: -1, // 中奖索引
            isInitialized: false // 是否已初始化
        };
    },
    computed: {
        canvasStyle() {
            return &quot;width:&quot; + this.width + &quot;px;; height:&quot; + this.height + &quot;px;&quot;;
        },
    },
    watch: {
        // 监听数据变化，重新初始化
        data: {
            handler(newVal) {
                if (newVal &amp;&amp; newVal.length &gt; 0 &amp;&amp; !this.isUseDefaultOptions) {
                    this.init();
                }
            },
            deep: true
        }
    },
    methods: {
        // 处理文本，根据长度进行截断或调整
        processText(text, maxLength) {
            if (!text) return &#039;&#039;;

            // 如果文本长度超过最大长度，截断并添加省略号
            if (text.length &gt; maxLength) {
                return text.substring(0, maxLength - 2) + &#039;..&#039;;
            }
            return text;
        },
        // 获取文本显示的字体大小
        getTextFontSize(text, baseSize) {
            // 根据文本长度动态调整字体大小
            if (!text) return baseSize;

            if (text.length &gt; 6) {
                // 文本较长时，逐步减小字体
                const reduction = Math.min(Math.floor(text.length / 2), 8);
                return Math.max(baseSize - reduction, 10); // 最小字体为10
            }
            return baseSize;
        },


        async playReward() {
            if (this.isLottery) {
                return
            }
            let len = this.options.length
            if (len == 0) {
                console.error(&quot;抽奖选项为空，无法开始抽奖&quot;);
                return;
            }
            this.isLottery = true
            console.log(&quot;开始抽奖&quot;)
            // 触发beforePlay事件
            if (this.beforePlay) {
                this.beforePlay();
            }
            this.$emit(&quot;beforePlay&quot;);
            // 获取中奖索引
            let num = -1;
            // 使用setWinnerFn获取中奖索引
            if (this.setWinnerFn &amp;&amp; typeof this.setWinnerFn === &#039;function&#039;) {
                try {
                    // 修复调用方式
                    num = this.setWinnerFn(this.options);
                    if (num === undefined || num &lt; 0 || num &gt;= len) {
                        console.warn(&quot;setWinnerFn返回的索引无效，将使用随机抽奖&quot;);
                        num = -1;
                    }
                } catch (error) {
                    console.error(&quot;调用setWinnerFn出错:&quot;, error);
                    num = -1;
                }
            }
            if (num &lt; 0) {
                // 进行权重抽奖
                let optionIndex = this.lotteryWeight(this.options)
                if (optionIndex !== undefined &amp;&amp; optionIndex &gt;= 0) {
                    num = optionIndex
                } else {
                    // 没有自动抽奖结果 &amp;&amp; 没有定义选项权重，则进行随机数抽奖
                    num = Math.floor(Math.random() * len)
                }
            }
            this.winnerIndex = num;
            try {
                const result = await this.roateCanvas(num)
                console.log(&quot;抽奖结果：&quot;, result.name)
                this.isLottery = false
                // 是否显示结果弹窗
                if (this.showResultModal) {
                    let title = &quot;&quot;
                    let content = result.name
                    if (this.turnModalContent.length &gt; 0) {
                        // 若设置了 content ，则随机取一个 content 内容，并且 title 将展示为抽奖结果 
                        title = result.name
                        content = this.turnModalContent[Math.floor(Math.random() * this.turnModalContent.length)]
                    }
                    uni.showModal({
                        confirmText: &quot;确定&quot;,
                        showCancel: false,
                        title: title,
                        content: content,
                        success: (res) =&gt; {
                            if (res.confirm) {
                                console.log(&#039;用户点击确定&#039;);
                                // 调用钩子函数
                                if (this.afterPlay) {
                                    this.afterPlay(result);
                                }
                                this.$emit(&quot;afterPlay&quot;, result)
                            }
                        }
                    })
                } else {
                    // 直接触发afterPlay事件
                    if (this.afterPlay) {
                        this.afterPlay(result);
                    }
                    this.$emit(&quot;afterPlay&quot;, result)
                }
            } catch (error) {
                console.error(&quot;抽奖过程出错:&quot;, error);
                this.isLottery = false;
                uni.showToast({
                    title: &#039;抽奖失败，请重试&#039;,
                    icon: &#039;none&#039;
                });
            }

        },

        initBtnCanvas() {
            try {
                let angleTo = 0
                let ctx = uni.createCanvasContext(&quot;canvasBtn&quot;, this);
                // 圆中心点的坐标 x： 宽度的一半
                let center_x = this.width / 2;
                // 圆中心点的坐标 y： 高度的一半
                let center_y = this.height / 2;

                // 先清除画布上在该矩形区域内的内容
                ctx.clearRect(0, 0, this.width, this.height);

                ctx.translate(center_x, center_y);

                // 画中心点圆
                ctx.beginPath();
                ctx.moveTo(0, -50); // 三角形顶点坐标
                ctx.lineTo(-20, 10); // 左下角坐标
                ctx.lineTo(20, 10); // 右下角坐标
                ctx.setFillStyle(&quot;#ff0000&quot;);
                ctx.fill();
                ctx.draw();
            } catch (error) {
                console.error(&quot;初始化按钮画布失败:&quot;, error);
            }
        },
        initCanvas: function (ctx, angleTo) {
            try {
                const len = this.options.length; //数组长度
                if (len == 0) {
                    console.warn(&quot;options len == 0&quot;);
                    return;
                }

                if (!angleTo) {
                    angleTo = 0
                }

                // 圆中心点的坐标 x： 宽度的一半
                let center_x = this.width / 2;
                // 圆中心点的坐标 y： 高度的一半
                let center_y = this.height / 2;
                // 圆的弧度的总度数，2π表示画圆
                let totalAngle = 2 * Math.PI;
                // 平均一个选项占用的孤度数
                let avgAngle = totalAngle / len;

                let radius = center_x - 14;
                let baseFontSize = this.getFontSize();

                // 先清除画布上在该矩形区域内的内容
                ctx.clearRect(0, 0, this.width, this.height);

                ctx.translate(center_x, center_y);
                ctx.setLineWidth(14);
                ctx.save();

                // 画外圆
                ctx.rotate(angleTo * Math.PI / 180);
                var beginAngle = 2 * Math.PI / 360 * (-90);
                ctx.setStrokeStyle(&quot;#ffffff&quot;);
                ctx.arc(0, 0, radius - 3, 0, Math.PI * 2);
                ctx.stroke();

                // 划分区域，并且填充颜色
                ctx.setLineWidth(0.1);
                beginAngle = 2 * Math.PI / 360 * (-90);
                //绘制填充形状
                for (var i = 0; i &lt; len; i++) {
                    ctx.save();
                    ctx.beginPath();
                    ctx.moveTo(0, 0);
                    ctx.setStrokeStyle(this.options[i].color);
                    ctx.setFillStyle(this.options[i].color);

                    ctx.arc(0, 0, radius, beginAngle, beginAngle + avgAngle, false);
                    ctx.fill();
                    ctx.save();
                    beginAngle = beginAngle + avgAngle;
                }

                // 绘制选项文字
                beginAngle = 0;
                for (var i = 0; i &lt; len; i++) {
                    // 根据奖项数量和文本长度调整文本位置和大小
                    let textDistance = -(center_x / 2) - 25;

                    // 奖项过多时，文本更靠近中心
                    if (len &gt; 10) {
                        textDistance = -(center_x / 2) - 15;
                    }
                    if (len &gt; 15) {
                        textDistance = -(center_x / 2) - 5;
                    }

                    // 处理文本，根据奖项数量决定最大长度
                    let maxTextLength = len &gt; 12 ? 4 : (len &gt; 8 ? 6 : 8);
                    let processedText = this.processText(this.options[i].name, maxTextLength);

                    // 根据文本长度调整字体大小
                    let fontSize = this.getTextFontSize(processedText, baseFontSize);

                    //绘制旋转文字
                    ctx.rotate((beginAngle + (avgAngle * 0.5))); //顺时针旋转
                    ctx.setTextAlign(&quot;center&quot;);
                    ctx.setFillStyle(&quot;#FFFFFF&quot;);
                    ctx.setFontSize(fontSize);
                    ctx.fillText(processedText, 0, textDistance);

                    ctx.restore();
                    beginAngle = beginAngle + avgAngle;
                }
                ctx.save();
                // 画中心点圆
                ctx.beginPath();
                ctx.arc(0, 0, 15, 0, Math.PI * 2); // 15 为中心点圆的半径
                ctx.setFillStyle(&quot;#FFFFFF&quot;);
                ctx.fill();
                ctx.draw();

                this.isInitialized = true;
            } catch (error) {
                console.error(&quot;初始化画布失败:&quot;, error);
                this.isInitialized = false;
            }
        },
        // 根据设置的权重进行抽奖
        lotteryWeight(prizes) {
            console.log(&quot;进行权重抽奖&quot;)
            if (!prizes || prizes.length === 0) {
                console.log(&quot;奖品列表为空&quot;)
                return -1
            }

            // 计算总权重和有效奖品
            let totalWeight = 0
            const validPrizes = []

            // 筛选有效奖品并计算总权重
            for (let index = 0; index &lt; prizes.length; index++) {
                const prize = prizes[index]
                const weight = Number(prize.weight)

                // 只有权重大于0的奖品才参与抽奖
                if (weight &amp;&amp; weight &gt; 0) {
                    totalWeight += weight
                    validPrizes.push({
                        index,
                        weight,
                        name: prize.name
                    })
                }
            }

            // 如果没有有效奖品，返回-1表示随机抽奖
            if (validPrizes.length === 0 || totalWeight === 0) {
                console.log(&quot;没有设置有效权重的奖品，将进行随机抽奖&quot;)
                return -1
            }

            // 生成0到总权重之间的随机数
            const random = Math.random() * totalWeight
            console.log(&quot;权重抽奖随机数:&quot;, random, &quot;总权重:&quot;, totalWeight)

            // 根据权重区间确定中奖奖品
            let accumulatedWeight = 0
            for (const prize of validPrizes) {
                accumulatedWeight += prize.weight
                if (random &lt; accumulatedWeight) {
                    console.log(&quot;权重抽奖奖品 &quot;, prizes[prize.index].name, &quot; 中奖，权重:&quot;, prize.weight)
                    return prize.index
                }
            }
            // 保底返回最后一个有效奖品
            console.log(&quot;未找到匹配奖品，进行随机抽奖&quot;)
            return -1
        },
        // 旋转画布，num 表示选项 options 的下标
        roateCanvas(num) {
            let len = this.options.length
            let angle = 360 / len;
            angle = num * angle + angle / 2;
            angle = angle || 0;
            angle = 360 - angle;
            angle += 360 * 5;
            if (this.turnCircle &gt; 0) {
                let turnCircle = this.turnCircle
                // 最多只能转 10 圈
                if (turnCircle &gt; 10) {
                    turnCircle = 10
                }
                angle += 360 * turnCircle;
            }
            let that = this;
            let count = 1;
            // 减速，值越小，减速效果越明显 
            let turnReduceSpeed = this.turnReduceSpeed
            if (turnReduceSpeed == 0) {
                turnReduceSpeed = 1
            }
            let baseStep = turnReduceSpeed;
            // 起始滚动速度
            let baseSpeed = 1;
            let result = {}
            return new Promise((resolve, reject) =&gt; {
                let timer = setInterval(function () {
                    // console.log(&quot;count = &quot;， count);
                    that.initCanvas(that.ctx, count);
                    if (count == angle) {
                        clearInterval(timer);
                        result = that.options[num];
                        resolve(result)
                    }
                    count = count + baseStep * (((angle - count) / angle) &gt; baseSpeed ? baseSpeed :
                        ((angle -
                            count) / angle)) + 0.1;

                    if (angle - count &lt; 0.5) {
                        count = angle;
                    }
                }, 25);
            });
        },
        getFontSize() {
            let fontSize = this.fontSize
            if (this.options.length &gt; 10) {
                if (this.fontSize &gt;= 18) {
                    fontSize = this.fontSize - (this.options.length - 10)
                }
            }

            return fontSize
        },
        // 生成随机颜色
        genRandColor() {
            // 生成随机的 RGB 值
            var r = Math.floor(Math.random() * 256); // 0 到 255 之间的随机数
            var g = Math.floor(Math.random() * 256);
            var b = Math.floor(Math.random() * 256);

            // 将 RGB 值转换为 Hex 颜色表示
            var hexColor = &quot;#&quot; + this.componentToHex(r) + this.componentToHex(g) + this.componentToHex(b);

            return hexColor;
        },
        componentToHex(c) {
            var hex = c.toString(16);
            return hex.length === 1 ? &quot;0&quot; + hex : hex;
        },
        initOptions: function () {
            try {
                let defaultOptions = JSON.parse(JSON.stringify(this.options)); // 深拷贝默认选项

                if (this.isUseDefaultOptions) {
                    // 使用默认数据
                    console.log(&quot;使用默认选项数据&quot;);
                } else if (this.data &amp;&amp; this.data.length &gt; 0) {
                    console.log(&quot;使用传入的选项数据, 长度:&quot;, this.data.length);
                    this.options = JSON.parse(JSON.stringify(this.data)); // 深拷贝传入数据
                } else {
                    console.warn(&quot;未提供有效的选项数据，将使用默认选项&quot;);
                }

                // 所有默认的颜色
                let allDefColorArr = defaultOptions.map(item =&gt; item.color);

                // 找到最大的 id
                let maxId = 0;
                this.options.forEach(item =&gt; {
                    if (item.id &amp;&amp; item.id &gt; maxId) {
                        maxId = item.id;
                    }
                });

                // 填充 id，确保 id 唯一
                var existIds = []
                for (var i = 0; i &lt; this.options.length; i++) {
                    let item = this.options[i]
                    if (item[&#039;id&#039;] == undefined || item.id == 0 || existIds.includes(item.id)) {
                        // id 不存在，或者 id 重复了
                        maxId++;
                        this.options[i][&#039;id&#039;] = maxId;
                    }
                    existIds.push(this.options[i].id)
                }

                // 填充颜色，确保颜色唯一
                let availableColor = JSON.parse(JSON.stringify(allDefColorArr)) // 可用颜色
                let existColor = [] // 存在颜色

                // 先收集已有颜色
                for (var i = 0; i &lt; this.options.length; i++) {
                    let item = this.options[i]
                    if (item[&#039;color&#039;] &amp;&amp; item.color !== &quot;&quot;) {
                        let color = item.color
                        existColor.push(color)
                        // 过滤掉已经用了的 color
                        availableColor = availableColor.filter(c =&gt; c !== color);
                    }
                }

                // 为没有颜色的选项分配颜色
                for (var i = 0; i &lt; this.options.length; i++) {
                    let item = this.options[i]
                    if (!item[&#039;color&#039;] || item.color === &quot;&quot;) {
                        if (availableColor.length == 0) {
                            // 没有可用颜色了，则随机生成一个
                            let color = &#039;&#039;;
                            for (var j = 0; j &lt; 100; j++) {
                                if (color !== &#039;&#039;) break;
                                let genColor = this.genRandColor()
                                if (!existColor.includes(genColor)) {
                                    existColor.push(genColor)
                                    color = genColor
                                }
                            }
                            this.options[i][&#039;color&#039;] = color
                        } else {
                            const color = availableColor.shift();
                            existColor.push(color)
                            this.options[i].color = color
                        }
                    }
                }

                console.log(&quot;选项初始化完成，数量:&quot;, this.options.length);
            } catch (error) {
                console.error(&quot;初始化选项失败:&quot;, error);
                // 确保至少有默认选项
                if (!this.options || this.options.length === 0) {
                    this.options = [{
                        id: 1,
                        name: &#039;默认选项&#039;,
                        color: &quot;#f6e174&quot;
                    }];
                }
            }
        },
        init() {
            try {
                this.initOptions();
                this.ctx = uni.createCanvasContext(&quot;canvas&quot;, this);
                this.initCanvas(this.ctx, 0);
                this.initBtnCanvas();
            } catch (error) {
                console.error(&quot;初始化组件失败:&quot;, error);
                uni.showToast({
                    title: &#039;初始化抽奖组件失败&#039;,
                    icon: &#039;none&#039;
                });
            }
        }
    },
    // 初始化画布
    mounted: function () {
        console.log(&quot;lottery mounted init......&quot;)
        this.init()
    }
}
&lt;/script&gt;
&lt;style scoped&gt;
.main {
    display: flex;
    flex-direction: column;
    justify-content: center;
    align-items: center;
}

.title-container {
    font-size: 25px;
    margin-top: 120rpx;
    margin-bottom: 40rpx;
    z-index: 100;
}

.canvas-container {
    position: relative;
    width: fit-content;
    height: fit-content;
}

.canvasBtn {
    position: absolute;
    top: 0%;
}

.canvas-btn {
    position: absolute;
    top: 50%;
    left: 50%;
    background-color: #ffffff;
    transform: translate(-50%, -50%);
    width: 110rpx;
    height: 110rpx;
    border-radius: 50%;
    line-height: 110rpx;
    text-align: center;
    font-size: 45rpx;
    text-shadow: 0 -1px 1px rgba(0, 0, 0, 0.6);
    box-shadow: 0 3px 5px rgba(0, 0, 0, 0.6);
    text-decoration: none;
}

.canvas-btn.isLottery {
    background-color: #CCCCCC;
    pointer-events: none;
}

.empty-tip {
    padding: 30rpx;
    text-align: center;
    color: #999;
    font-size: 28rpx;
}

.btn-container {
    margin-top: 30rpx;
}
&lt;/style&gt;测试Demo&lt;template&gt;
    &lt;view class=&quot;container&quot;&gt;
        &lt;view class=&quot;title&quot;&gt;转盘抽奖组件演示&lt;/view&gt;

        &lt;!-- 转盘抽奖组件 --&gt;
        &lt;lottery ref=&quot;lotteryRef&quot; :width=&quot;300&quot; :height=&quot;300&quot; :fontSize=&quot;16&quot; :data=&quot;lotteryOptions&quot;
            :isUseDefaultOptions=&quot;useDefaultOptions&quot; :turnReduceSpeed=&quot;reduceSpeed&quot; :turnCircle=&quot;circleCount&quot;
            :showBtn=&quot;true&quot; :btnTitle=&quot;&#039;开始抽奖&#039;&quot; :centerBtnText=&quot;&#039;GO&#039;&quot; :centerBtnColor=&quot;&#039;#ff5252&#039;&quot;
            :showResultModal=&quot;showModal&quot; :turnModalContent=&quot;modalContents&quot; :setWinnerFn=&quot;setWinner&quot;
            :beforePlay=&quot;onBeforePlay&quot; :afterPlay=&quot;onAfterPlay&quot; /&gt;

        &lt;!-- 配置面板 --&gt;
        &lt;view class=&quot;config-panel&quot;&gt;
            &lt;view class=&quot;panel-title&quot;&gt;配置选项&lt;/view&gt;

            &lt;view class=&quot;config-item&quot;&gt;
                &lt;text&gt;使用默认选项:&lt;/text&gt;
                &lt;switch :checked=&quot;useDefaultOptions&quot; @change=&quot;(e) =&gt; useDefaultOptions = e.detail.value&quot; /&gt;
            &lt;/view&gt;

            &lt;view class=&quot;config-item&quot;&gt;
                &lt;text&gt;减速效果({{ reduceSpeed }}):&lt;/text&gt;
                &lt;slider :value=&quot;reduceSpeed&quot; :min=&quot;1&quot; :max=&quot;100&quot; :step=&quot;1&quot; show-value
                    @change=&quot;(e) =&gt; reduceSpeed = e.detail.value&quot; /&gt;
            &lt;/view&gt;

            &lt;view class=&quot;config-item&quot;&gt;
                &lt;text&gt;额外旋转圈数({{ circleCount }}):&lt;/text&gt;
                &lt;slider :value=&quot;circleCount&quot; :min=&quot;0&quot; :max=&quot;10&quot; :step=&quot;1&quot; show-value
                    @change=&quot;(e) =&gt; circleCount = e.detail.value&quot; /&gt;
            &lt;/view&gt;

            &lt;view class=&quot;config-item&quot;&gt;
                &lt;text&gt;显示结果弹窗:&lt;/text&gt;
                &lt;switch :checked=&quot;showModal&quot; @change=&quot;(e) =&gt; showModal = e.detail.value&quot; /&gt;
            &lt;/view&gt;

            &lt;view class=&quot;config-item&quot;&gt;
                &lt;text&gt;指定中奖项:&lt;/text&gt;
                &lt;picker :value=&quot;winnerIndex&quot; :range=&quot;winnerOptions&quot; range-key=&quot;name&quot; @change=&quot;onWinnerChange&quot;&gt;
                    &lt;view class=&quot;picker-value&quot;&gt;{{ winnerOptions[winnerIndex].name || &#039;随机&#039; }}&lt;/view&gt;
                &lt;/picker&gt;
            &lt;/view&gt;

            &lt;button class=&quot;reset-btn&quot; type=&quot;default&quot; @click=&quot;resetLottery&quot;&gt;重置转盘&lt;/button&gt;
        &lt;/view&gt;

        &lt;!-- 事件日志 --&gt;
        &lt;view class=&quot;event-log&quot;&gt;
            &lt;view class=&quot;log-title&quot;&gt;事件日志&lt;/view&gt;
            &lt;scroll-view scroll-y class=&quot;log-content&quot;&gt;
                &lt;view v-for=&quot;(log, index) in eventLogs&quot; :key=&quot;index&quot; class=&quot;log-item&quot;&gt;
                    {{ log }}
                &lt;/view&gt;
            &lt;/scroll-view&gt;
            &lt;button size=&quot;mini&quot; type=&quot;default&quot; @click=&quot;clearLogs&quot;&gt;清空日志&lt;/button&gt;
        &lt;/view&gt;
    &lt;/view&gt;
&lt;/template&gt;

&lt;script&gt;
import Lottery from &#039;@/components/lottery/index.vue&#039;

export default {
    // 注册组件
    components: {
        Lottery
    },
    data() {
        return {
            // 转盘选项数据
            lotteryOptions: [
                { id: 1, name: &#039;一等奖&#039;, weight: 5, color: &#039;#ff4444&#039; },
                { id: 2, name: &#039;二等奖&#039;, weight: 10, color: &#039;#ff8800&#039; },
                { id: 3, name: &#039;三等奖&#039;, weight: 15, color: &#039;#ffcc00&#039; },
                { id: 4, name: &#039;四等奖&#039;, weight: 20, color: &#039;#33cc33&#039; },
                { id: 5, name: &#039;五等奖&#039;, weight: 25, color: &#039;#3399ff&#039; },
                { id: 6, name: &#039;谢谢参与&#039;, weight: 25, color: &#039;#9966ff&#039; }
            ],

            // 配置选项
            useDefaultOptions: false, // 是否使用组件默认选项
            reduceSpeed: 50, // 减速效果，值越小减速效果越明显
            circleCount: 3, // 额外旋转圈数
            showModal: true, // 是否显示结果弹窗

            // 弹窗内容配置
            modalContents: [
                &#039;恭喜您获得了奖品！&#039;,
                &#039;太棒了！您中奖了！&#039;,
                &#039;好运连连！&#039;
            ],

            // 指定中奖项配置
            forceWinner: false, // 是否强制指定中奖项
            winnerIndex: 0, // 指定的中奖项索引

            // 事件日志
            eventLogs: [],

            // 中奖选项列表（用于选择器）
            winnerOptions: [
                { index: -1, name: &#039;随机抽取&#039; }
            ]
        };
    },

    created() {
        // 初始化中奖选项列表
        this.initWinnerOptions();
    },

    methods: {
        /**
         * 初始化中奖选项列表
         */
        initWinnerOptions() {
            // 先添加随机选项
            this.winnerOptions = [{ index: -1, name: &#039;随机抽取&#039; }];

            // 添加所有奖品选项
            this.lotteryOptions.forEach((item, index) =&gt; {
                this.winnerOptions.push({
                    index: index,
                    name: item.name
                });
            });
        },

        /**
         * 设置中奖者的钩子函数
         * @param {Array} options - 当前抽奖选项列表
         * @returns {Number} - 返回中奖索引，-1表示随机抽取
         */
        setWinner(options) {
            // 记录日志
            this.addLog(&#039;调用 setWinnerFn 钩子函数&#039;);

            // 如果强制指定中奖项且索引有效，则返回指定索引
            if (this.forceWinner &amp;&amp; this.winnerIndex &gt; 0) {
                const selectedIndex = this.winnerOptions[this.winnerIndex].index;
                this.addLog(`指定中奖项: ${options[selectedIndex].name}`);
                return selectedIndex;
            }

            // 返回-1表示随机抽取
            this.addLog(&#039;未指定中奖项，将随机抽取&#039;);
            return -1;
        },

        /**
         * 抽奖开始前的钩子函数
         */
        onBeforePlay() {
            this.addLog(&#039;抽奖开始&#039;);
        },

        /**
         * 抽奖结束后的钩子函数
         * @param {Object} result - 抽奖结果
         */
        onAfterPlay(result) {
            this.addLog(`抽奖结束，结果: ${result.name}`);
        },

        /**
         * 重置转盘
         */
        resetLottery() {
            // 重新初始化转盘
            this.$refs.lotteryRef.init();
            this.addLog(&#039;转盘已重置&#039;);
        },

        /**
         * 添加日志
         * @param {String} message - 日志消息
         */
        addLog(message) {
            const now = new Date();
            const timeStr = `${now.getHours()}:${now.getMinutes()}:${now.getSeconds()}`;
            this.eventLogs.unshift(`[${timeStr}] ${message}`);
        },

        /**
         * 清空日志
         */
        clearLogs() {
            this.eventLogs = [];
        },

        /**
         * 中奖项选择变更
         */
        onWinnerChange(e) {
            this.winnerIndex = e.detail.value;
            this.forceWinner = this.winnerIndex &gt; 0;

            if (this.forceWinner) {
                const selectedIndex = this.winnerOptions[this.winnerIndex].index;
                this.addLog(`已设置指定中奖项: ${this.lotteryOptions[selectedIndex].name}`);
            } else {
                this.addLog(&#039;已设置为随机抽取&#039;);
            }
        }
    }
}
&lt;/script&gt;

&lt;style&gt;
.container {
    padding: 20rpx;
}

.title {
    font-size: 36rpx;
    font-weight: bold;
    text-align: center;
    margin-bottom: 30rpx;
}

.config-panel {
    margin-top: 40rpx;
    padding: 20rpx;
    border-radius: 10rpx;
    background-color: #f8f8f8;
}

.panel-title,
.log-title {
    font-size: 30rpx;
    font-weight: bold;
    margin-bottom: 20rpx;
    border-bottom: 1px solid #eee;
    padding-bottom: 10rpx;
}

.config-item {
    display: flex;
    justify-content: space-between;
    align-items: center;
    margin-bottom: 20rpx;
}

.picker-value {
    padding: 10rpx 20rpx;
    background-color: #fff;
    border-radius: 6rpx;
    min-width: 200rpx;
    text-align: center;
}

.reset-btn {
    margin: 20rpx 0;
}

.event-log {
    margin-top: 40rpx;
    padding: 20rpx;
    border-radius: 10rpx;
    background-color: #f8f8f8;
}

.log-content {
    height: 300rpx;
    background-color: #fff;
    padding: 10rpx;
    border-radius: 6rpx;
    margin-bottom: 20rpx;
}

.log-item {
    font-size: 24rpx;
    padding: 6rpx 0;
    border-bottom: 1px dashed #eee;
}
&lt;/style&gt;</description>
</item>
<item rdf:about="https://blog.anlucky.cn/index.php/programming/tools/223">
<title>Git如何更换远程仓库</title>
<link>https://blog.anlucky.cn/index.php/programming/tools/223</link>
<dc:date>2024-12-02T17:18:04+08:00</dc:date>
<description></description>
</item>
<item rdf:about="https://blog.anlucky.cn/index.php/programming/java/215">
<title>SpringBoot解决全局跨域问题</title>
<link>https://blog.anlucky.cn/index.php/programming/java/215</link>
<dc:date>2024-04-28T11:09:22+08:00</dc:date>
<description>SpringBoot解决全局跨域问题什么是跨域跨域：指的是浏览器不能执行其他网站的脚本。它是由浏览器的同源策略造成的，是浏览器对javascript施加的安全限制。例如：a页面想获取b页面资源，如果a、b页面的协议、域名、端口、子域名不同，所进行的访问行动都是跨域的，而浏览器为了安全问题一般都限制了跨域访问，也就是不允许跨域请求资源。注意：跨域限制访问，其实是浏览器的限制。理解这一点很重要！！！同源策略：是指协议，域名，端口都要相同，其中有一个不同都会产生跨域；同源策略同源，就是咱们域名、端口号、ip、采用的协议都相同，那么我们就是同源的反之就是不同源的！！！出于浏览器的同源策略限制。同源策略（Sameoriginpolicy）是一种约定，它是浏览器最核心也最基本的安全功能，如果缺少了同源策略，则浏览器的正常功能可能都会受到影响。可以说Web是构建在同源策略基础之上的，浏览器只是针对同源策略的一种实现。所以，用最简单的话来说，就是前端可以发请求给服务器，服务器也可以进行响应，只是因为浏览器会对请求头进行判断，所以要么前端设置请求头，要么后端设置请求头JAVA解决CORS跨域请求的方式返回新的CorsFilter重写 WebMvcConfigurer使用注解 @CrossOrigin手动设置响应头 (HttpServletResponse)自定web filter 实现跨域注意:CorFilter / WebMvConfigurer / @CrossOrigin 需要 SpringMVC 4.2以上版本才支持，对应springBoot 1.3版本以上上面前两种方式属于全局 CORS 配置，后两种属于局部 CORS配置。如果使用了局部跨域是会覆盖全局跨域的规则，所以可以通过 @CrossOrigin 注解来进行细粒度更高的跨域资源控制。其实无论哪种方案，最终目的都是修改响应头，向响应头中添加浏览器所要求的数据，进而实现跨域这里只介绍全局解决SpringBoot跨域问题，使用返回新的CorsFilter方式1. 编写CorsConfig.java 类/**
 * 解决全局跨域问题
 */
@Configuration
public class CorsConfig {
    @Bean
    public CorsFilter corsFilter() {

        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();

        // 配置跨域
        CorsConfiguration corsConfiguration = new CorsConfiguration();
        // 允许哪个请求头
        corsConfiguration.addAllowedHeader(&quot;*&quot;);
        // 允许哪个方法进行跨域
        corsConfiguration.addAllowedMethod(&quot;*&quot;);
        // 允许哪个请求来源进行跨域
        // corsConfiguration.addAllowedOrigin(&quot;*&quot;);
        corsConfiguration.addAllowedOriginPattern(&quot;*&quot;);
        // 是否允许携带cookie进行跨域
        corsConfiguration.setAllowCredentials(true);

        source.registerCorsConfiguration(&quot;/**&quot;,corsConfiguration);

        return new CorsFilter(source);
    }
}2. 全局拦截器配置/**
 * 拦截器
 */
@Configuration
public class WebConfigure implements WebMvcConfigurer {
    /**
     * 全局解决跨域问题
     * @param registry
     */
    @Override
    public void addCorsMappings(CorsRegistry registry) {
        registry.addMapping(&quot;/**&quot;)
                .allowedOriginPatterns(&quot;*&quot;)
                .allowedMethods(&quot;GET&quot;, &quot;HEAD&quot;, &quot;POST&quot;, &quot;PUT&quot;, &quot;DELETE&quot;, &quot;OPTIONS&quot;)
                .allowCredentials(true)
                .maxAge(3600)
                .allowedHeaders(&quot;*&quot;);
    }
}
如此，这样，即解决所有解决跨域问题，不外乎就是解决浏览器拦截问题，要么前端设置请求头，要么后端设置请求头，无论谁设置请求头，浏览器只要放行即可</description>
</item>
<item rdf:about="https://blog.anlucky.cn/index.php/programming/java/213">
<title>Java工具类-通用对象转换为Json字符</title>
<link>https://blog.anlucky.cn/index.php/programming/java/213</link>
<dc:date>2024-04-01T09:41:34+08:00</dc:date>
<description>Java工具类-通用对象转换为Json字符import com.alibaba.fastjson2.JSONObject;

import java.lang.reflect.Field;
import java.text.SimpleDateFormat;

public class JsonObjectUtlis {

    public static &lt;U&gt; JSONObject processEntityToJson(Class&lt;U&gt; clazz, U cla) {
        //将传过来的对象进行赋值处理，
        //此时u可用来代表传过来的对象（本示意中是Users）,
        //此时可以用u调用传过来对象的方法
        U u = clazz.cast(cla);
        //以下是验证此示意中实体类可被操作了
        //getDeclaredFields()：获得某个类的所有声明的字段，即包括public、private和proteced，但是不包括父类的申明字段。
        //.getClass()是一个对象实例的方法，只有对象实例才有这个方法，具体的类是没有的
        JSONObject jsonObject = new JSONObject();
        for (Field field : u.getClass().getDeclaredFields()) {
            //允许获取实体类private的参数信息 field.setAccessible(true);
            field.setAccessible(true);
            try {
                String name = field.getType().getName();
                if (&quot;java.util.Date&quot;.equals(name) &amp;&amp; field.get(u) != null) {
                    String dateStr = new SimpleDateFormat(&quot;yyyy-MM-dd HH:mm:ss&quot;).format(field.get(u));
                    if (dateStr.indexOf(&quot;00:00:00&quot;) &gt; 0){
                        dateStr = dateStr.substring(0,10) ;
                    }
                    jsonObject.put(field.getName(), dateStr);
                }else {
                    jsonObject.put(field.getName(), field.get(u));
                }
            } catch (IllegalAccessException e) {
                e.printStackTrace();
                jsonObject.put(&quot;convertJsonStatus&quot;,500);
                return jsonObject;
            }
        }
        return jsonObject;
    }
}使用例子        JSONObject jsonObject = JsonObjectUtlis.processEntityToJson(JudgeResultSynchronizationMatVO.class, synchronizationVO);
        // 第一个参数为对象的Clss类    第二个入参为实际对象Json判断判断当前字符串是不是Json字符串，是JSON返回True 否则返回falsepublic static boolean isjson(String str) {
        try {
            JSONObject jsonStr = JSONObject.parseObject(str);
            return  true;
        } catch (Exception e) {
            return false;
        }
}</description>
</item>
<item rdf:about="https://blog.anlucky.cn/index.php/programming/database/168">
<title>Redis持久化策略</title>
<link>https://blog.anlucky.cn/index.php/programming/database/168</link>
<dc:date>2023-05-28T18:34:35+08:00</dc:date>
<description>Redis持久化策略1. RDB持久化策略RDB是redis持久化数据到磁盘的策略将内存以快照形式保存Redis数据库中的数据的方式，就是某一时刻，将Redis内存中的数据全量备份到磁盘，RDB就是redis DataBase的缩写备份会不会阻塞主线程Redis是单线程数据处理，Redis提供了两个命令来生成RDB，一个是save 一个是bgsavesave 保存：会阻塞Redis主线程，直到RDB文件备份完成为止，在这个期间，Redis不能处理客户端的任何请求bgSave 保存：不会阻塞主线程，会创建一个子线程，单独我负责RDB文件的数据备份，RDB的优势和劣势优势RDB文件紧凑，全量备份，适合进行备份和灾难恢复生成RDB文件的时候，Redis主进程会分叉一个子进程来处理保存数据，主进程不需要进程IO操作RDB在恢复文件的时候要比AOP速度快2. AOF持久化策略AOF持久化策略，以文件的形式存储Redis内存中的数据，他的文件格式是AOF，他存储的数据是客户端交给Redis执行的写命令因为AOF是在文件中追加写入，由于IO操作的阻塞性，会导致主线程挂起，在写入的时间中无法服务新的请求，因而吞吐量收到影响为了解决上述的问题，Redis并不会立即将数据写入到硬盘中，</description>
</item>
<item rdf:about="https://blog.anlucky.cn/index.php/programming/java/167">
<title>Java浅拷贝和深拷贝</title>
<link>https://blog.anlucky.cn/index.php/programming/java/167</link>
<dc:date>2023-05-27T20:21:57+08:00</dc:date>
<description>浅拷贝和深拷贝参考资料：Java深拷贝和浅拷贝 - 掘金 (juejin.cn)浅拷贝与深拷贝 - 掘金 (juejin.cn)4.1 什么是浅拷贝？什么是深拷贝？浅拷贝：浅拷贝是创建一个新对象，这个对象有着原始属性值的一份精确拷贝，如果是基本数据类型，拷贝的就是基本数据类型的值，如果是深拷贝，拷贝的就是内存地址。所以如果浅拷贝的对象或者对象本身改变了属性或者值那么也会影响另一个对象深拷贝：深拷贝是将这个对象完整的拷贝下来，从内存空间中开辟一个新的地址来存放这个新对象，所以深拷贝创建的对象在改变值之后不会对另一个对象产生影响浅拷贝只复制指向某个对象的指针，而不复制对象本身，新旧对象还是共享同一块内存。但深拷贝会另外创造一个一模一样的对象，新对象跟原对象不共享内存，修改新对象不会改到原对象。4.2 赋值和深拷贝，浅拷贝的区别？赋值：当我们把一个对象赋值给另一个变量引用时，会把这个对象在栈内存中的地址给这个变量引用，这两个对象使用 == 进行比较返回true，这个情况下，不管哪个对象发生改变，改变的都是存储空间中同一块内存地址的数据，所以他们是联动的，不管改变哪个对象，另外一个对象的数据也会跟着改变。浅克隆代码演示准备实体类对象，并创建对应的get set 有参数构造和无参数构造并实现Cloneable接口中的clone方法创建学生实体类public class Student implements Cloneable{
    private int sid;
    private String sname;
    private Teacher teacher;

    @Override
    protected Object clone() throws CloneNotSupportedException {
        return super.clone();
    }
}创建老师实体类public class Teacher implements Cloneable{
    private Integer tid;
    private String tname;

    @Override
    protected Object clone() throws CloneNotSupportedException {
        return super.clone();
    }
}创建测试类Mainpublic class Main {
    public static void main(String[] args) throws CloneNotSupportedException {
        // 创建老师对象
        Teacher teacher = new Teacher();
        teacher.setTid(1);
        teacher.setTname(&quot;张老师&quot;);
        // 学生对象
        Student student = new Student();
        student.setSid(1);
        student.setSname(&quot;小明同学&quot;);
        student.setTeacher(teacher);

        // 创建学生的克隆对象   浅克隆
        Student cloneStudent = (Student) student.clone();

        System.out.println(&quot;浅克隆=》比较源对象和克隆对象  &quot; + (student == cloneStudent));
        System.out.println(&quot;浅克隆=》查询修改前源对象：&quot; + student);
        System.out.println(&quot;浅克隆=》查询修改前克隆对象：&quot; + cloneStudent);

        System.out.println(&quot;修改源对象中学生的sid属性为 2&quot;);
        student.setSid(2);
        System.out.println(&quot;修改源对象中学生的sname属性为 小红同学&quot;);
        student.setSname(&quot;小红同学&quot;);
        System.out.println(&quot;修改源对象中学生的teacher属性中的tid属性为 2&quot;);
        student.getTeacher().setTid(2);
        System.out.println(&quot;浅克隆=》查询修改后源对象：&quot; + student);
        System.out.println(&quot;浅克隆=》查询修改后克隆对象：&quot; + cloneStudent);
    }
}输出结果浅克隆=》比较源对象和克隆对象  false
浅克隆=》查询修改前源对象：Student(sid=1, sname=小明同学, teacher=Teacher(tid=1, tname=张老师))
浅克隆=》查询修改前克隆对象：Student(sid=1, sname=小明同学, teacher=Teacher(tid=1, tname=张老师))
修改源对象中学生的sid属性为 2
修改源对象中学生的sname属性为 小红同学
修改源对象中学生的teacher属性中的tid属性为 2
浅克隆=》查询修改后源对象：Student(sid=2, sname=小红同学, teacher=Teacher(tid=2, tname=张老师))
浅克隆=》查询修改后克隆对象：Student(sid=1, sname=小明同学, teacher=Teacher(tid=2, tname=张老师))浅克隆总结浅拷贝：拷贝前后对象的基本数据类型互不影响，但拷贝前后对象的引用类型因共享同一块内存，会相互影响。浅拷贝是创建一个新对象，这个对象有着原始对象属性值的一份精确拷贝。如果属性是基本类型，拷贝的就是基本类型的值，如果属性是引用类型，拷贝的就是内存地址 ，所以如果其中一个对象改变了这个地址，就会影响到另一个对象。深克隆代码演示深拷贝：从堆内存中开辟一个新的区域存放新对象，对对象中的子对象进行递归拷贝,拷贝前后的两个对象互不影响。创建学生实体类public class Student implements Cloneable{
    private int sid;
    private String sname;
    private Teacher teacher;
    
    @Override
    protected Object clone() throws CloneNotSupportedException {
        return new Student(sid,sname,new Teacher(teacher.getTid(),teacher.getTname()));
    }
}创建老师实体类public class Teacher implements Cloneable{
    private Integer tid;
    private String tname;

    @Override
    protected Object clone() throws CloneNotSupportedException {
        return new Teacher(tid,this.tname);
    }
}创建测试类public class Main2 {
    public static void main(String[] args) throws CloneNotSupportedException {
        // 创建老师对象
        Teacher teacher = new Teacher();
        teacher.setTid(1);
        teacher.setTname(&quot;张老师&quot;);
        // 学生对象
        Student student = new Student();
        student.setSid(1);
        student.setSname(&quot;小明同学&quot;);
        student.setTeacher(teacher);

        // 创建学生的克隆对象   浅克隆
        Student cloneStudent = (Student) student.clone();

        System.out.println(&quot;深克隆=》比较源对象和克隆对象  &quot; + (student == cloneStudent));
        System.out.println(&quot;深克隆=》比较源对象和克隆对象的自定义引用数据类型  &quot; + (student.getTeacher() == cloneStudent.getTeacher()));
        System.out.println(&quot;深克隆=》查询修改前源对象：&quot; + student);
        System.out.println(&quot;深克隆=》查询修改前克隆对象：&quot; + cloneStudent);

        System.out.println(&quot;修改源对象中学生的sid属性为 2&quot;);
        student.setSid(2);
        System.out.println(&quot;修改源对象中学生的sname属性为 小红同学&quot;);
        student.setSname(&quot;小红同学&quot;);
        System.out.println(&quot;修改源对象中学生的teacher属性中的tid属性为 2&quot;);
        student.getTeacher().setTid(2);
        System.out.println(&quot;深克隆=》查询修改后源对象：&quot; + student);
        System.out.println(&quot;深克隆=》查询修改后克隆对象：&quot; + cloneStudent);
    }
}输出结果深克隆=》比较源对象和克隆对象  false
深克隆=》比较源对象和克隆对象的自定义引用数据类型  false
深克隆=》查询修改前源对象：Student(sid=1, sname=小明同学, teacher=Teacher(tid=1, tname=张老师))
深克隆=》查询修改前克隆对象：Student(sid=1, sname=小明同学, teacher=Teacher(tid=1, tname=张老师))
修改源对象中学生的sid属性为 2
修改源对象中学生的sname属性为 小红同学
修改源对象中学生的teacher属性中的tid属性为 2
深克隆=》查询修改后源对象：Student(sid=2, sname=小红同学, teacher=Teacher(tid=2, tname=张老师))
### 深克隆总结

深拷贝：深拷贝会创建一个新的对象，将源对象的属性值放入到新对象，是创建对象赋值，并不是将引用地址指向原来的对象，所以我们在上面测试代码中可以发现深拷贝后的Teacher对象中的源对象和克隆对象比较时返回false，当一个值做更改的时候也不会影响另外一个对象
</description>
</item>
<item rdf:about="https://blog.anlucky.cn/index.php/programming/database/166">
<title>Redis和Mysql如何保证数据一致性</title>
<link>https://blog.anlucky.cn/index.php/programming/database/166</link>
<dc:date>2023-05-27T20:20:46+08:00</dc:date>
<description>Redis和Mysql如何保证数据一致性1. 导致数据不一致的原因当数据发生变化的时候，需要同时去更新Redis和Mysql，更新数据是有先后顺序的，在极端情况下就会出现数据一致性的问题。1. 先更新数据库再更新缓存如果先更新数据库，若是缓存中的数据更新失败，就会出现数据库和redis中的数据不一致问题2. 先删除缓存，再更新数据库先删除缓存再更新数据库，在理想情况删除了缓存，更新了数据库，再第二次请求的时候会发现缓存是空的，会去读取数据库将数据缓存起来。但是删除缓存和更新数据库不是一个原子操作，若是缓存删除了，此时，第二条线程抢到CPU时间轮片，开始读取数据，发现没有缓存，查询数据库，并将数据缓存起来，此时第一条线程拿到CPU时间轮片，开始更新数据库，这个情况下会导致数据库和缓存不一致采用最终一致性的方案解决1. 采用延迟双删除的策略具体步骤先删除缓存写入数据库休眠500毫秒（视情况而定）再次删除缓存具体实现public void write(String key,Object data){
     redis.delKey(key); // 删除缓存
     db.updateData(data); // 更新数据库
     Thread.sleep(500); // 睡眠延迟
     redis.delKey(key);// 再次删除缓存
 }2. 异步更新缓存（基于订阅Binlog）读取MysqlBinlog日志后分析，利用消息队列，推送更新到redis缓存中，这样一旦产生了新的写入，更新，删除等操作，就可以把相关的更新推送到redis中其实这种机制类似于Mysql的主从备份，Mysql的主从备份也是通过Binlog来实现数据一致性这里的消息推送工具可以使用Kafka、rabbitMQ等消息队列来实现

结合使用canal(阿里的一款开源框架)，通过该框架可以对MySQL的binlog进行订阅因为是最终一致性，所以业务如果无法接收短期不一致性的话就得更换策略</description>
</item>
<item rdf:about="https://blog.anlucky.cn/index.php/programming/java/165">
<title>String,StringBuffer,StringBuilder的区别</title>
<link>https://blog.anlucky.cn/index.php/programming/java/165</link>
<dc:date>2023-05-27T20:19:00+08:00</dc:date>
<description>可变性String是final修饰的，他是一个不可变的类，他在每次创建字符串的时候是新建了一个对象，StringBuffer和StringBuilder是一个可变的类，他在每次更改的时候会在源对象的基础上更改，并不会重新创建对象线程安全性String是一个不可变的类，所以他是一个线程安全的，StringBuffer也是一个线程安全的，他的每一个方法都用了一个Synchronized一个同步关键字修饰，StringBuider不是一个线程安全的，多线程的情况下应该使用StirngBuffer性能方面String是性能最低的，因为他是不可变的类，每次修改都是创建对象，StringBuffer是可变的，每次字符串修改或者拼接的时候他不会重新创建对象，StringBuider的性能是最高的，因为StringBuffer是加了锁的，加锁之后性能方面会有所损耗存储方面String存储在字符串常量池中，StringBuffer和StringBuilder是存储在堆内存中</description>
</item>
<item rdf:about="https://blog.anlucky.cn/index.php/programming/tools/164">
<title>阿里云配置Docker加速</title>
<link>https://blog.anlucky.cn/index.php/programming/tools/164</link>
<dc:date>2023-05-27T20:18:05+08:00</dc:date>
<description>1.材料准备准备一个阿里云的实名账号，自己直接注册就可以阿里云官网：https://www.aliyun.com/2. 配置步骤进入阿里云控制台在左上角的三条杠中点开找到容器这个就是配置地址3. 使用配置镜像加速的命令去配置docker</description>
</item>
</rdf:RDF>