编程导航智能BI平台话题讨论

智能BI平台

132 参与
分享

快来分享你的内容吧~

点击登录,快来和大家讨论吧~
表情
图片
话题
打卡
综合
交流
文章
问答

自定义拒绝策略怎么获取任务参数呢

如图 ![image.png](https://pic.code-nav.cn/post_picture/1827999601287352322/yZFyqEq4CktjOUFi.webp) 获取这个fn之后又找不到arg了 ![image.png](https://pic.code-nav.cn/post_picture/1827999601287352322/KvCXeSiWv0oARshT.webp)

🚀 编程导航成品项目(上百个真实项目)

编程导航已累计发布数十套项目教程:[课程地址](https://www.codefather.cn/course)。 为方便大家集中学习参考,也帮助已完成项目的同学获得更多曝光与真实用户,我们整理汇总了过往提交的优质项目帖。 🔍 如果你的项目已提交,欢迎确认是否被收录; 🚀 如果你正在开发中,不妨参考已有项目的实现思路与笔记总结,相信对你有帮助。 > 收录说明:若你的项目文章尚未被收录,欢迎评论区留言:项目文章链接 + 线上地址(如有),管理员会定期更新到汇总帖里,助力每一个认真分享的项目被更多人看见~ ## AI 零代码应用生成平台 企业级项目教程:https://codefather.cn/course/1948291549923344386 > 视频教程 + 项目文档 + 源码 + 简历写法 + 面试题解 + 专属交流群 #### CodeLoom(已上线) 👉 线上地址:http://1.15.243.228:9010/CodeLoom/ 📚 原帖链接:https://codefather.cn/post/1960256724123316226 ![](https://pic.code-nav.cn/post_picture/1608440217629360130/PdBjpzqUs22fOs8u.webp) ![](https://pic.code-nav.cn/post_picture/1703323293366480898/SJwcQ2vXnXHliL0I.webp) #### AI Code Mother(已上线) 👉 线上地址:https://joinoai.cloud/user/register?inviteCode=INVNJZKYSJH 📚 原帖链接:https://codefather.cn/post/1974436661408411649 ![](https://pic.code-nav.cn/post_picture/1608440217629360130/pivN94SB2tSIPyGq.webp) ![](https://pic.code-nav.cn/post_picture/1608440217629360130/qUJB4R1QU6My6glT.webp) #### 代码灵构 📚 原帖链接:https://codefather.cn/note/1957033275842670593 ![](https://pic.code-nav.cn/post_picture/1888144829114413057/Aj7Z4LXAk4PhSYts.webp) ![](https://pic.code-nav.cn/post_picture/1888144829114413057/sRWVEiAID8l6sR8n.webp) #### XiangAI 📚 原帖链接:https://codefather.cn/essay/1957335221259333633 ![](https://pic.code-nav.cn/post_picture/1670233072726978561/R0sTMQJQgAkXH7Vx.webp) ![](https://pic.code-nav.cn/post_picture/1670233072726978561/UvAzLkIYUEJG9fNb.webp) #### AI Code easen 📚 原帖链接:https://codefather.cn/post/1954389275447824386 ![](https://pic.code-nav.cn/post_picture/1824705191980900354/mVQ9cozeOJpyzc9q.webp) ![](https://pic.code-nav.cn/post_picture/1824705191980900354/andQermlfAWwlDJt.webp) ## AI 超级智能体 企业级项目教程:https://codefather.cn/course/1915010091721236482 > 视频教程 + 项目文档 + 源码 + 简历写法 + 面试题解 + 专属交流群 #### Crispix Agent 📚 原帖链接:https://codefather.cn/post/1962528203403792386 ![](https://pic.code-nav.cn/post_picture/1721414917258604546/B2nzTTkh8ADI4rNk.webp) #### ApocalyxAI(智能助手平台) 📚 原帖链接:https://codefather.cn/post/1937413626668843010 ![](https://pic.code-nav.cn/post_picture/1621488095675301890/tfgNMqVMDpOeFXvD.webp) ![](https://pic.code-nav.cn/post_picture/1621488095675301890/DNRVr0kffJtkRxeY.webp) #### 神农医慧库 📚 原帖链接:https://codefather.cn/post/1936010788000563201 ![](https://pic.code-nav.cn/post_picture/1608682750397644802/JEctFf5ICJqCqPqV.webp) #### AI 智能应用中心 📚 原帖链接:https://codefather.cn/post/1928019148442537985 ![](https://pic.code-nav.cn/post_picture/1848659043884322817/wV6VY0Hat7DXQ8FP.webp) #### CodeManus AI 助手 📚 原帖链接:https://codefather.cn/post/1966800552892235778 ![](https://pic.code-nav.cn/post_picture/1610246464317947905/o4VSTgUim0GA7FcX.webp) ![](https://pic.code-nav.cn/post_picture/1610246464317947905/CFgqRf4LSejLhl0S.webp) ## 智能协同云图库项目项目 企业级项目教程:https://codefather.cn/course/1864210260732116994 > 视频教程 + 项目文档 + 源码 + 简历写法 + 面试题解 + 专属交流群 #### 悦木图库(已上线) 👉 线上地址:https://lumenglover.com/ 📚 原帖链接:https://codefather.cn/essay/1890067861869486081 ![](https://pic.code-nav.cn/post_picture/1608440217629360130/murJOm00xs1Tamxc.webp) ![](https://pic.code-nav.cn/post_picture/1608440217629360130/Ehd5odwMeEidfCnS.webp) #### 暴龙图库(已上线) 👉 线上地址:https://picture.baolong.icu/ 📚 原帖链接:https://codefather.cn/post/1905997569888280577 ![](https://pic.code-nav.cn/post_picture/1608440217629360130/XASRZxI2jqxigDQf.webp) ![](https://pic.code-nav.cn/post_picture/1608440217629360130/L53RNA6qwEIFno2w.webp) #### Crispix AI 图库(已上线) 👉 线上地址:https://crispix.fun 📚 原帖链接:https://codefather.cn/post/1915861894909386754 ![](https://pic.code-nav.cn/post_picture/1608440217629360130/cOyr8gQLPI1JjmxB.webp) ![](https://pic.code-nav.cn/post_picture/1608440217629360130/WlmqGsoiWWNnhZHs.webp) #### 云图智联(已上线) 👉 线上地址:https://imagecore.netlify.app/ 📚 原帖链接:https://codefather.cn/post/1970098502037934081 ![](https://pic.code-nav.cn/post_picture/1608440217629360130/gllM8wi1pzZU2NPS.webp) ![](https://pic.code-nav.cn/post_picture/1608440217629360130/EFK4NYsecsCuI0t6.webp) #### 荒梦图库(已上线) 👉 线上地址:https://pichm.cloud/ 📚 原帖链接:https://codefather.cn/post/1960673049771610113 ![](https://pic.code-nav.cn/post_picture/1608440217629360130/7eblX4ytK6y3aFJ2.webp) ![](https://pic.code-nav.cn/post_picture/1608440217629360130/6iQvCfnibK5RgbeV.webp) #### Picazza 图库(已上线) 👉 线上地址:https://picazza.com/ 📚 原帖链接:https://www.codefather.cn/post/1945659153240215554 ![](https://pic.code-nav.cn/post_picture/1608440217629360130/gRQtlZSRvZGkTYa4.webp) ![](https://pic.code-nav.cn/post_picture/1608440217629360130/pwjMYgiQU382cnbA.webp) #### 悦突突(已上线) 👉 线上地址:https://coder-live.cn/ 📚 原帖链接:https://codefather.cn/post/1950740016980426754 ![](https://pic.code-nav.cn/post_picture/1608440217629360130/SwQMCfQxpisrijtQ.webp) ![](https://pic.code-nav.cn/post_picture/1608440217629360130/zZB5w163IY7BHOGh.webp) #### 图潮(已上线) 👉 线上地址:https://picwave.top/ 📚 原帖链接:https://codefather.cn/essay/1911024578559188993 ![](https://pic.code-nav.cn/post_picture/1608440217629360130/WDzHlwEBsW52u7MO.webp) ![](https://pic.code-nav.cn/post_picture/1608440217629360130/pAACMiXmClm4a9BJ.webp) #### 云相册(已上线) 👉 线上地址:https://pic.lin03.cn/ 📚 原帖链接:https://codefather.cn/post/1929477619980566530 ![](https://pic.code-nav.cn/post_picture/1608440217629360130/eTcmMDx1ntRVl1m2.webp) ![](https://pic.code-nav.cn/post_picture/1608440217629360130/5HdFimQDt1BBxfup.webp) #### HY 映像(已上线) 👉 线上地址:https://zhydoc.com/picture/ 📚 原帖链接:https://codefather.cn/post/1967843032026775553 ![](https://pic.code-nav.cn/post_picture/1608440217629360130/sWwwbTKOC7RiDaBU.webp) #### 智图云链(已上线) 👉 线上地址:http://117.72.69.235/ ![](https://pic.code-nav.cn/post_picture/1608440217629360130/7zUZPabtIn6tGiWJ.webp) #### han_picture 📚 原帖链接:https://codefather.cn/post/1974327430218887170 ![](https://pic.code-nav.cn/post_picture/1919975314153705473/eb9ZDRFdernRstG2.webp) ![](https://pic.code-nav.cn/post_picture/1608440217629360130/JtXGoj6yB1M6NGT4.webp) ## 面试刷题平台 企业级项目教程:https://codefather.cn/course/1826803928691945473 > 视频教程 + 项目文档 + 源码 + 简历写法 + 面试题解 + 专属交流群 #### 面试秘境(已上线) 👉 线上地址:http://mianshimijing.icu/ 📚 原帖链接:https://codefather.cn/post/1869660024067571713 ![](https://pic.code-nav.cn/post_picture/1608440217629360130/LaKjVB7UAoCMSzh6.webp) ![](https://pic.code-nav.cn/post_picture/1608440217629360130/5h3ODsIzIYOasUfs.webp) ## AI 答题应用平台 企业级项目教程:https://codefather.cn/course/1790274408835506178 > 视频教程 + 项目文档 + 源码 + 简历写法 + 面试题解 + 专属交流群 #### 腾蛇问渊(已上线) 👉 线上地址:https://aianswerproj.vercel.app/ 📚 原帖链接:https://codefather.cn/essay/1809498338799878146 ![](https://pic.code-nav.cn/post_picture/1608440217629360130/dt2goZktpI1JbuuR.webp) #### 智慧答 📚 原帖链接:https://codefather.cn/post/1826490068167761921 ![](https://pic.code-nav.cn/post_picture/1608440217629360130/kOBg5f4jysOjDPPa.webp) ![](https://pic.code-nav.cn/post_picture/1608440217629360130/N46CeGFrGLI7f16a.webp) ## OJ 在线判题系统 企业级项目教程:https://codefather.cn/course/1790980707917017089 > 视频教程 + 项目文档 + 源码 + 简历写法 + 面试题解 + 专属交流群 #### 雪 OJ(已上线) 👉 线上地址:http://snowyee.cn/snowyee-oj/#/ 📚 原帖链接:https://codefather.cn/note/1810751001442844674 ![](https://pic.code-nav.cn/post_picture/1608440217629360130/j77o1gfOaHniveqY.webp) ![](https://pic.code-nav.cn/post_picture/1608440217629360130/iWoNsdsAGTImIWex.webp) #### 流火 OJ 📚 原帖链接:https://codefather.cn/post/1827575600860672001 ![](https://pic.code-nav.cn/post_picture/1608440217629360130/qNZGKzPc1HENLb01.webp) ![](https://pic.code-nav.cn/post_picture/1608440217629360130/1qtIMgNICIsvOClW.webp) #### SSPU-OJ 📚 原帖链接:https://codefather.cn/post/1964988539086487554 ![](https://pic.code-nav.cn/post_picture/1608440217629360130/rFRFnHcW4VALYU4e.webp) ![](https://pic.code-nav.cn/post_picture/1608440217629360130/P8UYTXDcQG33XZVB.webp) #### 新 OJ 📚 原帖链接:https://codefather.cn/note/1806522941518364673 ![](https://pic.code-nav.cn/post_picture/1608440217629360130/jWATqmqsEvXDz44X.webp) ![](https://pic.code-nav.cn/post_picture/1608440217629360130/koUeN291i7wcVHE5.webp) #### HHOJ 📚 原帖链接:https://codefather.cn/post/1895681485945774082 ![](https://pic.code-nav.cn/post_picture/1608440217629360130/0HGQqyrWIim31igD.webp) ![](https://pic.code-nav.cn/post_picture/1608440217629360130/LDqN2amO3CYw5Ixv.webp) #### AICoder OJ 📚 原帖链接:https://codefather.cn/note/1811051057198858241 ![](https://pic.code-nav.cn/post_picture/1608440217629360130/fyB7zTkXnvZ7Oeqq.webp) 📚 原帖链接:https://codefather.cn/note/1829734814391640065 ![](https://pic.code-nav.cn/post_picture/1608440217629360130/fbWdtCXHbwwnVhCb.webp) ![](https://pic.code-nav.cn/post_picture/1608440217629360130/mTrhcyHIbBbmwNrr.webp) #### CsCS OJ 📚 原帖链接:https://codefather.cn/post/1836003849697837057 ![](https://pic.code-nav.cn/post_picture/1608440217629360130/XktyUujgypLwPaO4.webp) #### 疯 OJ 📚 原帖链接:https://codefather.cn/post/1913879429893369857 ![](https://pic.code-nav.cn/post_picture/1608440217629360130/oNvu7I0pVDoNbayB.webp) ![](https://pic.code-nav.cn/post_picture/1608440217629360130/AhgOcPOVH7LXyB6p.webp) ## 代码生成器共享平台 企业级项目教程:https://codefather.cn/course/1790980795074654209 > 视频教程 + 项目文档 + 源码 + 简历写法 + 面试题解 + 专属交流群 #### 飞云代码生成 📚 原帖链接:https://codefather.cn/note/1816913082742140930 ![](https://pic.code-nav.cn/post_picture/1608440217629360130/7dhzMIjx1JjUOQci.webp) ## 智能 BI 项目 企业级项目教程:https://codefather.cn/course/1790980531403927553 > 视频教程 + 项目文档 + 源码 + 简历写法 + 面试题解 + 专属交流群 #### 灵犀 BI(已上线) 👉 线上地址:http://bi.hejiajun.icu/ 📚 原帖链接:https://codefather.cn/post/1876163793297530881 ![](https://pic.code-nav.cn/post_picture/1608440217629360130/P3P64Q3ZAhNV2SSQ.webp) #### Kim 智能 BI 📚 原帖链接:https://codefather.cn/post/1908147328172138498 ![](https://pic.code-nav.cn/post_picture/1608440217629360130/ymESyvXAwiBMl0p8.webp) ![](https://pic.code-nav.cn/post_picture/1608440217629360130/z0zZrELsMlG0UMDb.webp) #### Liy 智能 BI 分析系统 👉 线上地址:https://8.141.7.82/login 📚 原帖链接:https://codefather.cn/essay/1853271746140782593 ![](https://pic.code-nav.cn/post_picture/1608440217629360130/anVPMhtHF8dLwwWe.webp) ![](https://pic.code-nav.cn/post_picture/1608440217629360130/VTgB0mDweYzvFUG9.webp) #### Admin 智能 BI 分析系统 📚 原帖链接:https://codefather.cn/post/1812563734513770498 ![](https://pic.code-nav.cn/post_picture/1608440217629360130/qFnQUyKESn4QCiZB.webp) ![](https://pic.code-nav.cn/post_picture/1608440217629360130/bJBfaq9aFFVyTTid.webp) #### Flance 智能 BI 系统 📚 原帖链接:https://codefather.cn/note/1813731731964133378 ![](https://pic.code-nav.cn/post_picture/1608440217629360130/t7RCNYnIAdu9zRKf.webp) ![](https://pic.code-nav.cn/post_picture/1608440217629360130/dVW6o9Zremug4yMG.webp) #### AI 洞察 📚 原帖链接:https://codefather.cn/post/1816913159401435138 ![](https://pic.code-nav.cn/post_picture/1608440217629360130/rt14jzmxeBJQAAqY.webp) ![](https://pic.code-nav.cn/post_picture/1608440217629360130/S5qWmB3w5fZ0R1wh.webp) ![](https://pic.code-nav.cn/post_picture/1608440217629360130/5ULufKqbzfgTv9ZL.webp) #### hx BI 📚 原帖链接:https://codefather.cn/post/1812563043074367490 ![](https://pic.code-nav.cn/post_picture/1608440217629360130/1B0jPqwUegFana8S.webp) ![](https://pic.code-nav.cn/post_picture/1608440217629360130/dBV0j0c5DoOajPc2.webp) #### Solar BI 📚 原帖链接:https://codefather.cn/note/1816551710832824322 ![](https://pic.code-nav.cn/post_picture/1608440217629360130/u80gX1VjnoRnjcKT.webp) ![](https://pic.code-nav.cn/post_picture/1608440217629360130/eaq5xkCFKzKWdToq.webp) #### 哥布林智能 BI 分析系统 📚 原帖链接:https://codefather.cn/note/1811050100016742402 ![](https://pic.code-nav.cn/post_picture/1608440217629360130/DUst52xI4jLeqR0K.webp) ![](https://pic.code-nav.cn/post_picture/1608440217629360130/1qgxMtMqgiZlErSQ.webp) #### PS 智能 BI 分析系统 📚 原帖链接:https://codefather.cn/note/1810278024374505474 ![](https://pic.code-nav.cn/post_picture/1608440217629360130/q3XSTASKy34lLvwV.webp) ![](https://pic.code-nav.cn/post_picture/1608440217629360130/u7F0m6WkLsgZZNbJ.webp) #### ToolVerse:一体化智能管理与创新服务平台 📚 原帖链接:https://codefather.cn/post/1810389203533025282 ![](https://pic.code-nav.cn/post_picture/1608440217629360130/1COY3JABTl4U4QnH.webp) ![](https://pic.code-nav.cn/post_picture/1608440217629360130/L3WMaLqk4YlDGuGt.webp) #### Nero 智能 BI 分析系统 📚 原帖链接:https://codefather.cn/note/1811010648561090562 ![](https://pic.code-nav.cn/post_picture/1608440217629360130/1pUUsi1shCbX1UBI.webp) ![](https://pic.code-nav.cn/post_picture/1608440217629360130/zBEAWF1Jzi1jTaL7.webp) #### GBC 智能 BI 分析系统 📚 原帖链接:https://codefather.cn/note/1808941076960215041 ![](https://pic.code-nav.cn/post_picture/1608440217629360130/UfDf4cCTNqgC5cNg.webp) ![](https://pic.code-nav.cn/post_picture/1608440217629360130/rdj71KilR4wHGE0J.webp) ### 聚合搜索平台 企业级项目教程:https://codefather.cn/course/1790979621621641217 > 视频教程 + 项目文档 + 源码 + 简历写法 + 面试题解 + 专属交流群 #### 空空-聚合搜索平台 📚 原帖链接:https://codefather.cn/note/1924150587763404802 ![](https://pic.code-nav.cn/post_picture/1608440217629360130/4ulDy7dYrCm8RSc2.webp) ![](https://pic.code-nav.cn/post_picture/1608440217629360130/3WHb8ivaA9XdanKh.webp) #### 崽崽聚合搜索平台 📚 原帖链接:https://codefather.cn/note/1817637092455018497 ![](https://pic.code-nav.cn/post_picture/1608440217629360130/8jFfL6mkHn5X2Zyc.webp) ![](https://pic.code-nav.cn/post_picture/1608440217629360130/f0cwumUlkt21a35o.webp) #### 青年聚合搜索平台 📚 原帖链接:https://codefather.cn/post/1825764402195537921 ![](https://pic.code-nav.cn/post_picture/1608440217629360130/mzAhwUz4gsEEqL9l.webp) ### API 开放平台 企业级项目教程:https://codefather.cn/course/1790979723916521474 > 视频教程 + 项目文档 + 源码 + 简历写法 + 面试题解 + 专属交流群 #### Qi-API 接口开放平台(已上线) 👉 线上地址:https://api.qimuu.icu/user/login 📚 原帖链接:https://codefather.cn/note/1805877794735435778 ![](https://pic.code-nav.cn/post_picture/1608440217629360130/skmoNw3A3gPS9PPu.webp) ![](https://pic.code-nav.cn/post_picture/1608440217629360130/QOTQxB6PAzqiVYlA.webp) #### 羊腿 API 开放平台(已上线) 👉 线上地址:http://api.panyuwen.top/user/login 📚 原帖链接:https://codefather.cn/note/1855478010485653505 ![](https://pic.code-nav.cn/post_picture/1608440217629360130/tm0BBjdGCZXKplUj.webp) ![](https://pic.code-nav.cn/post_picture/1608440217629360130/eYLL1EcTA6djKYJQ.webp) ![](https://pic.code-nav.cn/post_picture/1608440217629360130/bZtZsrS9Ueach1EF.webp) #### Cxm-API 接口开放平台 📚 原帖链接:https://codefather.cn/essay/1905452207706185730 ![](https://pic.code-nav.cn/post_picture/1608440217629360130/8iVIbRpTxfroPd7w.webp) ![](https://pic.code-nav.cn/post_picture/1608440217629360130/76Z2FYDxrD7xd0Ll.webp) ![](https://pic.code-nav.cn/post_picture/1608440217629360130/szPpxygi1YBhoYh2.webp) #### LeAPI 接口开放平台 📚 原帖链接:https://codefather.cn/note/1816551310788497409 ![](https://pic.code-nav.cn/post_picture/1608440217629360130/ukS0r3Mj03zscjBQ.webp) ![](https://pic.code-nav.cn/post_picture/1608440217629360130/C5oLhmwrafbpUpRd.webp) #### X API 开放调用平台 📚 原帖链接:https://codefather.cn/note/1816913304989921281 ![](https://pic.code-nav.cn/post_picture/1608440217629360130/LYNBTaBa7a8madB5.webp) ![](https://pic.code-nav.cn/post_picture/1608440217629360130/jwPBIlq8XEnNWVYV.webp) #### Adagio 接口开放平台 📚 原帖链接:https://codefather.cn/note/1816551835571425281 ![](https://pic.code-nav.cn/post_picture/1608440217629360130/qqLlsGva4HIdcef9.webp) ![](https://pic.code-nav.cn/post_picture/1608440217629360130/DpMaD9D8WLXgiobl.webp) #### 聚合 API 开放平台 📚 原帖链接:https://codefather.cn/note/1811049203207438337 ![](https://pic.code-nav.cn/post_picture/1608440217629360130/OATr47DXsMyAnKEi.webp) ![](https://pic.code-nav.cn/post_picture/1608440217629360130/e7E7wHhRBYIcR3iM.webp) #### EasyAPI 开放平台 📚 原帖链接:https://codefather.cn/note/1832713500474793986 ![](https://pic.code-nav.cn/post_picture/1608440217629360130/S65u4w8pSoctge36.webp) ![](https://pic.code-nav.cn/post_picture/1608440217629360130/RQM9tAarOuQSg0C6.webp) #### 洛言接口开放平台 📚 原帖链接:https://codefather.cn/post/1828449609845428226 ![](https://pic.code-nav.cn/post_picture/1608440217629360130/0z8RFcR4I29eLf47.webp) #### k 接口开放平台 📚 原帖链接:https://codefather.cn/note/1808772929765376001 ![](https://pic.code-nav.cn/post_picture/1608440217629360130/QV1dX1Lkso8cB4In.webp) #### 为-接口开放平台 📚 原帖链接:https://codefather.cn/note/1811050695448526849 ![](https://pic.code-nav.cn/post_picture/1608440217629360130/t8QRFTPlQz3Ec2eA.webp) ![](https://pic.code-nav.cn/post_picture/1608440217629360130/Go2WBa4g9LfP2diW.webp) #### API 接口服务平台 📚 原帖链接:https://codefather.cn/post/1839933551062880258 ![](https://pic.code-nav.cn/post_picture/1608440217629360130/0ziz0zrLw4Y3t6sD.webp) #### 银竹接口开放平台 📚 原帖链接:https://codefather.cn/post/1810389203533025282 ![](https://pic.code-nav.cn/post_picture/1608440217629360130/ljPrGAGf1ezhsH5d.webp) ![](https://pic.code-nav.cn/post_picture/1608440217629360130/z7qEJRXYzqv91qMO.webp) #### Insight平台 📚 原帖链接:https://codefather.cn/post/1812563734513770498 ![](https://pic.code-nav.cn/post_picture/1608440217629360130/4mkEBNrjMvvmk5j7.webp) ### 伙伴匹配系统 企业级项目教程:https://codefather.cn/course/1790950013153095682 > 视频教程 + 项目文档 + 源码 + 简历写法 + 面试题解 + 专属交流群 #### homie 伙伴匹配系统(已上线) 👉 线上地址:http://hm.hejiajun.icu/ 📚 原帖链接:https://codefather.cn/note/1823592419365535746 ![](https://pic.code-nav.cn/post_picture/1608440217629360130/wz7sPfpR2QRudAI4.webp) ![](https://pic.code-nav.cn/post_picture/1608440217629360130/ZNcYg7J5KsWHtwfg.webp) #### 花语-伙伴匹配系统(已上线) 👉 线上地址:http://huayu.hanades.online ![](https://pic.code-nav.cn/post_picture/1608440217629360130/LZqF0vfSWMw9o6bH.webp) #### 速配 SUPER - 伙伴匹配系统(已上线) 👉 线上地址:https://ochiamalu.fun/ 📚 原帖链接:https://codefather.cn/essay/1909073423851560961 ![](https://pic.code-nav.cn/post_picture/1608440217629360130/JfgYzKNGpBjQplNC.webp) ![](https://pic.code-nav.cn/post_picture/1608440217629360130/OFtqlPMWthyxI16S.webp) #### yes - 伙伴匹配系统 📚 原帖链接:https://codefather.cn/post/1811047157863481345 ![](https://pic.code-nav.cn/post_picture/1608440217629360130/GeHx8Gf32FyIG5hc.webp) ![](https://pic.code-nav.cn/post_picture/1608440217629360130/gIGQFbMoBwqj3Hq8.webp) #### 等闲 - 伙伴匹配系统 📚 原帖链接:https://codefather.cn/note/1812741780545921026 ![](https://pic.code-nav.cn/post_picture/1608440217629360130/iige3Z7yBxWbSNYG.webp) ![image-20251020230654641](https://pic.code-nav.cn/post_picture/1608440217629360130/gDl1p7yORflfUlw5.webp) #### 鱼泡 - 伙伴匹配系统 📚 原帖链接:https://codefather.cn/note/1806571986623836161 ![](https://pic.code-nav.cn/post_picture/1608440217629360130/JWOSAg0JsIAEK9tc.webp) ![](https://pic.code-nav.cn/post_picture/1608440217629360130/ITtKJ3vtVyWQXAjd.webp) #### Louis - 伙伴匹配系统 📚 原帖链接:https://codefather.cn/note/1806569725122228226 ![](https://pic.code-nav.cn/post_picture/1608440217629360130/p9q0iPxH14onUJMO.webp) #### 知秋 - 伙伴匹配系统 📚 原帖链接:https://codefather.cn/note/1816551544797106177 ![](https://pic.code-nav.cn/post_picture/1608440217629360130/HWArzTN5GaD8cIWW.webp) ![](https://pic.code-nav.cn/post_picture/1608440217629360130/2OwMcZNhF5YBqzxB.webp) #### 小虎 - 伙伴匹配系统 📚 原帖链接:https://codefather.cn/note/1814012455103463426 ![](https://pic.code-nav.cn/post_picture/1608440217629360130/th73iv6p3i5eTLlv.webp) #### 龙 - 伙伴匹配系统 📚 原帖链接:https://codefather.cn/note/1818361301050142721 ![](https://pic.code-nav.cn/post_picture/1608440217629360130/sMsbvABP1YIgHbmR.webp) ![](https://pic.code-nav.cn/post_picture/1608440217629360130/rOV6EaI9iJ1NUywM.webp) #### w - 伙伴匹配系统 📚 原帖链接:https://codefather.cn/note/1838446471885594625 ![](https://pic.code-nav.cn/post_picture/1608440217629360130/qUDiR36wTZdLZAvv.webp) ![](https://pic.code-nav.cn/post_picture/1608440217629360130/GfD0IWREJjSOiv3s.webp) #### 云交友 - 伙伴匹配系统 📚 原帖链接:https://codefather.cn/post/1825763690480869378 ![](https://pic.code-nav.cn/post_picture/1608440217629360130/i155QP4Hd3REXqcW.webp) ![](https://pic.code-nav.cn/post_picture/1608440217629360130/Q8EmIzBAwEIzKkBW.webp) #### 大学生 - 伙伴匹配系统 📚 原帖链接:https://codefather.cn/note/1820898093159849986 ![](https://pic.code-nav.cn/post_picture/1608440217629360130/qkonRv0WYWp5SOL3.webp) ![](https://pic.code-nav.cn/post_picture/1608440217629360130/0mpZQA3gfXqYsNIp.webp) #### Yz - 伙伴匹配系统 📚 原帖链接:https://codefather.cn/essay/1870039737155010561 ![](https://pic.code-nav.cn/post_picture/1608440217629360130/c6nNH57CsI9JWymR.webp) ![](https://pic.code-nav.cn/post_picture/1608440217629360130/Ig3cGnc62j1hUcZH.webp) ### 用户中心项目 企业级项目教程:https://codefather.cn/course/1790943469757837313 > 视频教程 + 项目文档 + 源码 + 简历写法 + 面试题解 + 专属交流群 #### 可乐用户中心(已上线) 👉 线上地址:http://user.xykcola.site/user/login 📚 原帖链接:https://codefather.cn/note/1811475602152685570 ![](https://pic.code-nav.cn/post_picture/1608440217629360130/4l9hF6tw4pll5gNj.webp) ![](https://pic.code-nav.cn/post_picture/1608440217629360130/bfI4YVnQ8dUr4w17.webp) #### (NextJS 版)用户中心管理 📚 原帖链接:https://codefather.cn/post/1912880102888280065 ![](https://pic.code-nav.cn/post_picture/1608440217629360130/HWBlRoRW8E1YAJIh.webp) #### chen 用户中心 📚 原帖链接:https://codefather.cn/note/1811050198691938306 ![image-20251020214956147](https://pic.code-nav.cn/post_picture/1608440217629360130/weh0N9Pk2CulRQ4O.webp) ![image-20251020214935643](https://pic.code-nav.cn/post_picture/1608440217629360130/kHC7ldXvzKSfz7mV.webp) #### Find Me 用户中心 📚 原帖链接:https://codefather.cn/note/1811342501757911041 ![image-20251020215040970](https://pic.code-nav.cn/post_picture/1608440217629360130/zZCuu5KaeJ1V92o9.webp) ![](https://pic.code-nav.cn/post_picture/1608440217629360130/34pAlY4UxMNzLN8z.webp) #### 小汉用户中心 📚 原帖链接:https://codefather.cn/note/1810181026137473026 ![](https://pic.code-nav.cn/post_picture/1608440217629360130/4DePWBnUfA1bodr2.webp) ![](https://pic.code-nav.cn/post_picture/1608440217629360130/tzqvnIo0vbcTYhHR.webp) #### 用户中心 📚 原帖链接:https://codefather.cn/post/1925206864866131970 ![](https://pic.code-nav.cn/post_picture/1608440217629360130/Jxj9MAgBYXAosMpW.webp) ![](https://pic.code-nav.cn/post_picture/1608440217629360130/d08kmg9hwDzDFZIB.webp) #### 吃鱼用户中心 📚 原帖链接:https://codefather.cn/post/1896897093685288961 ![](https://pic.code-nav.cn/post_picture/1608440217629360130/3fdpBFu9VKN5Yq2t.webp) 📚 原帖链接:https://codefather.cn/note/1811049194567172097 ![](https://pic.code-nav.cn/post_picture/1608440217629360130/kveqp7kQywUObLeq.webp) #### 崽崽用户中心 📚 原帖链接:https://codefather.cn/note/1817637102550708225 ![](https://pic.code-nav.cn/post_picture/1608440217629360130/o1DPyQe5mZPhQBhY.webp) ![](https://pic.code-nav.cn/post_picture/1608440217629360130/iDNFFWf3rkjFoOJm.webp) #### Jiaxiong 用户中心 📚 原帖链接:https://codefather.cn/note/1815828758460895233 ![](https://pic.code-nav.cn/post_picture/1608440217629360130/68N1VfScVFL95sMy.webp) ![](https://pic.code-nav.cn/post_picture/1608440217629360130/2BXvytZ5Ob4Uc77x.webp)

可有一起做只能Bi项目的

有吗

广州小厂(实习)BI项目面筋

# 几乎不拷打八股文,一直扣项目 - 笔试 - 面试 ## 笔试问题: 笔试涵盖 springboot,mysql,vue,js,系统优化,线上问题排查 ## 面试问题: - 介绍项目实现哪些功能 - 项目分为哪些用户端 - 在功能上项目还有哪些优化点(一直想从技术的角度,从性能的角度去回答这个问题,但是面试官认为更想要听到的是功能方面的添加,优化) - 一个大屏存放图表,如何设计数据库(随之拓展到数据库的多对多设计,这里前面没有问好具体场景,导致逻辑错乱回答的不是很好,索性后面是根据面试官的提示全回答出来了。) - 分表的意义,为什么分表 - 分表具体的实现思路 - 一个Excel 文档一个100个字段,一个 10 字段如何去实现分分表 - 表格有1万个字段 你如何去拼接prompt发送给大模型。(说了前后端校验大表的提交,还能拆分多个表来生成图表) - 讲了图表生成逻辑,反问还有哪些工作量 (还说到调 api 没有太大意义) - 除了调用大模型,还有哪些工作量 - 为什么做这个项目 - 还做过哪些管理类的系统 感觉面试官拷打项目更多的是注重我的工作量我的CRUD功能,而对我这个项目的性能提升,技术优化毫不care。 也是从公司的业务层面出发问问题,因为公司作为乙方对接的是学校管理系统主要更多的是要会一些 crud 操作,而对性能和技术的要求并发的控制都没有太大的兴趣。 ## 个人总结 准备了几天的八股文,到后面发现小厂实习都是只问比较表层的,常见的八股,甚至不问八股,之间拷打项目。 个人还是对项目没有更深入的思考,也得复盘回答不出来的问题

MQ总结 v1.0

1. 什么时候用? --------- 场景: 1. 系统处理高耗时 需要异步操作 2. 解决原来的线程池丢消息的情况 2. 使用 ----- ### 2.1. 开始 1. 安装mq 2. 安装mq管理UI 初始账户密码为guest 3. 导入依赖 编写demo 测试 1. 创建工厂 建立连接 2. 创建信道 用信道声明队列 声明交换机 绑定交换机等操作 **生产者消费者在声明队列时需参数保持一致** ### 2.2. 交换机 exchange 介绍: 如计网里面的交换机根据MAC地址的转发数据帧的效果类似 不同的交换机可以根据不同的规则转发消息 分类: fanout; direct; topic; #### 2.2.1. fanout 介绍: '**扇出' 交换机 名如其机 感觉一下子消息全部扇到了脸上一样** **特点: 就像计网的广播帧 转发给所有信道 每个都可以收到消息** <img src="https://pic.code-nav.cn/post_picture/1608648175386624001/NzeNmC83YC1YyAlo.png" alt="" width="433px" /> 使用:[https://www.rabbitmq.com/tutorials/tutorial-three-java](https://www.rabbitmq.com/tutorials/tutorial-three-java) 生产者信道在声明交换机时 就声明fanout 所有绑定该交换机的消费者信道都会受到消息 #### 2.2.2. direct 介绍: 直连交换机 可以根据路由key 直接发送消息 单绑定: ![](https://pic.code-nav.cn/post_picture/1608648175386624001/GkTUegVS7t0eFLRb.png) 多绑定: ![](https://pic.code-nav.cn/post_picture/1608648175386624001/1j0YVoFmDpSCtZsi.png) 使用:[https://www.rabbitmq.com/tutorials/tutorial-four-java](https://www.rabbitmq.com/tutorials/tutorial-four-java) 在声明队列时 声明路由参数 那么创建的交换机在转发消息时就会根据路由key发送到相应的队列 可以应用的场景: **根据不同的路由 发送不同的日志消息到消费者** ![](https://pic.code-nav.cn/post_picture/1608648175386624001/cbwrhZ2A3nCqnM1f.webp) #### 2.2.3. topic 介紹: topic 交换机是用根据规则来模糊匹配路由key的交换机 ![](https://pic.code-nav.cn/post_picture/1608648175386624001/oFQYsdi2nQvWTKqu.png) <img src="https://pic.code-nav.cn/post_picture/1608648175386624001/6Ybx07r1yq7U2ImB.png" alt="" width="625px" /> *是用来匹配一个词 #是用来匹配0个或者多个词 **举例:** ![](https://pic.code-nav.cn/post_picture/1608648175386624001/jgrLSLWrf1M7n16w.png)Q1: *.orange.* Q2: *.*.rabbith 和`lazy.#`. 那么 一下方式 quick.orange.rabbit 匹配Q1 Q2 lazy.orange.elephant 匹配Q1 Q2 quick.orange.fox 匹配Q1 lazy.brown.fox 匹配Q2 lazy.pink.rabbit 匹配Q2 只匹配一次 使用 [https://www.rabbitmq.com/tutorials/tutorial-five-java](https://www.rabbitmq.com/tutorials/tutorial-five-java) ### 2.3. 发布确认机制 Publish Confirm #### 2.3.1. 设置消息确认的到期时间 ```java while (thereAreMessagesToPublish()) { byte[] body = ...; BasicProperties properties = ...; channel.basicPublish(exchange, queue, properties, body); // uses a 5 second timeout channel.waitForConfirmsOrDie(5_000); } ``` 在规定的到期时间内 未确认消息 或者该消息被拒绝 就会抛出一个异常 处理该异常通常是 重发或者是打印错误日志 **优点: 简单** **缺点: 当确认消息阻塞发布消息时 发布效率就会很低 不适合高吞吐量的系统** #### 2.3.2. 批量发布 ```java // 设置批次数量大小 int batchSize = 100; int outstandingMessageCount = 0; while (thereAreMessagesToPublish()) { byte[] body = ...; BasicProperties properties = ...; channel.basicPublish(exchange, queue, properties, body); outstandingMessageCount++; // 发送数量没到就不设置过期时间 if (outstandingMessageCount == batchSize) { channel.waitForConfirmsOrDie(5_000); outstandingMessageCount = 0; } } // 发送完了 发现都还没到数量 就直接设置过期时间 if (outstandingMessageCount > 0) { channel.waitForConfirmsOrDie(5_000); } ``` 优点: 相比于接受单独的确认 效率好很多 缺点: 出问题不知道是哪个出了错 #### 2.3.3. 手动确认 ### 2.4. 死信 死信: 1. 被拒绝的消息 2. 过期的消息 3. 被删除的消息 当定义了死信交换机和死信队列 这些消息就会由该交换机转发到死信队列 #### 2.4.1. 死信交换机 设置: [https://www.rabbitmq.com/docs/dlx](https://www.rabbitmq.com/docs/dlx) ```java channel.exchangeDeclare("some.exchange.name", "direct"); // Important: prefer using policies over hardcoded x-arguments Map<String, Object> args = new HashMap<String, Object>(); //绑定个死信交换机 args.put("x-dead-letter-exchange", "some.exchange.name"); //给绑定的死信交换机 指明它所要转发死信到哪条死信队列上去 args.put("x-dead-letter-routing-key", "some-routing-key"); //此时 该队列就绑定了死信交换机 channel.queueDeclare("myqueue", false, false, false, args); ``` 死信交换机就像direct交换机一样 正常创建就好 #### 2.4.2. 示例 ![](https://pic.code-nav.cn/post_picture/1608648175386624001/sdjGWd3odiaOf9IZ.webp) 创建死信的direct交换机和队列 ```java public class DieLetterWorker { private final static String QUEUE_NAME1 = "m3"; private final static String QUEUE_NAME2 = "m4"; public static void main(String[] argv) throws Exception { ConnectionFactory factory = new ConnectionFactory(); factory.setHost("localhost"); Connection connection = factory.newConnection(); Channel channel1 = connection.createChannel(); Channel channel2 = connection.createChannel(); //声明死信交换机 channel1.exchangeDeclare("e2", "direct"); //声明死信队列 channel1.queueDeclare(QUEUE_NAME1, true, false, false, null); //绑定队列到交换机 channel1.queueBind(QUEUE_NAME1, "e2", "worker1"); //声明交换机 同上 channel2.exchangeDeclare("e2", "direct"); channel2.queueDeclare(QUEUE_NAME2, true, false, false, null); channel2.queueBind(QUEUE_NAME2, "e2", "worker2"); System.out.println(" [*] Waiting for messages. To exit press CTRL+C"); DeliverCallback deliverCallback1 = (consumerTag, delivery) -> { try { String message = new String(delivery.getBody(), StandardCharsets.UTF_8); System.out.println(" [x]worker1 Received1 '" + message + "'"); //模拟消息处理 Thread.sleep(1000); //如果消息处理失败,将消息发送到死信队列 } catch (InterruptedException e) { throw new RuntimeException(e); } }; DeliverCallback deliverCallback2 = (consumerTag, delivery) -> { try { String message = new String(delivery.getBody(), StandardCharsets.UTF_8); System.out.println(" [x]worker2 Received1 '" + message + "'"); //模拟消息处理 Thread.sleep(1000); } catch (InterruptedException e) { throw new RuntimeException(e); } }; channel1.basicConsume(QUEUE_NAME1, true, deliverCallback1, consumerTag -> { }); channel2.basicConsume(QUEUE_NAME2, true, deliverCallback2, consumerTag -> { }); } } ``` 生产者 ```java public class DieLetterProvider { private final static String QUEUE_NAME1 = "hello1"; public static void main(String[] argv) throws Exception { ConnectionFactory factory = new ConnectionFactory(); factory.setHost("localhost"); try (Connection connection = factory.newConnection(); //创建两个消息队列 Channel channel = connection.createChannel(); ) { //声明交换机 channel.exchangeDeclare("e1", "direct"); //手动输入 Scanner scanner = new Scanner(System.in); while (scanner.hasNextLine()) { String message = scanner.nextLine(); String[] strs = message.split(" "); //将消息发送到交换机 不指定路由 channel.basicPublish("e1", strs[1], null, message.getBytes(StandardCharsets.UTF_8)); System.out.println(" [x] Sent '" + message + "'"); } } } } ``` 消费者 ```java public class DieLetterConsumer { private final static String QUEUE_NAME1 = "m1"; private final static String QUEUE_NAME2 = "m2"; public static void main(String[] argv) throws Exception { ConnectionFactory factory = new ConnectionFactory(); factory.setHost("localhost"); Connection connection = factory.newConnection(); Channel channel1 = connection.createChannel(); Channel channel2 = connection.createChannel(); //声明死信交换机 channel1.exchangeDeclare("e2", "direct"); channel2.exchangeDeclare("e2", "direct"); //声明死信队列参数 HashMap<String, Object> args1 = new HashMap<>(); args1.put("x-dead-letter-exchange", "e2"); args1.put("x-dead-letter-routing-key", "worker1"); HashMap<String, Object> args2 = new HashMap<>(); args2.put("x-dead-letter-exchange", "e2"); args2.put("x-dead-letter-routing-key", "worker2"); //声明交换机 channel1.exchangeDeclare("e1", "direct"); //声明队列 channel1.queueDeclare(QUEUE_NAME1, true, false, false, args1); //绑定队列到交换机 channel1.queueBind(QUEUE_NAME1, "e1", "exc1"); //声明交换机 同上 channel2.exchangeDeclare("e1", "direct"); channel2.queueDeclare(QUEUE_NAME2, true, false, false, args2); channel2.queueBind(QUEUE_NAME2, "e1", "exc2"); System.out.println(" [*] Waiting for messages. To exit press CTRL+C"); DeliverCallback deliverCallback1 = (consumerTag, delivery) -> { try { String message = new String(delivery.getBody(), StandardCharsets.UTF_8); if (message.equals("hello exc1")) { throw new InterruptedException("hello exc1"); } System.out.println(" [x]exc1 Received1 '" + message + "'"); //模拟消息处理 Thread.sleep(1000); //如果消息处理失败,将消息发送到死信队列 //确认消息 channel1.basicAck(delivery.getEnvelope().getDeliveryTag(), false); } catch (InterruptedException e) { //如果消息处理失败,将消息发送到死信队列 channel1.basicNack(delivery.getEnvelope().getDeliveryTag(), false, false); } }; DeliverCallback deliverCallback2 = (consumerTag, delivery) -> { try { String message = new String(delivery.getBody(), StandardCharsets.UTF_8); if (message.equals("hello exc2")) { throw new InterruptedException("hello exc2"); } System.out.println(" [x]exc2 Received1 '" + message + "'"); //模拟消息处理 Thread.sleep(1000); //确认消息 channel2.basicAck(delivery.getEnvelope().getDeliveryTag(), false); } catch (InterruptedException e) { //如果消息处理失败,将消息发送到死信队列 channel2.basicNack(delivery.getEnvelope().getDeliveryTag(), false, false); } }; channel1.basicConsume(QUEUE_NAME1, false, deliverCallback1, consumerTag -> { }); channel2.basicConsume(QUEUE_NAME2, false, deliverCallback2, consumerTag -> { }); } } ```

收获: 1. 实践了MQ 基本学习了MQ 2. 服务器部署项目 (以前都是虚拟机) nginx配置再学习 https://blog.csdn.net/sheep_fur/article/details/148034942?spm=1011.2415.3001.5331 3. 文件上传要验证 ! 4. 学习了promt的优化技巧 5. 项目开始用git进行版本控制 6. 异步+线程池实践 7. 限流算法 踩坑: 1. 云服务器端口问题: 外网访问不了端口 访问不了 一定一定要看看是不是服务器端口没有打开 2. 消息队列要先创建 queue才能启动 皮总的一次性创建队列的任务由于服务器没队列 项目启动不了好像直接跑不了 可以先登录UI管理平台 创建队列和交换机 再跑就能跑通了 3. 踩坑 nginx 访问路由项目 路由跳转访问不了问题 4. 星火的智能体不好用哈哈哈哈哈哈 百度的还行 硅基流动 好东西

做了下智能BI平台,发现大模型只能处理少量的数据,数据量就加到三百大模型输出就报异常了。这部分有没有可以优化的方法呢

图库+智能bi的笔记

# 智能BI ## esayExcel 引入easyExcel来读取Excel文件 依赖 ```xml <!-- easy Excel --> <dependency> <groupId>com.alibaba</groupId> <artifactId>easyexcel</artifactId> <version>3.1.0</version> </dependency> ``` 配置实体类 ```java @Data @EqualsAndHashCode public class IndexOrNameData { /* * 用名字去匹配,这里需要注意,如果名字重复,会导致只有一个字段读取到数据 * @ExcelProperty("成员") 默认读的就是第一列的名字 */ @ExcelProperty("成员") private String name; @ExcelProperty("班级") private String aaa; } ``` 配置项,拦截器,在发生前和完成后做点什么 ```java @Slf4j public class DemoDataListener implements ReadListener<IndexOrNameData> { /** * 每读一条数据都会调用这个方法 * @param data * @param context */ @Override public void invoke(IndexOrNameData data, AnalysisContext context) { // data就是所有数据 System.out.println(data); } /** * 所有数据解析完成了 都会来调用 * * @param context */ @Override public void doAfterAllAnalysed(AnalysisContext context) { // 这里也要保存数据,确保最后遗留的数据也存储到数据库 // saveData(); log.info("所有数据解析完成!"); } } ``` 最后是就是调用了,2种方式同步和异步,同步适合数据量小,异步适合数据量大 ```java public class importexcel { public static void main(String[] args) { // since: 3.0.0-beta1 String fileName ="D:\\code\\Intelligent-BI\\Intelligent-BI-backend\\src\\main\\resources\\test1.xlsx"; // 这里默认每次会读取100条数据 然后返回过来 直接调用使用数据就行 // 具体需要返回多少行可以在`PageReadListener`的构造函数设置 readByListener(fileName); } // 同步读取 public static void readByListener(String fileName) { EasyExcel.read(fileName, IndexOrNameData.class,new DemoDataListener()).sheet().doRead(); } // 异步读取 public static void readSyncByListener(String fileName) throws FileNotFoundException { File file = ResourceUtils.getFile("classpath:test1.xlsx"); List<Map<Integer,String>> list = EasyExcel.read(file).excelType(ExcelTypeEnum.XLSX).sheet().headRowNumber(0).doReadSync(); System.out.println(list); } } ``` 异步调用并将excel从xlsx转为csv格式(推荐,而且不需要实体类,拦截器看你需求,反正我是没加) ```java public static String excelToCsv(MultipartFile multipartFile) { // 读取数据 List<Map<Integer, String>> list = null; try { // headRowNumber(0)表示表头在第一行 list = EasyExcel.read(multipartFile.getInputStream()) .excelType(ExcelTypeEnum.XLSX) .sheet() .headRowNumber(0) .doReadSync(); } catch (IOException e) { log.error("表格处理错误", e); } if (CollUtil.isEmpty(list)) { return ""; } // 转换为 csv StringBuilder stringBuilder = new StringBuilder(); // 读取表头(表头第一行) LinkedHashMap<Integer, String> headerMap = (LinkedHashMap) list.get(0); List<String> headerList = headerMap.values().stream().filter(ObjectUtils::isNotEmpty).collect(Collectors.toList()); stringBuilder.append(StringUtils.join(headerList, ",")).append("\n"); // 读取数据 for (int i = 1; i < list.size(); i++) { LinkedHashMap<Integer, String> dataMap = (LinkedHashMap) list.get(i); List<String> dataList = dataMap.values().stream().filter(ObjectUtils::isNotEmpty).collect(Collectors.toList()); stringBuilder.append(StringUtils.join(dataList, ",")).append("\n"); } return stringBuilder.toString(); } ``` ```java EasyExcel.read(multipartFile.getInputStream()). excelType(ExcelTypeEnum.XLSX).sheet().headRowNumber(0).doReadSync(); ``` 这行代码会读取Excel文件的全部数据,包括表头和数据 分析: 1. 这段代码将读取到的数据转换为CSV格式。首先,读取表头并过滤掉空值,然后读取每一行的数据并过滤掉空值,最后将表头和数据拼接成CSV格式的字符串。 ### 可能的问题 1. **Excel文件格式**:确保上传的文件是`.xlsx`格式,否则`excelType(ExcelTypeEnum.XLSX)`可能会出错。 2. **表头和数据行数**:确保Excel文件至少有两行,第一行是表头,第二行开始是数据。 3. **数据类型**:确保Excel中的数据类型与`Map<Integer, String>`匹配,否则可能会出现类型转换错误。 ### 拿到的结果 ```json 分析目标:12 我的数据:成员,班级 徐瑜蔚,网络232 罗奕恒,物联网221 林鑫桐,移动223 陈炫东,移动223 蓝増鑫,物联网222 郑柳静,大数据221 潘瑞鸿,广告222 符城,移动221 谭思敏,物联网23 卢子坤,移动231 黄宇然,移动232 马朝凯,大数据232 扶政丰,大数据232 钟楚丹,企管233 沈丽玲,会计232 ``` 对了上面的代码用到了hutool工具库的方法,不要忘了引入 ```xml <dependency> <groupId>cn.hutool</groupId> <artifactId>hutool-all</artifactId> <version>5.8.8</version> </dependency> ``` ## AI调用 ### 引入SDK ```xml <dependency> <groupId>cn.bigmodel.openapi</groupId> <artifactId>oapi-java-sdk</artifactId> <version>release-V4-2.3.0</version> </dependency> ``` 在yml文件定义apiKey(自己封装) ```yaml ai: api-key: ... ``` apikey封装配置类 ```java @Configuration @ConfigurationProperties(prefix = "ai") @Data public class AiConfig { private String apiKey; @Bean public ClientV4 clientV4(){ return new ClientV4.Builder(apiKey).build(); } } ``` prompt ```tex 你是一个数据分析师和前端开发专家,接下来我会按照以下固定格式给你提供内容: 分析需求: {数据分析的需求或者目标} 原始数据: {csv格式的原始数据,用,作为分隔符} 请根据这两部分内容,按照以下指定格式生成内容(此外不要输出任何多余的开头、结尾、注释) 【【【【【 {前端可视化工具 Echart 的 option 配置对象js代码,合理地将数据进行可视化,不要生成任何多余的内容,比如注释} 【【【【【 {明确的数据分析结论、越详细越好,不要生成多余的注释} 【【【【【 以下是示例代码 【【【【【 ({ type: "view", autoFit: true, data: [ { year: "1991", value: 3 }, { year: "1992", value: 4 }, { year: "1993", value: 3.5 }, { year: "1994", value: 5 }, { year: "1995", value: 4.9 }, { year: "1996", value: 6 }, { year: "1997", value: 7 }, { year: "1998", value: 9 }, { year: "1999", value: 13 }, ], encode: { x: "year", y: "value" }, scale: { x: { range: [0, 1] }, y: { domainMin: 0, nice: true } }, children: [ { type: "line", labels: [{ text: "value", style: { dx: -10, dy: -12 } }] }, { type: "point", style: { fill: "white" }, tooltip: false }, ], }); 【【【【【 根据数据分析可得,该网站用户数量逐日增长,时间越长,用户数量增长越多。 ``` 新版promet ```tex 你是一个数据分析师和前端开发专家,接下来我会按照以下固定格式给你提供内容: 分析需求: {数据分析的需求或者目标} 原始数据: {csv格式的原始数据,用,作为分隔符} 请根据这两部分内容,严格按照以下指定格式生成内容(此外不要输出任何多余的开头、结尾、注释)同时不要使用这个符号 '】' '【【【【【' {前端 ANTV的G2的v5.2.2版本 的 option 配置对象 JSON 代码, 不要生成任何多余的内容,比如注释和代码块标记} '【【【【【' {明确的数据分析结论、越详细越好,不要生成多余的注释} 下面是一个具体的例子的模板: '【【【【【' JSON格式代码 '【【【【【' 结论: ``` ## 前端封装调用后端接口(axios) 这里在调用的时候遇到一个问题就是,axios不能单纯的传文件,必须修改一些属性 接口,这里接口的请求头要改 ```js export const genChartByAi = (data) => request.post('/chart/gen', data, { headers: { 'Content-Type': 'multipart/form-data', }, }) ``` 调用接口,可以看到我们这里如果要传文件和其他变量的话,就一定要new一个formdata,然后通过append来传参,这样后端就接收到所有参数了 ```js const formState = ref({ file: [], goal: '', ChartName: '', ChartType: '', }) const onFinish = async () => { console.log('formState:', formState.value) // 传入的第一个文件 const file = formState.value.file[0].originFileObj const formData = new FormData() formData.append('file', file) formData.append('goal', formState.value.goal) formData.append('chartName', formState.value.ChartName) formData.append('chartType', formState.value.ChartType) console.log(formData) const res = await genChartByAi(formData) console.log(res) } ``` ## 封装后端传过来的图表数据 ```js //获取完数据直接解构并存pinia里面 const res = await genChartByAi(formData).catch((err) => { console.log(err) loading.value = false }) console.log(res) if (res?.code == 0) { message.success('提交成功') // result.value = res.data.aiGenResult chartStore.SET_AIGENRESULT(res.data.aiGenResult) result.value = res.data.aiGenResult chartStore.SET_AIGENCHART(JSON.parse(res.data.aiGenChart)) console.log('结论数据', res.data.aiGenResult) console.log('pinia的结论数据', chartStore.AIGENRESULT) } else { message.error('提交失败') } loading.value = false } ``` pinia ```js import { reactive, ref } from 'vue' import { defineStore } from 'pinia' export const useChartStore = defineStore('chart', () => { const AIGENRESULT = ref('') const AIGENCHART = ref({}) const SET_AIGENRESULT = (data) => { AIGENRESULT.value = data } const SET_AIGENCHART = (data) => { AIGENCHART.value = data console.log('AIGENCHART', data) } return { AIGENRESULT, SET_AIGENRESULT, AIGENCHART, SET_AIGENCHART, } }) ``` ## VUE3里加如果没有匹配的路由就自动跳转到404 在路由里面加上这两个路由 ```js // 404页面(1级) { path: '/404', component: () => import('@/components/error.vue'), hidden: true }, //这个*匹配必须放在最后,将改路由配置放到所有路由的配置信息的最后,否则会其他路由path匹配造成影响。 { path: '/:catchAll(.*)', // 不识别的path自动匹配404 redirect: '/404', }, ``` ## 强制关闭端口 ```powershell PS C:\Users\HYR> netstat -nao | findstr "8081" TCP 0.0.0.0:8081 0.0.0.0:0 LISTENING 50696 TCP [::]:8081 [::]:0 LISTENING 50696 PS C:\Users\HYR> taskkill -pid 50696 -f ``` ##VUE登录权限,获取用户信息 ## Pinia响应式丢失问题 storeToRefs+数组解构能很好的解决问题 ```js const { AIGENRESULT: result } = storeToRefs(chartStore) ``` # 性能系统优化 限制文件大小 消息队列 加锁 线程池 ## 校验文件 ```java // 校验文件 long size = multipartFile.getSize(); String filename = multipartFile.getOriginalFilename(); final long ONE_SIZE = 1024 * 1024 ; // 文件不能超过1M if (size > ONE_SIZE){ throw new BesinessException(ErrorCode.PARAMS_ERROR, "文件过大"); } // 校验文件后缀 String validFileSuffixList = FileUtil.getSuffix(filename); // 文件白名单(允许用的文件类型) List<String> list = Arrays.asList("xls", "xlsx","csv"); if(!list.contains(validFileSuffixList)){ throw new BesinessException(ErrorCode.PARAMS_ERROR, "文件类型错误"); } ``` ## 限流 ### 分布式限流 这里把以后要部署多台服务器的情况也考虑进去,分布式也是适用单机的。 ####Redission限流实现 Redission内置了一个限流工具类,可以帮助你理由redis来统计 1.使用redisson(一个操作redis的工具库) ```xml <dependency> <groupId>org.redisson</groupId> <artifactId>redisson</artifactId> <version>3.40.0</version> </dependency> ``` Redission配置类 ```java @Configuration @ConfigurationProperties(prefix = "spring.redis") @Data public class RedissionConfig { private Integer port; private String host; private Integer database; @Bean public RedissonClient getRedissonClient(){ Config config = new Config(); // 这里由于我们现在还是单机,所以使用单机配置useSingleServer,集群多机就要useClusterServers config.useSingleServer().setDatabase(1).setAddress("redis://"+host+":"+port); // or read config from file // config = Config.fromYAML(new File("config-file.yaml")); // Sync and Async API RedissonClient redisson = Redisson.create(config); return redisson; } } ``` Redission速率限制器的工具类 ```java @Service public class RedisLimiterManager { @Resource private RedissonClient redissonClient; public void doRateLimiter(String key) { RRateLimiter limiter = redissonClient.getRateLimiter(key); // Initialization required only once. // 5 permits per 2 seconds //1秒2次 limiter.trySetRate(RateType.OVERALL, 2, 1, RateIntervalUnit.SECONDS); // acquire 3 permits or block until they became available boolean canOP = limiter.tryAcquire(1); if (!canOP){ throw new BesinessException(ErrorCode.TOO_MANY_REQUEST); } } } ``` controller调用 ```java // 限流判断,每个用户一个限流器 redisLimiterManager.doRateLimiter("genChartByAI"+String.valueOf(userId)); ``` 这段代码的用途是限制对某个资源的访问频率,防止资源被过度使用。例如,可以用于限制API的调用频率,防止DDoS攻击,或者限制数据库查询的频率 ## 异步化 这里先思考一个问题,加入你把这个项目单机部署在一台服务器上,此时有多个的用户同时调用AI生成接口,而1台服务器只能同时处理一个请求,ai的生成调用又需要较长时间,如果同时抛出2个接口,会导致接口压力过大,更甚直接崩(高并发)。那么怎么去处理这个问题呢? 解决:加一个中间件来处理这些用户的消息,给用户的请求排队(任务队列),上个请求处理完了,才可以开始处理下个请求,如果处理的压力过大就直接给拒绝掉,如果还有余力的话,就给他排队调用 这个中间件就是线程池 ### 线程池 说白了,以上说的各类需求都是由于Ai服务加载较慢,我们需要给他排队列好需求防止崩掉 #### 代码实现 编写线程池配置类 ```java @Configuration public class ThreadPoolExecutorConfig { @Bean public ThreadPoolExecutor threadPoolExecutor(){ ThreadFactory threadFactory=new ThreadFactory() { private int count=1; @Override public Thread newThread(@NotNull Runnable r) { Thread thread=new Thread(r); thread.setName("线程"+count); return thread; } }; //下面这行代码分别是线程池默认2个,大于2个可以放到任务队列里面,任务队列默认4个,但是如果任务多(超过任务队列4个了),全部线程有了6个之后,可以将线程池提到4个,线程持续时间100秒,任务队列默认4个,也就是说这里面可以执行的4个加上排队的4个,一共最多8个,超过8个再加报错 ThreadPoolExecutor threadPoolExecutor=new ThreadPoolExecutor(2,4,100, TimeUnit.SECONDS,new ArrayBlockingQueue<>(4),threadFactory); return threadPoolExecutor; } } ``` 编写接口类测试 ```java @RestController @RequestMapping("/queue") @Slf4j public class QueueController { @Resource // 自动注入一个线程池的实例 private ThreadPoolExecutor threadPoolExecutor; @GetMapping("/add") // 接收一个参数name,然后将任务添加到线程池中 public void add(String name) { // 使用CompletableFuture运行一个异步任务 CompletableFuture.runAsync(() -> { // 打印一条日志信息,包括任务名称和执行线程的名称 log.info("任务执行中:" + name + ",执行人:" + Thread.currentThread().getName()); try { // 让线程休眠10分钟,模拟长时间运行的任务 Thread.sleep(600000); } catch (InterruptedException e) { e.printStackTrace(); } // 异步任务在threadPoolExecutor中执行 }, threadPoolExecutor); } @GetMapping("/get") // 该方法返回线程池的状态信息 public String get() { // 创建一个HashMap存储线程池的状态信息 Map<String, Object> map = new HashMap<>(); // 获取线程池的队列长度 int size = threadPoolExecutor.getQueue().size(); // 将队列长度放入map中 map.put("队列长度", size); // 获取线程池已接收的任务总数 long taskCount = threadPoolExecutor.getTaskCount(); // 将任务总数放入map中 map.put("任务总数", taskCount); // 获取线程池已完成的任务数 long completedTaskCount = threadPoolExecutor.getCompletedTaskCount(); // 将已完成的任务数放入map中 map.put("已完成任务数", completedTaskCount); // 获取线程池中正在执行任务的线程数 int activeCount = threadPoolExecutor.getActiveCount(); // 将正在工作的线程数放入map中 map.put("正在工作的线程数", activeCount); // 将map转换为JSON字符串并返回 return JSONUtil.toJsonStr(map); } } ``` ### RabbitMQ 首先去官网下载安装,按照提示 然后去cmd开启配置 ```powershell rabbitmq-plugins.bat enable rabbitmq_management ``` 然后就可以访问打开ui界面 ```net http://localhost:15672 ``` 如果不行的话重启一下服务 ```powershell # 关闭服务 net stop rabbitmq # 开启服务 net start rabbitmq ``` 账号密码默认都是guest 本地能运行起来就可以在代码层面实现了 #### 代码实现 producer代码(发出"消息") ```java public class SingProducer { private final static String QUEUE_NAME = "hello"; public static void main(String[] argv) throws Exception { ConnectionFactory factory = new ConnectionFactory(); factory.setHost("localhost"); try (Connection connection = factory.newConnection(); Channel channel = connection.createChannel()) { channel.queueDeclare(QUEUE_NAME, false, false, false, null); String message = "Hello World!"; channel.basicPublish("", QUEUE_NAME, null, message.getBytes(StandardCharsets.UTF_8)); System.out.println(" [x] Sent '" + message + "'"); } } } ``` consumer代码(接收消息) ```java import com.rabbitmq.client.Channel; import com.rabbitmq.client.Connection; import com.rabbitmq.client.ConnectionFactory; import com.rabbitmq.client.DeliverCallback; import java.nio.charset.StandardCharsets; public class Recv { private final static String QUEUE_NAME = "hello"; public static void main(String[] argv) throws Exception { ConnectionFactory factory = new ConnectionFactory(); factory.setHost("localhost"); Connection connection = factory.newConnection(); Channel channel = connection.createChannel(); channel.queueDeclare(QUEUE_NAME, false, false, false, null); System.out.println(" [*] Waiting for messages. To exit press CTRL+C"); DeliverCallback deliverCallback = (consumerTag, delivery) -> { String message = new String(delivery.getBody(), StandardCharsets.UTF_8); System.out.println(" [x] Received '" + message + "'"); }; channel.basicConsume(QUEUE_NAME, true, deliverCallback, consumerTag -> { }); } } ``` 注意:同名称的消息队列只能用同样的参数调用一次 这样就实现了一次消息的发生和接收了 #### 交换机 交换机的部分真听不懂,可能是还没到需要的业务场景,这里留个空,以后补 ## 前端精度丢失问题 解决:在后端加一个配置类 ```java /** * Spring MVC Json 配置 */ @JsonComponent public class JsonConfig { /** * 添加 Long 转 json 精度丢失的配置 */ @Bean public ObjectMapper jacksonObjectMapper(Jackson2ObjectMapperBuilder builder) { ObjectMapper objectMapper = builder.createXmlMapper(false).build(); SimpleModule module = new SimpleModule(); module.addSerializer(Long.class, ToStringSerializer.instance); module.addSerializer(Long.TYPE, ToStringSerializer.instance); objectMapper.registerModule(module); return objectMapper; } } ``` ```html <template #bodyCell="{ column, record }"> <template v-if="column.dataIndex === 'userAvatar'"> <a-image :src="record.userAvatar" :width="120" /> </template> <template v-else-if="column.dataIndex === 'userRole'"> <div v-if="record.userRole === 'admin'"> <a-tag color="green">管理员</a-tag> </div> <div v-else> <a-tag color="blue">普通用户</a-tag> </div> </template> <template v-else-if="column.dataIndex === 'createTime'"> {{ dayjs(record.createTime).format('YYYY-MM-DD HH:mm:ss') }} </template> <template v-else-if="column.key === 'action'"> <a-button danger>删除</a-button> </template> </template> ``` # 图片功能开发 ## 全局跨域配置类 ```java @Configuration public class CorsConfig implements WebMvcConfigurer { @Override public void addCorsMappings(CorsRegistry registry) { // 覆盖所有请求 registry.addMapping("/**") // 允许发送 Cookie .allowCredentials(true) // 放行哪些域名(必须用 patterns,否则 * 会和 allowCredentials 冲突) .allowedOriginPatterns("*") .allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS") .allowedHeaders("*") .exposedHeaders("*"); } } ``` ## 前端引入openapi ```powershell npm i --save-dev @umijs/openapi ``` 前端配置类 ```js import { generateService } from '@umijs/openapi' generateService({ requestLibPath: "import request from '@/utils/myaxios'", schemaPath: 'http://localhost:8081/api/v3/api-docs', serversPath: './src', }) ``` 在package.json加上 ```js "openapi": "node openapi.config.js", ``` 这样用命令行就可以用了(注:使用会在src包下生成api文件,如果原来有的话会覆盖掉) ```js npm run openapi ``` ```java public interface UserConstant { /** * 用户登录态键 */ String USER_LOGIN_STATE = "user_login"; // region 权限 /** * 默认角色 */ String DEFAULT_ROLE = "user"; /** * 管理员角色 */ String ADMIN_ROLE = "admin"; // endregion } ``` ## AOP切面编程,接口权限注解 给接口上权限注解,规定只有管理员才能用 依赖 ```xml <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-aop</artifactId> </dependency> ``` 编写注解类 ```java @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface AuthCheck { /** * 必须有某个角色 */ String mustRole() default ""; } ``` 权限注册切面(回去沉淀吧) ```java @Aspect @Component public class AuthInterceptor { @Resource private UserService userService; /** * 执行拦截 * * @param joinPoint 切入点 * @param authCheck 权限校验注解 */ @Around("@annotation(authCheck)") public Object doInterceptor(ProceedingJoinPoint joinPoint, AuthCheck authCheck) throws Throwable { String mustRole = authCheck.mustRole(); RequestAttributes requestAttributes = RequestContextHolder.currentRequestAttributes(); HttpServletRequest request = ((ServletRequestAttributes) requestAttributes).getRequest(); // 当前登录用户 User loginUser = userService.getLoginUser(request); UserRoleEnum mustRoleEnum = UserRoleEnum.getEnumByValue(mustRole); // 不需要权限,放行 if (mustRoleEnum == null) { return joinPoint.proceed(); } // 以下为:必须有该权限才通过 // 获取当前用户具有的权限 UserRoleEnum userRoleEnum = UserRoleEnum.getEnumByValue(loginUser.getUserRole()); // 没有权限,拒绝 if (userRoleEnum == null) { throw new BusinessException(ErrorCode.NO_AUTH_ERROR); } // 要求必须有管理员权限,但用户没有管理员权限,拒绝 if (UserRoleEnum.ADMIN.equals(mustRoleEnum) && !UserRoleEnum.ADMIN.equals(userRoleEnum)) { throw new BusinessException(ErrorCode.NO_AUTH_ERROR); } // 通过权限校验,放行 return joinPoint.proceed(); } } ``` ##分页功能修复 要加上分页的插件 ```xml <!-- MyBatis-Plus Spring Boot Starter --> <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-spring-boot3-starter</artifactId> <version>3.5.9</version> </dependency> <!-- https://mvnrepository.com/artifact/com.baomidou/mybatis-plus-jsqlparser --> <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-jsqlparser</artifactId> <version>3.5.9</version> </dependency> ``` 配置类 拦截器 ```java @Configuration @MapperScan("com.yupi.yupicturebackend.mapper") public class MyBatisPlusConfig { /** * 拦截器配置 * * @return {@link MybatisPlusInterceptor} */ @Bean public MybatisPlusInterceptor mybatisPlusInterceptor() { MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor(); // 分页插件 interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL)); return interceptor; } } ``` ## ANTD国际化配置 ```vue <script setup> // 引入中文语言包 import zhCN from 'ant-design-vue/es/locale/zh_CN' </script> <template> <a-config-provider :locale="zhCN"> <RouterView></RouterView> </a-config-provider> </template> <style scoped></style> ``` ##前端菜单的编写和权限管理 ```vue <a-menu v-model:selectedKeys="selectedKeys" mode="horizontal" :style="{ lineHeight: '64px' }" :items="filterMenusResult" @click="doMenuClick" ></a-menu> ``` ```js // 菜单(这里我们规定key以admin开头的菜单为管理员菜单,普通用户登录是看不到的) const items = ref([ { key: 'home', // icon: () => h(MailOutlined), label: '主页', title: '主页', }, { key: 'add_picture', label: '创建图片', title: '创建图片', }, { key: 'chart', label: '智能分析', title: '智能分析', }, { key: 'chartAsync', label: '异步分析', title: '异步分析', }, { key: 'mychart', // icon: () => h(MailOutlined), label: '图表管理', title: '图表管理', }, { key: 'adminUserManager', // icon: () => h(MailOutlined), label: '用户管理', title: '用户管理', }, { key: 'my', // icon: () => h(MailOutlined), label: '我的', title: '我的', }, ]) // 切换菜单 const doMenuClick = ({ key }) => { router.push({ name: key }) } // 路由更新时自动更新选中的菜单项 router.afterEach((to) => { console.log('菜单更新', to) selectedKeys.value = [to.name] }) // 根据权限过滤菜单项 const filterMenus = (menus) => { return menus?.filter((menu) => { // 如果菜单以admin开头 if (menu.key.startsWith('admin')) { // 获取pinia里的用户信息 // 如果用户信息不存在,或者存在了但userRole不是管理员,就返回false,不显示这个菜单 const loginUser = useUserStore().userInfo if (!loginUser || loginUser.userRole !== 'admin') { return false } } return true }) } // 写完方法,我们来引用,过滤原始的菜单项(有变化都要重新过滤一次用computed) const filterMenusResult = computed(() => { return filterMenus(items.value) }) ``` ### 这样子可以多字段查找,就一个搜索条件你可以搜name也可以搜introduce ```java // 从多字段中搜索 if (StrUtil.isNotBlank(searchText)) { // 需要拼接查询条件 queryWrapper.and(qw -> qw.like("name", searchText) .or() .like("introduction", searchText) ); } ``` ## 腾讯云COS存储 ### 首先数据库图片表 ```sql -- 图片表 create table if not exists picture ( id bigint auto_increment comment 'id' primary key, url varchar(512) not null comment '图片 url', name varchar(128) not null comment '图片名称', introduction varchar(512) null comment '简介', category varchar(64) null comment '分类', tags varchar(512) null comment '标签(JSON 数组)', picSize bigint null comment '图片体积', picWidth int null comment '图片宽度', picHeight int null comment '图片高度', picScale double null comment '图片宽高比例', picFormat varchar(32) null comment '图片格式', userId bigint not null comment '创建用户 id', createTime datetime default CURRENT_TIMESTAMP not null comment '创建时间', editTime datetime default CURRENT_TIMESTAMP not null comment '编辑时间', updateTime datetime default CURRENT_TIMESTAMP not null on update CURRENT_TIMESTAMP comment '更新时间', isDelete tinyint default 0 not null comment '是否删除', INDEX idx_name (name), -- 提升基于图片名称的查询性能 INDEX idx_introduction (introduction), -- 用于模糊搜索图片简介 INDEX idx_category (category), -- 提升基于分类的查询性能 INDEX idx_tags (tags), -- 提升基于标签的查询性能 INDEX idx_userId (userId) -- 提升基于用户 ID 的查询性能 ) comment '图片' collate = utf8mb4_unicode_ci; ``` ###导入依赖 ```xml <!-- 腾讯云对象存储--> <dependency> <groupId>com.qcloud</groupId> <artifactId>cos_api</artifactId> <version>5.6.227</version> </dependency> ``` yml配置腾讯云对象存储 ```yaml # 对象存储配置(需要从腾讯云获取) cos: client: host: xxx secretId: xxx secretKey: xxxx region: xxx bucket: xxx ``` ### 封装配置类 ```java /** * 对象存储配置文件 */ @Configuration @ConfigurationProperties(prefix = "cos.client") @Data public class CosClientConfig { /** * 域名 */ private String host; /** * secretId */ private String secretId; /** * 密钥(注意不要泄露) */ private String secretKey; /** * 区域 */ private String region; /** * 桶名 */ private String bucket; @Bean public COSClient cosClient() { // 初始化用户身份信息(secretId, secretKey) COSCredentials cred = new BasicCOSCredentials(secretId, secretKey); // 设置bucket的区域, COS地域的简称请参照 https://www.qcloud.com/document/product/436/6224 ClientConfig clientConfig = new ClientConfig(new Region(region)); // 生成cos客户端 return new COSClient(cred, clientConfig); } } ``` ### 封装通用方法类(上传下载) 注:腾讯云要开通数据万象 ```java @Component public class CosManager { @Resource private CosClientConfig cosClientConfig; @Resource private COSClient cosClient; /** * 上传对象 * * @param key 唯一键 * @param file 文件 */ public PutObjectResult putObject(String key, File file) { PutObjectRequest putObjectRequest = new PutObjectRequest(cosClientConfig.getBucket(), key, file); return cosClient.putObject(putObjectRequest); } /** * 下载对象 * * @param key 唯一键 */ public COSObject getObject(String key) { GetObjectRequest getObjectRequest = new GetObjectRequest(cosClientConfig.getBucket(), key); return cosClient.getObject(getObjectRequest); } /** * 上传对象,并且附带图片信息(解析) * 注:腾讯云要开通数据万象 * * @param key 唯一键 * @param file 文件 */ public PutObjectResult putPictureObject(String key, File file) { PutObjectRequest putObjectRequest = new PutObjectRequest(cosClientConfig.getBucket(), key, file); // 获取图片信息,解析 PicOperations picOperations = new PicOperations(); // 1表示返回图片信息 picOperations.setIsPicInfo(1); // 构造处理参数 putObjectRequest.setPicOperations(picOperations); return cosClient.putObject(putObjectRequest); } } ``` ### 文件上传(逻辑) ```java @Service @Slf4j public class FileManager { @Resource private CosClientConfig cosClientConfig; @Resource private CosManager cosManager; /** * 用户上传图片通用方法 * @param multipartFile * @param uploadPathPrefix * @return */ public UploadPictureResult uploadPicture(MultipartFile multipartFile,String uploadPathPrefix) { // 校验图片 validPicture(multipartFile); // 图片上传地址 // 随机生成16为字符串放在url地址前面避免重复 String uuid = RandomUtil.randomString(16); // 获取上传文件的原始文件名 example.txt的文件,那么multipartFile.getOriginalFilename()将返回example.txt String originalFilename = multipartFile.getOriginalFilename(); // 自己拼接文件名字,避免重复和增加安全性 String uploadFileName = String.format("%s_%s.%s", DateUtil.formatDate(new Date()), uuid,FileUtil.getSuffix(originalFilename)); // 定义文件路径,把不同用户的图片放到不同的文件夹下,文件夹的前缀由用户的id定义 String uploadPath = String.format("/%s/%s", uploadPathPrefix,uploadFileName); // 解析结果并返回 File file = null; try { // 上传文件 file = File.createTempFile(uploadPath, null); multipartFile.transferTo(file); // 对象存储到腾讯云 PutObjectResult putObjectResult = cosManager.putPictureObject(uploadPath, file); // 获取图片信息 ImageInfo imageInfo = putObjectResult.getCiUploadResult().getOriginalInfo().getImageInfo(); // 图片的宽高和宽高比 图片格式 int width = imageInfo.getWidth(); int height = imageInfo.getHeight(); double scale = NumberUtil.round(width *1.0 / height, 2).doubleValue(); String format = imageInfo.getFormat(); // 封装返回结果 UploadPictureResult uploadPictureResult = new UploadPictureResult(); uploadPictureResult.setUrl(cosClientConfig.getHost() + "/" + uploadPath ); uploadPictureResult.setPicName(FileUtil.mainName(originalFilename)); uploadPictureResult.setPicSize(FileUtil.size(file)); uploadPictureResult.setPicWidth(width); uploadPictureResult.setPicHeight(height); uploadPictureResult.setPicScale(scale); uploadPictureResult.setPicFormat(format); // 图片结果 return uploadPictureResult; } catch (Exception e) { log.error("file upload error, filepath = " + uploadPath, e); throw new BesinessException(ErrorCode.SYSTEM_ERROR, "上传失败"); } finally { // 临时文件清理 deleteTempFile(file); } } /** * 图片的校验逻辑 * @param multipartFile */ private void validPicture(MultipartFile multipartFile) { // 检验文件大小 long size = multipartFile.getSize(); final long ONE_M=1024 * 1024 ; if (size > ONE_M) { throw new BesinessException(ErrorCode.PARAMS_ERROR,"文件大小不能超过1M"); } // 校验文件后缀 String suffix = FileUtil.getSuffix(multipartFile.getOriginalFilename()); // 允许上传的文件列表 final List<String> ALLOW_FORMAT_LIST = Arrays.asList("png", "jpg", "jpeg", "bmp", "gif","webp"); if (!ALLOW_FORMAT_LIST.contains(suffix)) { throw new BesinessException(ErrorCode.PARAMS_ERROR,"文件类型错误"); } } /** * 图片的异常逻辑 * @param file */ public void deleteTempFile(File file) { if (file != null) { return; } // 删除临时文件 boolean delete = file.delete(); if (!delete) { log.error("file delete error, filepath = {}", file.getAbsolutePath()); } } } ``` ### Controller测试 ```java @PostMapping("/upload") public BaseResponse<PictureVO> UploadFile(@RequestPart("file") MultipartFile multipartFile, PictureUploadRequest pictureUploadRequest, HttpServletRequest request) { User loginuser = userService.getloginuser(request); PictureVO pictureVO = pictureService.uploadPicture(multipartFile, pictureUploadRequest, loginuser); // 这里有个bug,如果Spring为了防止文件攻击所以限制了上传文件的大小,所以我们要主动开启 return ResultUtils.success(pictureVO); } ``` 这里有个bug,如果Spring为了防止文件攻击所以限制了上传文件的大小,所以我们要主动开启 ```yaml spring: servlet: multipart: max-file-size: 10MB ``` ### 后端props父组件拿到回调的东西,再通过props给子组件 父组件 ```js <template> <div id="addPicturePage"> <PictureUpload :picture="picture" :onSuccess="onSuccess"></PictureUpload> </div> </template> <script setup> import PictureUpload from '@/components/pictureUpload.vue' import { ref } from 'vue' const picture = ref() const onSuccess = (newvalue) => { picture.value = newvalue } </script> ``` 子组件 ```js const props = defineProps({ picture: { type: String, }, onSuccess: { type: Function, }, }) const loading = ref(false) /** * 上传图片 * @param param0 */ const handleUpload = async ({ file }) => { loading.value = true try { const formData = new FormData() const params = props.picture ? props.picture.id : '' formData.append('file', file) formData.append('id', params) const res = await uploadPicture(formData) if (res.code == 0 && res.data) { message.success('上传成功') // 将上传成功的信息返回给父组件 props.onSuccess?.(res.data) } else { message.error('上传失败' + res.message) } } catch (error) { message.error('error') message.error('上传失败' + error.message) } loading.value = false } ``` 图片管理页50分钟,先去看主页 ```js // 数据 const dataList = ref([]) const total = ref(0) // 搜索条件 const searchParams = reactive<API.PictureQueryRequest>({ current: 1, pageSize: 10, sortField: 'createTime', sortOrder: 'descend', }) // 分页参数 const pagination = computed(() => { return { current: searchParams.current ?? 1, pageSize: searchParams.pageSize ?? 10, total: total.value, showSizeChanger: true, showTotal: (total) => `共 ${total} 条`, } }) // 获取数据 const fetchData = async () => { const res = await listPictureByPageUsingPost({ ...searchParams, }) if (res.data.data) { dataList.value = res.data.data.records ?? [] total.value = res.data.data.total ?? 0 } else { message.error('获取数据失败,' + res.data.message) } } // 页面加载时请求一次 onMounted(() => { fetchData() }) // 获取数据 const doSearch = () => { // 重置搜索条件 searchParams.current = 1 fetchData() } // 表格变化处理 const doTableChange = (page: any) => { searchParams.current = page.current searchParams.pageSize = page.pageSize fetchData() } ``` ## 通过URL获取图片 主要是通过hutool里面的httpUtils.downfile来实现 这是以前的上传图片的代码,我们在以前的代码上改动更方便 1.首先将传入参数换成String 2.校验图片方法加一个判断String类型的,之前是MultipartFile类型的 3.获取URL名称 4.保存临时文件,下载下来保存到数据库 ```java public UploadPictureResult uploadPictureByURL(String fileUrl,String uploadPathPrefix) { // 校验图片 validPicture(fileUrl); // 图片上传地址 // 随机生成16为字符串放在url地址前面避免重复 String uuid = RandomUtil.randomString(16); // 获取上传文件的原始文件名 example.txt的文件,那么multipartFile.getOriginalFilename()将返回example.txt String originalFilename = FileUtil.mainName(fileUrl); // 自己拼接文件名字,避免重复和增加安全性 String uploadFileName = String.format("%s_%s.%s", DateUtil.formatDate(new Date()), uuid,FileUtil.getSuffix(originalFilename)); // 定义文件路径,把不同用户的图片放到不同的文件夹下,文件夹的前缀由用户的id定义 String uploadPath = String.format("/%s/%s", uploadPathPrefix,uploadFileName); // 解析结果并返回 File file = null; try { // 上传文件 // 创建临时文件夹 file = File.createTempFile(uploadPath, null); // multipartFile.transferTo(file); // 将文件下载下来 HttpUtil.downloadFile(fileUrl,file); // 对象存储到腾讯云 PutObjectResult putObjectResult = cosManager.putPictureObject(uploadPath, file); // 获取图片信息 ImageInfo imageInfo = putObjectResult.getCiUploadResult().getOriginalInfo().getImageInfo(); // 图片的宽高和宽高比 图片格式 int width = imageInfo.getWidth(); int height = imageInfo.getHeight(); double scale = NumberUtil.round(width *1.0 / height, 2).doubleValue(); String format = imageInfo.getFormat(); // 封装返回结果 UploadPictureResult uploadPictureResult = new UploadPictureResult(); uploadPictureResult.setUrl(cosClientConfig.getHost() + "/" + uploadPath ); uploadPictureResult.setPicName(FileUtil.mainName(originalFilename)); uploadPictureResult.setPicSize(FileUtil.size(file)); uploadPictureResult.setPicWidth(width); uploadPictureResult.setPicHeight(height); uploadPictureResult.setPicScale(scale); uploadPictureResult.setPicFormat(format); // 图片结果 return uploadPictureResult; } catch (Exception e) { log.error("file upload error, filepath = " + uploadPath, e); throw new BesinessException(ErrorCode.SYSTEM_ERROR, "上传失败"); } finally { // 临时文件清理 deleteTempFile(file); } } ``` URL校验方法 ```java private void validPicture(String fileUrl) { ThrowUtils.throwIf(StrUtil.isBlank(fileUrl), ErrorCode.PARAMS_ERROR, "文件地址不能为空"); try { // 1. 验证 URL 格式 new URL(fileUrl); // 验证是否是合法的 URL } catch (MalformedURLException e) { throw new BusinessException(ErrorCode.PARAMS_ERROR, "文件地址格式不正确"); } // 2. 校验 URL 协议 ThrowUtils.throwIf(!(fileUrl.startsWith("http://") || fileUrl.startsWith("https://")), ErrorCode.PARAMS_ERROR, "仅支持 HTTP 或 HTTPS 协议的文件地址"); // 3. 发送 HEAD 请求以验证文件是否存在 HttpResponse response = null; try { response = HttpUtil.createRequest(Method.HEAD, fileUrl).execute(); // 未正常返回,无需执行其他判断 if (response.getStatus() != HttpStatus.HTTP_OK) { return; } // 4. 校验文件类型 String contentType = response.header("Content-Type"); if (StrUtil.isNotBlank(contentType)) { // 允许的图片类型 final List<String> ALLOW_CONTENT_TYPES = Arrays.asList("image/jpeg", "image/jpg", "image/png", "image/webp"); ThrowUtils.throwIf(!ALLOW_CONTENT_TYPES.contains(contentType.toLowerCase()), ErrorCode.PARAMS_ERROR, "文件类型错误"); } // 5. 校验文件大小 String contentLengthStr = response.header("Content-Length"); if (StrUtil.isNotBlank(contentLengthStr)) { try { long contentLength = Long.parseLong(contentLengthStr); final long TWO_MB = 2 * 1024 * 1024L; // 限制文件大小为 2MB ThrowUtils.throwIf(contentLength > TWO_MB, ErrorCode.PARAMS_ERROR, "文件大小不能超过 2M"); } catch (NumberFormatException e) { throw new BusinessException(ErrorCode.PARAMS_ERROR, "文件大小格式错误"); } } } finally { if (response != null) { response.close(); } } } ``` ## 设计模式---模版模式 我们现在在FileManager里面有对文件和URL地址的上传,他们之间逻辑 步骤相同,唯一区别在于类型,文件是File类型URL地址是String类型,还有基于这个类型不同做的不同校验,这时候就可以写一个模版抽象类来参考实现,相当于Service和serviceimpl的关系,但区别在于service一般只有一个serviceimpl,但模版抽象类可以有多个,而且完全一样 两边都能用的代码可以放在模版类里,子类可以继承用模版类的东西,那些需要改的有差异的东西,可以给子类override 写,这样就实现了FileManager的精简,把FileManager分成3个类。 模版类 ```java @Slf4j public abstract class PictureUploadTemplate { @Resource protected CosManager cosManager; @Resource protected CosClientConfig cosClientConfig; /** * 模板方法,定义上传流程 */ public final UploadPictureResult uploadPicture(Object inputSource, String uploadPathPrefix) { // 1. 校验图片 validPicture(inputSource); // 2. 图片上传地址 String uuid = RandomUtil.randomString(16); String originFilename = getOriginFilename(inputSource); String uploadFilename = String.format("%s_%s.%s", DateUtil.formatDate(new Date()), uuid, FileUtil.getSuffix(originFilename)); String uploadPath = String.format("/%s/%s", uploadPathPrefix, uploadFilename); File file = null; try { // 3. 创建临时文件 file = File.createTempFile(uploadPath, null); // 处理文件来源(本地或 URL) processFile(inputSource, file); // 4. 上传图片到对象存储 PutObjectResult putObjectResult = cosManager.putPictureObject(uploadPath, file); ImageInfo imageInfo = putObjectResult.getCiUploadResult().getOriginalInfo().getImageInfo(); // 5. 封装返回结果 return buildResult(originFilename, file, uploadPath, imageInfo); } catch (Exception e) { log.error("图片上传到对象存储失败", e); throw new BesinessException(ErrorCode.SYSTEM_ERROR, "上传失败"); } finally { // 6. 清理临时文件 deleteTempFile(file); } } /** * 校验输入源(本地文件或 URL) */ protected abstract void validPicture(Object inputSource); /** * 获取输入源的原始文件名 */ protected abstract String getOriginFilename(Object inputSource); /** * 处理输入源并生成本地临时文件 */ protected abstract void processFile(Object inputSource, File file) throws Exception; /** * 封装返回结果 * @param originFilename 文件名 * @param file * @param uploadPath 路径 * @param imageInfo 对象存储的文件信息 * @return */ private UploadPictureResult buildResult(String originFilename, File file, String uploadPath, ImageInfo imageInfo) { UploadPictureResult uploadPictureResult = new UploadPictureResult(); int picWidth = imageInfo.getWidth(); int picHeight = imageInfo.getHeight(); double picScale = NumberUtil.round(picWidth * 1.0 / picHeight, 2).doubleValue(); uploadPictureResult.setPicName(FileUtil.mainName(originFilename)); uploadPictureResult.setPicWidth(picWidth); uploadPictureResult.setPicHeight(picHeight); uploadPictureResult.setPicScale(picScale); uploadPictureResult.setPicFormat(imageInfo.getFormat()); uploadPictureResult.setPicSize(FileUtil.size(file)); uploadPictureResult.setUrl(cosClientConfig.getHost() + "/" + uploadPath); return uploadPictureResult; } /** * 删除临时文件 */ public void deleteTempFile(File file) { if (file == null) { return; } boolean deleteResult = file.delete(); if (!deleteResult) { log.error("file delete error, filepath = {}", file.getAbsolutePath()); } } } ``` 继承的文件上传子类 ```java public class FilePictureUploadTemplateImpl extends PictureUploadTemplate { @Override protected void validPicture(Object inputSource) { // 强转一下 MultipartFile multipartFile = (MultipartFile) inputSource; // 检验文件大小 long size = multipartFile.getSize(); final long ONE_M=1024 * 1024 ; if (size > ONE_M) { throw new BesinessException(ErrorCode.PARAMS_ERROR,"文件大小不能超过1M"); } // 校验文件后缀 String suffix = FileUtil.getSuffix(multipartFile.getOriginalFilename()); // 允许上传的文件列表 final List<String> ALLOW_FORMAT_LIST = Arrays.asList("png", "jpg", "jpeg", "bmp", "gif","webp"); if (!ALLOW_FORMAT_LIST.contains(suffix)) { throw new BesinessException(ErrorCode.PARAMS_ERROR,"文件类型错误"); } } @Override protected String getOriginFilename(Object inputSource) { // 强转一下 MultipartFile multipartFile = (MultipartFile) inputSource; return multipartFile.getOriginalFilename(); } @Override protected void processFile(Object inputSource, File file) throws Exception { // 强转一下 MultipartFile multipartFile = (MultipartFile) inputSource; multipartFile.transferTo(file); } } ``` 继承的URL上传子类 ```java public class UrlPictureUploadTemplateImpl extends PictureUploadTemplate { @Override protected void validPicture(Object inputSource) { String fileUrl = (String) inputSource; // 校验非空 ThrowUtils.throwIf(fileUrl == null, ErrorCode.PARAMS_ERROR, "文件地址为空"); // 校验URL格式 try { // 这里利用java自带的URL,new一个对象来校验,有问题就抛异常 new URL(fileUrl); } catch (MalformedURLException e) { throw new BesinessException(ErrorCode.PARAMS_ERROR, "文件地址格式错误"); } // 校验URL协议(如果不是以http或者https开头就有问题) ThrowUtils.throwIf(!fileUrl.startsWith("http://") && !fileUrl.startsWith("https://"), ErrorCode.PARAMS_ERROR, "仅支持 http 或 https 协议的文件地址"); // 3. 发送 HEAD 请求以验证文件是否存在 // 发送 HEAD 请求以验证文件是否存在 HttpResponse response = null; try { response = HttpUtil.createRequest(Method.HEAD, fileUrl).execute(); // 未正常返回,无需执行其他判断 注意不要抛异常 if (response.getStatus() != HttpStatus.HTTP_OK) { return; } // 4. 校验文件类型 String contentType = response.header("Content-Type"); if (StrUtil.isNotBlank(contentType)) { // 允许的图片类型 final List<String> ALLOW_CONTENT_TYPES = Arrays.asList("image/jpeg", "image/jpg", "image/png", "image/webp"); ThrowUtils.throwIf(!ALLOW_CONTENT_TYPES.contains(contentType.toLowerCase()), ErrorCode.PARAMS_ERROR, "文件类型错误"); } // 5. 校验文件大小 String contentLengthStr = response.header("Content-Length"); if (StrUtil.isNotBlank(contentLengthStr)) { try { long contentLength = Long.parseLong(contentLengthStr); final long TWO_MB = 2 * 1024 * 1024L; // 限制文件大小为 2MB ThrowUtils.throwIf(contentLength > TWO_MB, ErrorCode.PARAMS_ERROR, "文件大小不能超过 2M"); } catch (NumberFormatException e) { throw new BesinessException(ErrorCode.PARAMS_ERROR, "文件大小格式错误"); } } } finally { if (response != null) { response.close(); } } } @Override protected String getOriginFilename(Object inputSource) { // 强转一下 String fileUrl = (String) inputSource; // 获取上传文件的原始文件名 example.txt的文件,那么multipartFile.getOriginalFilename()将返回example.txt return FileUtil.mainName(fileUrl); } @Override protected void processFile(Object inputSource, File file) throws Exception { // 强转一下 String fileUrl = (String) inputSource; // 将文件下载下来 HttpUtil.downloadFile(fileUrl,file); } } ``` 上面3个类的工作完成后,我们已经可以完全用这种方法来代替了,给原来的类加上 @Deprecated表示废弃了 ```java /** * 文件通用方法类 * @Deprecated已废弃,改为用upload的模版方法优化 */ @Service @Slf4j @Deprecated public class FileManager { ``` 那既然整个文件改了,那用到了的service也要对应改 1.将原来的MultipartFile类型换成Object,跟上面的类同理,方便强转 ```java PictureVO uploadPicture(Object imputSource, PictureUploadRequest pictureUploadRequest, User LoginUser); ``` 2.改用到的地方 ```java @Resource private FilePictureUpload filePictureUpload; @Resource private UrlPictureUpload urlPictureUpload; // 上传图片 public PictureVO uploadPicture(Object inputSource, PictureUploadRequest pictureUploadRequest, User loginUser) { if (inputSource == null) { throw new BusinessException(ErrorCode.PARAMS_ERROR, "图片为空"); } // ... // 按照用户 id 划分目录 String uploadPathPrefix = String.format("public/%s", loginUser.getId()); // 根据 inputSource 的类型判断是上传文件还是上传url PictureUploadTemplate pictureUploadTemplate = filePictureUpload; if (inputSource instanceof String) { pictureUploadTemplate = urlPictureUpload; } UploadPictureResult uploadPictureResult = pictureUploadTemplate.uploadPicture(inputSource, uploadPathPrefix); // 构造要入库的图片信息 // ... } ``` URL文件上传的接口实现(这个可以直接接上一个,毕竟我们设计模式只是冗杂代码的优化) ```java /** * 通过 URL 上传图片(可重新上传) */ @PostMapping("/upload/url") public BaseResponse<PictureVO> uploadPictureByUrl( @RequestBody PictureUploadRequest pictureUploadRequest, HttpServletRequest request) { User loginUser = userService.getLoginUser(request); String fileUrl = pictureUploadRequest.getFileUrl(); PictureVO pictureVO = pictureService.uploadPicture(fileUrl, pictureUploadRequest, loginUser); return ResultUtils.success(pictureVO); } ``` ## 空间路由拦截 1.登录后用userid获取空间存pinia,有pinia里面就有数据,没有就空 2.在进入ai智绘页面进行1次路由拦截: 2.1:判断空间是否存在(也就是pinia里面是否有数据),没有重定向到创建空间页面, 2.2:判断空间是否是用户本人,pinia的space表 里面的userid是不是跟 pinia的user表里面的userid一样,如果一样就给进,如果不一样就抛一个无权限访问,然后重定向回我的空间 其实可以总结为1个点不用这么复杂,你只需要获取你进去页面spaceid的空间详情就可以了。但这样做有个弊端,用户要进入这个页面一定要从我的空间里面进入,没办法从页面的其他地方比如菜单栏,活动页什么的进入。如果要解决这个问题,就需要去调以userid获取space的接口,或者是一开始就将space存pinia里面,但这样不论是哪种我的接口逻辑都要改,那再说了空间一多怎么办呢 综上所述,现在比较好的方法是在进去ai智绘页面就给判断一次路由拦截,获取根据空间id获取这个空间详情,然后看这个空间详情的userid是不是跟pinia的user表里的userid一样,一样就进入,不一样就给一个"无权限,从”我的空间“内进入",然后重定向回我的空间,如果没有空间也不用管,因为我们在进入我的空间页面会自动进行一次判断如果没空间也会重定向到创建空间 ### 组件内使用路由拦截 组件内使用路由拦截建议在路由配置里面使用,路由配置里有专门的配置项用来配置路由拦截 路由配置 ```js // AI生图页面(1级) { path: '/ai/generation', name: 'ai_generation', component: () => import('@/views/Pages/ai/AiPage.vue'), beforeEnter: async (to) => { const result = await checkSpaceAuth(to.query.spaceId) if (typeof result === 'string') { return { path: result } } return result }, }, ``` 路由拦截的方法 ```js export const checkSpaceAuth = async (spaceId) => { // 获取当前登录用户 const loginStore = useUserStore() const loginUser = loginStore.userInfo // 如果传入的spaceid为空,就给一个错误消息提示,并重定向到"我的空间" if (!spaceId) { message.error('访问链接不完整,请从「我的空间」重新进入') return '/my_space' // 返回重定向路径 } // 接口获取spaceid的空间信息,如果获取不到,就给一个无权限的错误消息提示,并重定向到"我的空间" try { const res = await getSpaceVOById({ id: spaceId }) // 空间不存在的情况,不给进,重定向到"我的空间",但给错误的消息提示 if (res.code !== 0) { message.error('请求失败,' + res.description) return '/my_space' // 允许继续导航 } if (res.data.user.id !== loginUser.id) { message.error('无权限,请从 "我的空间" 进入') return '/my_space' } return true // 放行 } catch (error) { message.error('请求失败,接口异常') return true } } ``` ## vue 3 遇到 process is not defined 使用 vite 安装 vue3 时,如果使用了 process.env,会遇到 process 未定义的情况,原因是 process.env 已经被移除了。解决办法是在 vite.config.ts 中增加 define ```js import { defineConfig } from 'vite' // ... export default defineConfig({ // ... define: { 'process.env': {} } }) ```

最近花了点时间把去年写的智能BI项目改造了一下,因为看了智能协同云图库的DDD改造有点手痒,所以把BI项目改了一下,结果写着写着了解了更多领域驱动的知识,发现自己离标准的DDD还是有很大差距,我写的一点也优雅。然后就是又引入了一下Langchain4j,调整了一下AI的调用和新增了知识库,实现了Rag的智能知识问答(向量数据库选的ES)。有兴趣的看看,最好是可以指点一下哈哈哈哈哈 项目地址https://github.com/chenrgSix/rgbi_backend

bi项目完结&扩展

## 项目地址 www.pikim.fun (AItoken有限,各位鱼友轻点打) ## 项目实现思路 我并没有把他当做一个纯BI的项目,而是借助当下火爆的大模型,利用大模型开发一些有意思的AI功能。当然,目前实现的功能也少,后期在慢慢完善了吧哈哈哈~ ### 实现功能 - 图表生成 - 图片理解 - AI对话(海龟汤) 部分页面只有管理员能看到,后期会再修一下逻辑~ ## 扩展点 这里对图表生成扩展最对,也是花费时间最多的地方,后面两个模块仅仅实现功能,等有时间慢慢优化 1. 对图表表做了分表,easyExcel 提取的数据分离原始表格,实现一个图表一个数据表 2. 图表生成添加生成超时功能 3. 对生成失败表格进行重试(还未完善) 4. 图表生成校验,单用户相同图表数据,相同图表类型,只能生成一次 5. 用redis,对图表的分页查询做缓存 6. 管理员能查看所有用户生成的图表,普通用户只能查看自己的图表记录 7. 添加注册页面,接口 9. 对图表做了CRUD,支持更新,删除动态表格(仅支持关管理员修改) 10. 扩展图片理解页面 11. 扩展AI对话海龟汤游戏,查询对话记录等 ## 局限性 1. 图表分析,只支持excel文档数据,用户上传文件受限 2. 图表分析要求较高的AI算力,目前AI生成复杂的图表耗时长,内容有误,渲染失败等等 3. 图片理解页面,暂未开发历史图片页面 4. AI对话,仅仅是实现功能 ## 后期计划&优化 ### 图表生成 1. 统计当前用户生成图表次数 2. 对图表页面实现自动刷新功能 3. 限制单个用户每天生成图表次数 4. 分析结果采用,“展开更多”显示 ### 图片理解 1. 添加历史图片理解页面 2. 图片仅仅上传到服务器,尝试采用cos➕CDN存储图片 ### AI对话 1. 添加多个ai,接口处理对话,每次轮训不同AI接口 2. 用redis缓存聊天记录,mq添加聊天信息,写入到数据库 3. 分库分表,对每个房间消息做隔离 4. 用redis缓存当前对话记录 5. 聊天框异常,用户突然退出,死机,黑屏等操作,保证聊天记录计时被保存 6. 当房间没有活动,超过某一时间后,自动结束,修改房间状态为“已关闭” ## 最后 项目的难点 1. 在于对动态表做CRUD,由于用户上传的表数据不一,导致每个表的表名,字段名,字段数量都不相同,这样去拼接动态sql,代码都比较繁杂。 2. 处理科大讯飞的AI接口,没有完全封装,需要手动修改部分代码 3. 项目上线,后端采用docker-compose.yml部署容器,对docker-compose语法不熟悉,试错了多次才成功部署。 ![image.png](https://pic.code-nav.cn/post_picture/1609191652126097409/IO5OGPLjmWcxH0fz.webp) <img src="https://pic.code-nav.cn/post_picture/1609191652126097409/HQwNjqaHCBwGk8YR.webp" alt="image.png" width="100%" /> <img src="https://pic.code-nav.cn/post_picture/1609191652126097409/H3FMtqaztGT3oMje.webp" alt="image.png" width="100%" /> <img src="https://pic.code-nav.cn/post_picture/1609191652126097409/y1c7Fysvm1SdPOgy.webp" alt="image.png" width="100%" />

下载 APP