From 88f74950bcc8f479958226fff0b9581ce0ccbb4b Mon Sep 17 00:00:00 2001 From: zhangsan <646228430@qq.com> Date: Tue, 8 Jul 2025 15:44:31 +0800 Subject: [PATCH] =?UTF-8?q?7.8=20=E8=B0=83=E6=95=B4=E5=89=8D=E7=AB=AF?= =?UTF-8?q?=E6=95=B0=E6=8D=AE=E8=AF=B7=E6=B1=82=EF=BC=8C=E5=8A=A8=E6=80=81?= =?UTF-8?q?=E5=8A=A0=E8=BD=BD=E5=90=8E=E7=AB=AF=E6=95=B0=E6=8D=AE=EF=BC=9B?= =?UTF-8?q?=E6=9E=84=E5=BB=BA=E9=95=9C=E5=83=8F=E9=83=A8=E7=BD=B2=E4=BA=91?= =?UTF-8?q?=E6=9C=8D=E5=8A=A1=E5=99=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/dev-ops/docker-compose-app.yml | 149 +++++- .../docker-compose-environment-aliyun.yml | 1 + docs/dev-ops/docker-compose-environment.yml | 3 +- docs/dev-ops/mysql/my.cnf | 24 + docs/dev-ops/nginx/html/index.html | 128 ++---- docs/dev-ops/nginx/html/js/index.js | 424 ++++++++++-------- docs/dev-ops/nginx/html/login.html | 4 +- group-buying-sys-app/Dockerfile | 2 +- group-buying-sys-app/build.sh | 3 +- .../src/main/resources/application-prod.yml | 61 ++- .../trigger/http/MarketIndexController.java | 6 +- 11 files changed, 471 insertions(+), 334 deletions(-) create mode 100644 docs/dev-ops/mysql/my.cnf diff --git a/docs/dev-ops/docker-compose-app.yml b/docs/dev-ops/docker-compose-app.yml index a49e331..3cb9cf7 100644 --- a/docs/dev-ops/docker-compose-app.yml +++ b/docs/dev-ops/docker-compose-app.yml @@ -1,27 +1,132 @@ -# /usr/local/bin/docker-compose -f /docs/dev-ops/environment/environment-docker-compose-2.4.yml up -d version: '3.8' -# docker-compose -f docker-compose-app.yml up -d -# 你需要修改system为你自身系统的仓库名 -services: - group-buying-sys: - image: system/group-buying-sys:1.0-SNAPSHOT - container_name: group-buying-sys - restart: on-failure - ports: - - "8091:8091" - environment: - - TZ=PRC - - SERVER_PORT=8091 - volumes: - - ./log:/data/log - logging: - driver: "json-file" - options: - max-size: "10m" - max-file: "3" - networks: - - my-network networks: my-network: driver: bridge + +services: + # 1. 前端 + group-buy-market-front: + image: nginx:alpine + container_name: group-buy-market-front + restart: always + ports: + - '80:80' + - '443:443' + volumes: + - ./nginx/html:/usr/share/nginx/html + privileged: true + networks: + - my-network + + # 2. MySQL + mysql: + image: mysql:8.0 + container_name: mysql + command: --default-authentication-plugin=mysql_native_password + restart: always + environment: + TZ: Asia/Shanghai + MYSQL_ROOT_PASSWORD: 123456 + ports: + - '13306:3306' # 宿主机访问用 13306 + volumes: + - ./mysql/my.cnf:/etc/mysql/conf.d/mysql.cnf:ro + - ./mysql/sql:/docker-entrypoint-initdb.d + healthcheck: + test: ['CMD', 'mysqladmin', 'ping', '-h', 'localhost'] + interval: 5s + timeout: 10s + retries: 10 + start_period: 15s + networks: + - my-network + + # 3. Redis + redis: + image: redis:6.2 + container_name: redis + restart: always + hostname: redis + ports: + - '16379:6379' # 宿主机访问用 16379 + volumes: + - ./redis/redis.conf:/usr/local/etc/redis/redis.conf + command: redis-server /usr/local/etc/redis/redis.conf + healthcheck: + test: ['CMD', 'redis-cli', 'ping'] + interval: 10s + timeout: 5s + retries: 3 + networks: + - my-network + + # 4. Java 后端 + group-buying-sys: + image: smile/group-buying-sys + container_name: group-buying-sys + restart: on-failure + depends_on: + mysql: + condition: service_healthy + redis: + condition: service_healthy + ports: + - '8091:8091' + environment: + # 时区 & 端口 + - TZ=PRC + - SERVER_PORT=8091 + # —— MySQL —— + - SPRING_DATASOURCE_USERNAME=root + - SPRING_DATASOURCE_PASSWORD=123456 + - SPRING_DATASOURCE_URL=jdbc:mysql://mysql:3306/big_market?useUnicode=true&characterEncoding=utf8&autoReconnect=true&serverTimezone=Asia/Shanghai&useSSL=false + - SPRING_DATASOURCE_DRIVER_CLASS_NAME=com.mysql.cj.jdbc.Driver + - SPRING_HIKARI_POOL_NAME=Retail_HikariCP + # —— Redis —— + - REDIS_SDK_CONFIG_HOST=redis + - REDIS_SDK_CONFIG_PORT=6379 + volumes: + - ./log:/data/log + logging: + driver: json-file + options: + max-size: '10m' + max-file: '3' + networks: + - my-network + + # 5. phpMyAdmin + phpmyadmin: + image: phpmyadmin:5.2.1 + container_name: phpmyadmin + hostname: phpmyadmin + depends_on: + mysql: + condition: service_healthy + ports: + - '8899:80' + environment: + - PMA_HOST=mysql + - PMA_PORT=3306 + - MYSQL_ROOT_PASSWORD=123456 + networks: + - my-network + + # 6. Redis Commander + redis-admin: + image: spryker/redis-commander:0.8.0 + container_name: redis-admin + hostname: redis-commander + restart: always + depends_on: + redis: + condition: service_healthy + ports: + - '8081:8081' + environment: + - REDIS_HOSTS=local:redis:6379 + - HTTP_USER=admin + - HTTP_PASSWORD=admin + networks: + - my-network diff --git a/docs/dev-ops/docker-compose-environment-aliyun.yml b/docs/dev-ops/docker-compose-environment-aliyun.yml index d21ae7a..5997e92 100644 --- a/docs/dev-ops/docker-compose-environment-aliyun.yml +++ b/docs/dev-ops/docker-compose-environment-aliyun.yml @@ -12,6 +12,7 @@ services: ports: - "13306:3306" volumes: + - ./mysql/my.cnf:/etc/mysql/conf.d/mysql.cnf:ro - ./mysql/sql:/docker-entrypoint-initdb.d healthcheck: test: [ "CMD", "mysqladmin" ,"ping", "-h", "localhost" ] diff --git a/docs/dev-ops/docker-compose-environment.yml b/docs/dev-ops/docker-compose-environment.yml index eff81f6..794cf38 100644 --- a/docs/dev-ops/docker-compose-environment.yml +++ b/docs/dev-ops/docker-compose-environment.yml @@ -1,4 +1,4 @@ -version: '3.9' +version: '3.8' services: mysql: image: mysql:8.0 @@ -11,6 +11,7 @@ services: ports: - "13306:3306" volumes: + - ./mysql/my.cnf:/etc/mysql/conf.d/mysql.cnf:ro - ./mysql/sql:/docker-entrypoint-initdb.d healthcheck: test: [ "CMD", "mysqladmin" ,"ping", "-h", "localhost" ] diff --git a/docs/dev-ops/mysql/my.cnf b/docs/dev-ops/mysql/my.cnf new file mode 100644 index 0000000..0768a14 --- /dev/null +++ b/docs/dev-ops/mysql/my.cnf @@ -0,0 +1,24 @@ +[client] +port = 3306 +default-character-set = utf8mb4 + +[mysqld] +user = mysql +port = 3306 +sql_mode = NO_ENGINE_SUBSTITUTION,STRICT_TRANS_TABLES + +default-storage-engine = InnoDB +default-authentication-plugin = mysql_native_password +character-set-server = utf8mb4 +collation-server = utf8mb4_unicode_ci +init_connect = 'SET NAMES utf8mb4' + +slow_query_log +#long_query_time = 3 +slow-query-log-file = /var/log/mysql/mysql.slow.log +log-error = /var/log/mysql/mysql.error.log + +default-time-zone = '+8:00' + +[mysql] +default-character-set = utf8mb4 \ No newline at end of file diff --git a/docs/dev-ops/nginx/html/index.html b/docs/dev-ops/nginx/html/index.html index e87bede..278c400 100644 --- a/docs/dev-ops/nginx/html/index.html +++ b/docs/dev-ops/nginx/html/index.html @@ -1,140 +1,83 @@ - - - 手写MyBatis:渐进式源码实践 - 拼多多 - - + + + 手写 MyBatis:渐进式源码实践 - 拼多多 + + + + +
-
-
-
+
+
+
- +
-
80
-
100
+
+
-
手写MyBatis:渐进式源码实践(全彩)
+ + +
手写 MyBatis:渐进式源码实践(全彩)
+
大促优惠 - - 直降 ¥60 - +
- +
+
-
-
-
- -
- - -
-
-
- -
- - -
-
-
- -
- - -
-
-
- -
- - -
- -
-
- -
- - -
-
-
- -
- - -
-
+ +
- - 首页 + 首页
- - 收藏 + 收藏
- - 购物车 + 购物车
+
- -
- + +

请扫码支付

- 支付二维码 + 支付二维码
@@ -142,6 +85,7 @@
+ - \ No newline at end of file + diff --git a/docs/dev-ops/nginx/html/js/index.js b/docs/dev-ops/nginx/html/js/index.js index 5c32d27..b7dc218 100644 --- a/docs/dev-ops/nginx/html/js/index.js +++ b/docs/dev-ops/nginx/html/js/index.js @@ -1,205 +1,253 @@ -// index.js (改进版) -// 功能: -// 1. 解决用户列表竖向轮播在无缝跳转时出现的卡顿/闪动问题 -// 2. 为每条拼单信息增加实时倒计时,秒级更新 +/* ------------------------------------------------------- + * Author : 你 + * Desc : 改进版拼团页面脚本(userId 从 cookie 里读) + * ----------------------------------------------------- */ +document.addEventListener('DOMContentLoaded', () => { -document.addEventListener('DOMContentLoaded', function () { - /* ---------- 顶部横向轮播 ---------- */ + /* ========== 通用工具 ========== */ + const getCookie = (k) => + document.cookie + .split(';') + .map((c) => c.trim()) + .find((c) => c.startsWith(k + '='))?.split('=')[1] || null; + + /* ----------- 0. DOM 快捷引用 ----------- */ + const $ = (id) => document.getElementById(id); + const currentPrice = $('currentPrice'); + const originalPriceElem = $('originalPrice'); + const dropPrice = $('dropPrice'); + const soldBox = $('soldBox'); + const groupTitle = $('groupTitle'); + const userList = $('userList'); + const singlePriceSpan = $('singlePrice'); + const groupPriceSpan = $('groupPrice'); + const btnSingle = $('btnSingle'); + const btnGroup = $('btnGroup'); + + /* ===================================================== + * 1. 取接口数据并渲染 + * =================================================== */ + const API_URL = 'http://127.0.0.1:8091/api/v1/gbm/index/query_group_buy_market_config'; + + // 读取 cookie 中的 username 当作 userId + const username = getCookie('username'); + + // 如果没登录,直接跳去登录页,免得后面接口 401/判空 + if (!username) { + location.href = 'login.html'; + return; + } + + const POST_BODY = { + userId : username, // 不再写死 + source : 's01', + channel: 'c01', + goodsId: '9890001' + }; + + fetch(API_URL, { + method : 'POST', + headers: { 'Content-Type': 'application/json' }, + body : JSON.stringify(POST_BODY), + }) + .then((r) => r.json()) + .then(({ code, info, data }) => { + if (code !== '0000' || !data) { + console.error('接口异常:', info); + return; + } + renderGoods(data.goods); + renderStatistic(data.teamStatistic); + renderTeams(data.teamList, data.goods?.payPrice); + }) + .catch((e) => console.error('接口请求失败:', e)); + + /* ------------- 渲染商品信息 ------------- */ + function renderGoods(g = {}) { + const { originalPrice = 0, payPrice = 0, deductionPrice = 0 } = g; + currentPrice.textContent = payPrice; + originalPriceElem.textContent = originalPrice; + dropPrice.textContent = `直降 ¥${deductionPrice}`; + + singlePriceSpan.textContent = `¥${originalPrice}`; + groupPriceSpan.textContent = `¥${payPrice}`; + btnSingle.dataset.price = originalPrice; + btnGroup.dataset.price = payPrice; + } + + /* ------------- 渲染统计信息 ------------- */ + function renderStatistic(stat = {}) { + const { allTeamUserCount = 0 } = stat; + groupTitle.textContent = `${allTeamUserCount}人在抢,参与可立即拼成`; + soldBox.textContent = `${allTeamUserCount}人再抢`; + } + + /* ------------- 渲染拼团列表 ------------- */ + function renderTeams(list = [], groupPrice = 0) { + if (!list || list.length === 0) { + groupTitle.textContent = '小伙伴,赶紧去开团吧,做村里最靓的仔。'; + return; + } + userList.innerHTML = ''; + list.forEach((t) => userList.appendChild(makeItem(t, groupPrice))); + initUserMarquee(); + initCountdown(); + } + + function makeItem(team, price) { + const { userId, targetCount, lockCount, validTimeCountdown } = team; + const leftNum = Math.max(targetCount - lockCount, 0); + const timeText = validTimeCountdown || '00:00:00'; + + const div = document.createElement('div'); + div.className = 'user-item'; + div.innerHTML = ` +
+
+
${userId}
+
+ 仅剩${leftNum}人成团 + ${timeText} +
+
+ + `; + return div; + } + + /* ===================================================== + * 2. 拼单列表纵向轮播 + * =================================================== */ + function initUserMarquee() { + const items = userList.querySelectorAll('.user-item'); + if (items.length <= 1) return; + + const itemH = items[0].offsetHeight; + userList.appendChild(items[0].cloneNode(true)); // 无缝衔接 + + let idx = 0; + userList.addEventListener('transitionend', () => { + if (idx >= items.length) { + userList.style.transition = 'none'; + userList.style.transform = 'translateY(0)'; + idx = 0; + void userList.offsetWidth; + } + }); + + setInterval(() => { + idx++; + userList.style.transition = 'transform .5s ease'; + userList.style.transform = `translateY(${-idx * itemH}px)`; + }, 3000); + } + + /* ===================================================== + * 3. 倒计时 + * =================================================== */ + let countdownData = []; + + function initCountdown() { + const els = document.querySelectorAll('.countdown'); + countdownData = Array.from(els).map((el) => ({ + el, + remain: toSec(el.textContent.trim()), + })); + setInterval(tick, 1000); + } + + const toSec = (t) => { + if (!t.includes(':')) return 0; + const [h = '00', m = '00', s = '00'] = t.split(':'); + return +h * 3600 + +m * 60 + +s; + }; + const fmt = (n) => String(n).padStart(2, '0'); + const format = (s) => `${fmt(s / 3600 | 0)}:${fmt((s % 3600) / 60 | 0)}:${fmt(s % 60)}`; + + function tick() { + countdownData.forEach((c) => { + if (c.remain > 0) { + c.remain--; + c.el.textContent = format(c.remain); + if (c.remain === 0) expire(c.el); + } + }); + } + function expire(el) { + el.textContent = '00:00:00'; + const item = el.closest('.user-item'); + item?.classList.add('expired'); + item?.querySelector('.buy-btn')?.setAttribute('disabled', 'disabled'); + } + + /* ===================================================== + * 4. 支付弹窗(事件委托) + * =================================================== */ + const modal = $('paymentModal'); + const amountText = $('paymentAmount'); + const cancelPayment = $('cancelPayment'); + const completePayment= $('completePayment'); + + document.body.addEventListener('click', (e) => { + const btn = e.target.closest('.buy-btn, .btn-single, .btn-group'); + if (!btn) return; + + // 再次确认 cookie,防止手动删 cookie + if (!getCookie('username')) { + location.href = 'login.html'; + return; + } + + amountText.textContent = `支付金额 ¥${btn.dataset.price || 0}`; + modal.style.display = 'flex'; + }); + + cancelPayment.onclick = () => modal.style.display = 'none'; + completePayment.onclick= () => { alert('支付成功!'); modal.style.display = 'none'; }; + modal.addEventListener('click', (e) => { if (e.target === modal) modal.style.display = 'none'; }); + + /* ===================================================== + * 5. 顶部横向轮播(原逻辑保留) + * =================================================== */ const wrapper = document.querySelector('.swiper-wrapper'); const slides = [...wrapper.children]; const pagination = document.querySelector('.swiper-pagination'); const count = slides.length; - let current = 0; // 当前索引 - let startX = 0; // 手势起点 - let dragging = false; // 拖动状态 - let timer = null; // 自动轮播计时器 - - /* --- 1. 生成分页小圆点 --- */ - for(let i=0;igoTo(i)); + dot.className = 'swiper-dot' + (i === 0 ? ' active' : ''); + dot.onclick = () => goTo(i); pagination.appendChild(dot); } const dots = pagination.children; - /* --- 2. 切换核心 --- */ - function goTo(index){ - current = (index + count) % count; // 防越界 + const goTo = (i) => { + current = (i + count) % count; wrapper.style.transition = 'transform .3s ease'; - wrapper.style.transform = `translateX(-${current*100}%)`; - [...dots].forEach((d,i)=>d.classList.toggle('active',i===current)); - } - - /* --- 3. 自动轮播 --- */ - function startAuto(){ - timer = setInterval(()=>goTo(current+1),3000); - } - function stopAuto(){ - clearInterval(timer); - } - startAuto(); - - /* --- 4. 手势/鼠标拖动 --- */ - const getX = e => e.touches ? e.touches[0].clientX : e.clientX; - - wrapper.addEventListener('pointerdown',e=>{ - stopAuto(); - dragging = true; - startX = getX(e); - wrapper.style.transition = 'none'; // 跟手拖动 - }); - - wrapper.addEventListener('pointermove',e=>{ - if(!dragging) return; - const diff = getX(e) - startX; // 像素位移 - wrapper.style.transform = - `translateX(calc(${-current*100}% + ${diff}px))`; - }); - - const endSwipe = e=>{ - if(!dragging) return; - dragging = false; - const diff = getX(e) - startX; - const limit = wrapper.offsetWidth * 0.15; // 15% 宽度阈值 - if(diff > limit) goTo(current-1); - else if(diff < -limit) goTo(current+1); - else goTo(current); // 回弹 - startAuto(); + wrapper.style.transform = `translateX(-${current * 100}%)`; + [...dots].forEach((d, j) => d.classList.toggle('active', j === current)); }; - wrapper.addEventListener('pointerup', endSwipe); - wrapper.addEventListener('pointercancel', endSwipe); - wrapper.addEventListener('pointerleave', endSwipe); + const auto = () => { timer = setInterval(() => goTo(current + 1), 3000); }; + const stop = () => clearInterval(timer); + auto(); + const getX = (e) => (e.touches ? e.touches[0].clientX : e.clientX); + wrapper.addEventListener('pointerdown', (e) => { stop(); dragging = true; startX = getX(e); wrapper.style.transition = 'none'; }); + wrapper.addEventListener('pointermove', (e) => { if (!dragging) return; const diff = getX(e) - startX; wrapper.style.transform = `translateX(calc(${-current * 100}% + ${diff}px))`; }); + wrapper.addEventListener('pointerup', endSwipe); + wrapper.addEventListener('pointercancel',endSwipe); + wrapper.addEventListener('pointerleave', endSwipe); - /* --------- 动态生成“xx人在抢,参与可立即拼成” --------- */ - const leftNum = Math.floor(Math.random() * 101) + 100; // 100 ~ 200 - const groupTitle = document.getElementById('groupTitle'); - if (groupTitle) groupTitle.textContent = `${leftNum}人在抢,参与可立即拼成`; - - /* ---------- 拼单用户纵向轮播 ---------- */ - const userList = document.getElementById('userList'); - const userItems = userList.querySelectorAll('.user-item'); - const itemHeight = userItems[0].offsetHeight; - let userIndex = 0; - const originalCount = userItems.length; - - // 克隆第一条放到末尾,实现无缝衔接 - userList.appendChild(userItems[0].cloneNode(true)); - - userList.addEventListener('transitionend', () => { - if (userIndex >= originalCount) { - // 闪电跳回首条,关闭过渡以避免闪屏 - userList.style.transition = 'none'; - userList.style.transform = 'translateY(0)'; - userIndex = 0; - // 强制回流,保证下次 transition 生效 - void userList.offsetWidth; - } - }); - - function rotateUsers() { - userIndex++; - userList.style.transition = 'transform 0.5s ease'; - userList.style.transform = `translateY(${-userIndex * itemHeight}px)`; + function endSwipe(e) { + if (!dragging) return; + dragging = false; + const diff = getX(e) - startX; + const limit = wrapper.offsetWidth * 0.15; + if (diff > limit) goTo(current - 1); + else if (diff < -limit) goTo(current + 1); + else goTo(current); + auto(); } - - // 每 3 秒滚动一次 - setInterval(rotateUsers, 3000); - // 页面加载后立即滚动一次,保证视觉一致 - rotateUsers(); - - /* ---------- 拼单倒计时 ---------- */ - const countdownEls = document.querySelectorAll('.countdown'); - // 预处理:把初始文本转为秒数 - const countdownData = Array.from(countdownEls).map((el) => ({ - el, - remain: parseTime(el.textContent.trim()), - })); - - function parseTime(t) { - const [h = '00', m = '00', s = '00'] = t.split(':'); - return Number(h) * 3600 + Number(m) * 60 + Number(s); - } - function formatTime(sec) { - const h = String(Math.floor(sec / 3600)).padStart(2, '0'); - const m = String(Math.floor((sec % 3600) / 60)).padStart(2, '0'); - const s = String(sec % 60).padStart(2, '0'); - return `${h}:${m}:${s}`; - } - - function updateCountdown() { - countdownData.forEach((c) => { - if (c.remain > 0) { - c.remain -= 1; - c.el.textContent = formatTime(c.remain); - } else { - c.el.textContent = '00:00:00'; - // 可选:到点后给整条加灰色样式,并禁用按钮 - const item = c.el.closest('.user-item'); - item?.classList.add('expired'); - item?.querySelector('.buy-btn')?.setAttribute('disabled', 'disabled'); - } - }); - } - - /* --------- 动态填充“已抢 xxx 件” --------- */ - const soldBox = document.getElementById('soldBox'); - if (soldBox){ - const soldNum = Math.floor(Math.random()*101)+200; // 200~300 - soldBox.textContent = `已抢 ${soldNum} 件`; - } - - /* --------- 给每条拼单状态前加 “仅剩 x 人成团” --------- */ - document.querySelectorAll('.user-status').forEach(statusEl=>{ - const x = Math.floor(Math.random()*3)+1; // 1 ~ 3 随机整数 - const span = document.createElement('span'); - span.className = 'left-num'; - span.textContent = `仅剩${x}人成团,`; // 注意带逗号或空格 - statusEl.prepend(span); - }); - - /* ============= 支付 & 登录判断 ============= */ - const modal = document.getElementById('paymentModal'); - const paymentAmount = document.getElementById('paymentAmount'); - const cancelPayment = document.getElementById('cancelPayment'); - const completePayment= document.getElementById('completePayment'); - - /* 把 3 类按钮统一选出来 */ - [...document.querySelectorAll('.buy-btn, .btn-single, .btn-group')].forEach(btn=>{ - btn.addEventListener('click',()=>{ - /* 简单读取 cookie 判断是否登录 */ - if(!getCookie('username')){ - window.location.href='login.html'; // 跳转到登录页 - return; - } - /* 已登录:弹出支付弹窗 */ - const price = btn.dataset.price || '0'; - paymentAmount.textContent = `支付金额 ¥${price}`; - modal.style.display='flex'; - }); - }); - - /* 取消/完成支付 */ - cancelPayment.addEventListener('click', ()=>modal.style.display='none'); - completePayment.addEventListener('click', ()=>{ - alert('支付成功!'); - modal.style.display='none'; - }); - - /* 读取 cookie 工具函数 */ - function getCookie(name){ - return document.cookie.split(';').map(c=>c.trim()) - .find(c=>c.startsWith(name+'='))?.split('=')[1] || null; - } - - /* 点击遮罩空白关闭弹窗 */ - modal.addEventListener('click', e=>{ - if(e.target===modal) modal.style.display='none'; - }); - - - // 每秒刷新一次倒计时 - setInterval(updateCountdown, 1000); }); diff --git a/docs/dev-ops/nginx/html/login.html b/docs/dev-ops/nginx/html/login.html index 221de38..c4ea967 100644 --- a/docs/dev-ops/nginx/html/login.html +++ b/docs/dev-ops/nginx/html/login.html @@ -3,13 +3,13 @@ - 欢迎登录 - 小傅哥拼团 + 欢迎登录 - 宇哥拼团