首页
留言
友链
关于
Search
1
思源笔记docker私有化部署及使用体验分享
1,193 阅读
2
windows11 远程提示:为安全考虑,已锁定该用户帐户,原因是登录尝试或密码更改尝试过多。
619 阅读
3
Pointer-Focus:一款功能强大的教学、录屏辅助软件
389 阅读
4
在vue-cli3使用sass(scss)定义的全局样式及变量
323 阅读
5
使用cspell对项目做拼写规范检查
264 阅读
Web前端
CSS
JavaScript
交互
Vue
小程序
后端
运维
生活
其他
转载
软件
登录
Search
标签搜索
docker
DevOps
magic-boot
Linux
酷壳
RabbitMQ
Node
git
工具
Vue
MybatisPlus
clickhouse
规范
前端
产品
markdown
axios
H5
经纬度
vue-cli
朱治龙
累计撰写
118
篇文章
累计收到
0
条评论
首页
栏目
Web前端
CSS
JavaScript
交互
Vue
小程序
后端
运维
生活
其他
转载
软件
页面
留言
友链
关于
搜索到
118
篇与
朱治龙
的结果
2024-05-12
RabbitMQ学习:③基本使用
初始配置添加用户使用默认的 guest 账号登录后,可以在 Admin → Users中添加用户:添加 Virtual Host在Admin → Virtual Hosts 中添加虚拟机:给用户授权点击 Virtual Host 的名称,进入详情界面,可在Permissions中给新建的用户设置权限:建立连接1、新建Maven 项目2、导入依赖<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>net.x2m</groupId> <artifactId>rabbitmq</artifactId> <version>1.0-SNAPSHOT</version> <properties> <maven.compiler.source>17</maven.compiler.source> <maven.compiler.target>17</maven.compiler.target> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> </properties> <dependencies> <dependency> <groupId>com.rabbitmq</groupId> <artifactId>amqp-client</artifactId> <version>5.21.0</version> </dependency> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.13.2</version> <scope>test</scope> </dependency> </dependencies> </project>3、创建工具类连接 RabbitMQpublic static Connection getConnection() { ConnectionFactory factory = new ConnectionFactory(); factory.setHost("localhost"); factory.setPort(5672); factory.setUsername("zhuzl"); factory.setPassword("123456"); factory.setVirtualHost("/test"); Connection conn = null; try { conn = factory.newConnection(); } catch (IOException e) { throw new RuntimeException(e); } catch (TimeoutException e) { throw new RuntimeException(e); } return conn; }4、代码层面获取连接后,在管理界面可以看到如下连接信息:5、点击Name 可查看连接详情如下:6、调试界面的连接信息:
2024年05月12日
1 阅读
0 评论
0 点赞
2024-05-12
RabbitMQ学习:②基本概念
本文核心内容参加官网链接:https://www.rabbitmq.com/tutorials/amqp-conceptsPublisher:生产者(发布消息到R啊波比跳MQ中的Exchange)Consumer:消费者(监听RabbitMQ中的Queue中的消息)Exchange:交换机(和生产者建立连接并接收生产者的消息)Queue:队列(Exchange会将消息分发到指定的Queue,Queue和消费者进行交互)Routes:路由(交换机以什么样的策略将消息发布到 Queue)RabbitMQ 的完整架构图:
2024年05月12日
1 阅读
0 评论
0 点赞
2024-05-12
RabbitMQ学习:①安装
背景近期参与公司的在线充值业务的功能开发,该业务涉及多个系统交互,采用MQ的方式实现跨系统通讯:而在我既往的项目经验中还未使用过 MQ,便利用工作之余对相关的知识点进行补充学习。本系列内容即是我的一个0基础入门学习记录,仅做参考。RabbitMQ 基本介绍RabbitMQ 是一个由 Erlang 语言开发的 AMQP 的开源实现。Erlang是为电话交换机编写的语言,天然对分布式和高并发支持良好。常用MQ对比比较项RabbitMQActiveMQRocketMQKafka公司/社区Broadcom Inc.Apache阿里Apache开发语言ErlangJavaJavaScala&Java协议支持AMQP,XMPP,SMTP,STOMPoPENwIRE,stomp,REST,XMPP,AMQP自定义自定义协议,社区封装了http协议支持客户端支持语言官方支持Erlang,Java,Ruby,PHP,.NET,GO,JS等,社区产出多种API,几乎支持所有语言Java,C,C++,Python,PHP,Perl,.NET等Java,C++官方支持Java,社区产出多种API,如PHP,Python等单机吞吐量万级(其次)万级(最差)十万级(最好)十万级(次之)消息延迟微秒级毫秒级毫秒级毫秒以内功能特性并发能力强,性能极其好,延时低,社区活跃,管理界面丰富老牌产品,成熟度高,文档较多MQ功能必要完备,扩展性佳只支持主要的MQ功能,主要为大数据领域场景安装 RabbitMQ为快速安装部署,使用 docker compose 方式运行,docker-compose.yaml文件内容如下:services: rabbitmq: image: rabbitmq:management restart: always container_name: rabbitmq ports: - 5672:5672 - 15672:15672 volumes: - ./data:/var/lib/rabbitmq使用 docker compose up -d 启动后,使用浏览器访问如下地址:http://localhost:15672/,显示如下界面则表示 RabbitMQ 运行成功:在上面的登录界面使用 guest 作为用户名和密码登录,打开如下图所示的主界面:
2024年05月12日
3 阅读
0 评论
0 点赞
2024-05-11
用 1 年时间赶超同龄人 38 倍的人生算法
成功的人都抓住了复利,我们可以从认证做好每一件小事开始,不断进步复利成长,用1年的时间超越同龄人38倍。
2024年05月11日
2 阅读
0 评论
0 点赞
2024-04-07
基于 Magic-api + Clickhouse 实现业务数据更新的项目记录
背景介绍项目有用到 Clickhouse 作为数仓,存储一些用户日常业务产生的大数据,下面先简单介绍一下我们这个任务的需求背景:我们的每个用户都会归属于某个用户组,并基于用户所在的计费组织实现产品使用过程中的消费等情况。而按照系统的设定用户初始注册时是没有归属用户组的,计费组织的主账号可以在控制台将用户绑定到该计费组下,也可以解绑,解绑后也可以绑定到其他用户组。为了更好的这个变更情况,我们在 Clickhouse 添加了一张名为 user_type 的表,每次该数据变更都会新增一条记录,该表的结构如下:CREATE TABLE user_type ( `user_id` Nullable(String), `present_type` Nullable(String), `pay_type` Nullable(String), `group_type` Nullable(String), `start_date` Nullable(Date), `end_date` Nullable(Date), `uni_key` Nullable(String) ) ENGINE = Log;实现方案本项目初期由使用 dbt + Clickhouse 的方式来实现,但是经实践运行一段时间后发现 dbt 做数据同步很方便,但是要添加一些业务逻辑就显得很棘手。为了解决 dbt 的问题,我们使用已搭建的 magic-api 来实现这个数据的更新,由于相关数据仅需一天一更新即可,所以我们可以直接利用 magic-api 自带的定时任务机制来实现更新。技术细节为便于相关业务逻辑在接口和定时任务中复用,我们将核心代码写在函数模块中:相关步骤核心代码如下步骤:1、从计费系统获取最新的userType信息var statSQL = `select e.*, CONCAT(e.user_id,'-',e.present_type,'-',e.pay_type,'-',e.group_type,'-',date_format(e.start_date,'%Y-%m-%d')) as uni_key FROM( SELECT a.user_id, CASE WHEN EXISTS ( SELECT 1 FROM ( SELECT t1.user_id user_id from b_contract t1 LEFT JOIN b_contract_item t2 ON t1.id = t2.contract_id WHERE t2.is_present = 0 and t2.received_payments > 0 GROUP BY t1.user_id UNION SELECT u2.user_id user_id from b_user as u1, b_user as u2 where u1.group_id=u2.group_id AND u1.user_id != u2.user_id AND EXISTS( SELECT 1 FROM (SELECT t1.user_id from b_contract t1 LEFT JOIN b_contract_item t2 ON t1.id = t2.contract_id WHERE t2.is_present = 0 and t2.received_payments > 0 GROUP BY t1.user_id) c WHERE c.user_id = u1.user_id ) ) d WHERE a.user_id = d.user_id ) then 'pay' else 'no pay' END as present_type, CASE WHEN EXISTS( SELECT 1 FROM( SELECT t1.user_id FROM b_user t1 , b_group t2 WHERE t1.user_id=t2.pay_user_id AND t2.pay_user_id IS NOT NULL )b WHERE a.user_id = b.user_id ) THEN 'master' ELSE 'slave' END as pay_type, CASE WHEN EXISTS(SELECT 1 FROM(SELECT t1.user_id FROM b_user t1 WHERE t1.group_id IS NOT NULL)b WHERE a.user_id = b.user_id) THEN 'group' ELSE 'no group' END as group_type, CURRENT_DATE as start_date, DATE(null) as end_date FROM b_user a )e` return db['NB'].select(statSQL)2、将上一步获取到的信息存储到Clickhouse 的一张临时表import log; import cn.hutool.core.date.DateUtil; import '@/statForProduction/userTypeStat/getLatestUserTypeData' as getLatestUserType; // ------------------- 一、创建临时表 ------------------- const TEMP_TABLE_NAME = 'user_type_temp' var checkExistRes = db['CH'].select(`SELECT 1 FROM system.tables WHERE database = 'dw' AND name = '${TEMP_TABLE_NAME}'`) log.info(checkExistRes.size() + '') // 不存在表的话就基于 user_type 表创建一张临时表 if (checkExistRes.size() === 0) { var initTemporaryTableSQL = `CREATE TABLE ${TEMP_TABLE_NAME} as user_type` db['CH'].update(initTemporaryTableSQL) } else { // 临时表存在则先清空临时表的数据,便于下一步将输入存入临时表 var truncateTemporaryTableSQL = `truncate table ${TEMP_TABLE_NAME}` db['CH'].update(truncateTemporaryTableSQL) } // ------------------- 二、获取最新的用户类型数据 ------------------- log.info(`============ 开始从计费系统获取最新的用户类型数据,该操作耗时较长,请耐心等待 ============`) var timer = DateUtil.timer() const userTypeList = getLatestUserType() log.info(`getLatestUserType cost time: ${timer.intervalPretty()}.`) // ------------------- 三、将数据存入临时表 ------------------- const BATCH_INSERT_COUNT = 1000 // 分批次入临时表,一次插入记录条数 var timer = DateUtil.timer() const allDataCount = userTypeList.size() if (allDataCount > 0) { log.info(`开始导入数据到临时表,待导入的总记录数为:${allDataCount},预计分${Math.ceil(allDataCount/BATCH_INSERT_COUNT)::int}批导入。`) const willInsertArr = [] var insertSQL = `insert into ${TEMP_TABLE_NAME}(user_id,present_type,pay_type,group_type,start_date,end_date,uni_key)` // 分批次插入临时表 for (index,userTypeItem in userTypeList) { willInsertArr.push(`('${userTypeItem.userId}','${userTypeItem.presentType}','${userTypeItem.payType}','${userTypeItem.groupType}','${userTypeItem.startDate}', null,'${userTypeItem.uniKey}')`) if (willInsertArr.size() === BATCH_INSERT_COUNT) { db['CH'].update(`${insertSQL} values${willInsertArr.join(',')}`) // 清空数据 willInsertArr.clear() log.info('Batch insert:' + index) } } // 不满整批次数据单独处理 if (willInsertArr.size() > 0) { db['CH'].update(`${insertSQL} values${willInsertArr.join(',')}`) // 清空数据 willInsertArr.clear() } } log.info(`insert latest user type to Temporary Table cost time: ${timer.intervalPretty()}.`) return true3、将临时表数据跟前一次最新的用户数据对比后,将有变更和新增的数据写入user_type表import log; import cn.hutool.core.date.DateUtil; const LATEST_TABLE_NAME = 'user_type_latest' // 用户最新类型数据表 const TEMP_TABLE_NAME = 'user_type_temp' // 该表存储从计费表获取到用户当前的用户类型数据,已在上一步获取数据完毕 // 一、从user_type表获取所有用户最新的用户类型数据并插入到用于计算的临时表 // 1.1 新建临时表,用于存储每个用户user_type 表中最新的用户类型数据 var checkExistRes = db['CH'].select(`SELECT 1 FROM system.tables WHERE database = 'dw' AND name = '${LATEST_TABLE_NAME}'`) log.info(checkExistRes.size() + '') // 不存在表的话就基于 user_type 表创建一张临时表 if (checkExistRes.size() === 0) { var initTemporaryTableSQL = `CREATE TABLE ${LATEST_TABLE_NAME} as user_type` db['CH'].update(initTemporaryTableSQL) } else { // 临时表存在则先清空临时表的数据,便于下一步将输入存入临时表 var truncateTemporaryTableSQL = `truncate table ${LATEST_TABLE_NAME}` db['CH'].update(truncateTemporaryTableSQL) } // 1.2 将最新数据写入临时表 // 该方式在数据量较大的情况下极有可能导致内存溢出,拟采取其他方案:在user_type 数据初始化的时候,将最新的用户类型数据存储到user_type_latest表,对比更新完成后将临时表的数据更新到user_type_latest便于下次对比 // const insertLatestDataSQL = `insert into ${LATEST_TABLE_NAME} SELECT user_type.user_id uid,user_type.present_type ,user_type.pay_type ,user_type.group_type,user_type.start_date,user_type.end_date,user_type.uni_key // FROM user_type, (SELECT user_type.user_id uid2,max(user_type.start_date) AS latestDate FROM user_type GROUP BY user_type.user_id) AS temp // WHERE user_type.start_date = temp.latestDate and uid = temp.uid2` // db['CH'].update(insertLatestDataSQL) // 二、两个临时表的数据做对比,并将最新数据更新到 user_type var timer = DateUtil.timer() // 2.1 更新有变更的数据 const changedInsertSQL = `insert into user_type select tuts.* from ${LATEST_TABLE_NAME} tutl left join ${TEMP_TABLE_NAME} tuts on tutl.user_id =tuts.user_id where tutl.present_type != tuts.present_type or tutl.pay_type != tuts.pay_type or tutl.group_type != tuts.group_type` timer.start("insertChangeData") db['CH'].update(changedInsertSQL) // 2.2 新增用户数据直接插入 timer.start("insertNewData") const insertNewUserSQL = `insert into user_type select * from ${TEMP_TABLE_NAME} tuts where tuts.user_id not in (select tutl.user_id from ${LATEST_TABLE_NAME} tutl) ` db['CH'].update(insertNewUserSQL) // 三、如果有数据更新,则将临时表的数据替换latest表 // 3.1 清理已有的数据 const truncateLatestTableSQL = `truncate table ${LATEST_TABLE_NAME}` db['CH'].update(truncateLatestTableSQL) // 3.2 从临时表导入最新的数据 const initialLatestTableDataSQL = `insert into ${LATEST_TABLE_NAME} select * from ${TEMP_TABLE_NAME}` db['CH'].update(initialLatestTableDataSQL) log.info(`insertChangeData cost time: ${timer.intervalPretty('insertChangeData')}`) log.info(`insertNewUser cost time: ${timer.intervalPretty('insertNewData')}`) // 四、清理临时表 const dropTempTableSQL = `drop table ${TEMP_TABLE_NAME}` db['CH'].update(dropTempTableSQL) return true 定义好相关函数后,我们可以直接在接口中用起来了,为此我定义了两个接口,一个接口用于数据初始化,一个接口用于手动更新数据:接口定义01数据初始化import log; import '@/statForProduction/userTypeStat/maintenance/clearUserTypeData' as clearUserTypeData import '@/statForProduction/userTypeStat/saveToTemporaryTable' as saveToTemporaryTable const LATEST_TABLE_NAME = 'user_type_latest' // 用户最新类型数据表 const TEMP_TABLE_NAME = 'user_type_temp' // 该表存储从计费表获取到用户当前的用户类型数据 // 一、清空所有user_type表的数据 clearUserTypeData() // 二、一次性写入所有 saveToTemporaryTable() // 三、将临时表的所有数据一次性写入user_type 表作为初始数据 const initialUserTypeDataSQL = `insert into user_type select * from ${TEMP_TABLE_NAME}` db['CH'].update(initialUserTypeDataSQL) // 四、将数据写入最新用户类型表,便于下一次做数据比对 // 4.1 基于 user_type 表 创建 user_type_latest 表 var checkExistRes = db['CH'].select(`SELECT 1 FROM system.tables WHERE database = 'dw' AND name = '${LATEST_TABLE_NAME}'`) log.info(checkExistRes.size() + '') // 不存在表的话就基于 user_type 表创建一张 if (checkExistRes.size() === 0) { var createLatestTableSQL = `CREATE TABLE ${LATEST_TABLE_NAME} as user_type` db['CH'].update(createLatestTableSQL) } else { // 表存在则先清空表的数据,便于下一步将最新的用户类型数据存入该表 var truncateLatestTableSQL = `truncate table ${LATEST_TABLE_NAME}` db['CH'].update(truncateLatestTableSQL) } // 4.2 插入该表的初始数据 const initialLatestTableDataSQL = `insert into ${LATEST_TABLE_NAME} select * from ${TEMP_TABLE_NAME}` db['CH'].update(initialLatestTableDataSQL) // 五、清理临时表 const dropTempTableSQL = `drop table ${TEMP_TABLE_NAME}` db['CH'].update(dropTempTableSQL) 02手工同步用户类型数据/** * 本接口用于手工临时同步数据用,日常使用定时任务自动同步操作即可 */ import '@/statForProduction/userTypeStat/saveToTemporaryTable' as saveToTemporaryTable import '@/statForProduction/userTypeStat/updateUserTypeData' as updateUserTypeData saveToTemporaryTable() updateUserTypeData()添加定时任务本任务用到的部分 Clickhouse SQL-- 判断数据表是否存在 SELECT 1 FROM system.tables WHERE database = 'dw' AND name = 'temp_user_type_session' -- 根据user_type 表创建一张名为 temp_user_type_session 的临时表 CREATE TABLE temp_user_type_session as user_type; -- 清空某数据表中的所有内容 truncate table temp_user_type_session; -- 查询所有用户最新的用户类型数据 SELECT user_type.user_id uid,user_type.present_type ,user_type.pay_type ,user_type.group_type,user_type.start_date,user_type.end_date,user_type.uni_key FROM user_type, (SELECT user_type.user_id uid2,max(user_type.start_date) AS latestDate FROM user_type GROUP BY user_type.user_id) AS temp WHERE user_type.start_date = temp.latestDate and uid = temp.uid2; -- 获取有差异的数据 select tutl.*,tuts.user_id user_id2, tuts.present_type present_type2, tuts.pay_type pay_type2, tuts.group_type group_type2, tuts.start_date start_date2,tuts.uni_key uni_key2 from temp_user_type_latest tutl left join temp_user_type_session tuts on tutl.user_id =tuts.user_id where tutl.present_type != tuts.present_type or tutl.pay_type != tuts.pay_type or tutl.group_type != tuts.group_type;
2024年04月07日
5 阅读
0 评论
0 点赞
2024-02-28
酷壳(coolshell.cn)镜像站建设经验分享之五——镜像站制作
为了能保障镜像站能永久提供服务,不依赖于后端的数据库及中间件,我们需要将爬取到的正文内容做静态化发布,静态化发布后,我们可以将内容托管到 Gitee 或 Github。
2024年02月28日
24 阅读
0 评论
0 点赞
2024-02-28
酷壳(coolshell.cn)镜像站建设经验分享之四——解析文章正文html中的图片并离线下载图片到本地
上一步我们已经得到了文章详情所需的数据,要想保障酷壳站停掉后,我们仍然能完整的查看内容,我们需要将正文中的图片也都下载到本地,然后后续发布成镜像站的时候就不用外链图片资源了,这样也在最大程度保障原站无法访问的情况下,能通过镜像站完整的浏览博客。
2024年02月28日
6 阅读
0 评论
0 点赞
2024-02-28
酷壳(coolshell.cn)镜像站建设经验分享之三——分析爬取的文章html得到正文及相关的元数据
这个过程主要是分析文章 html,得到文章如下元数据:正文内容、作者、发布时间、分类列表、Tag词列表、浏览次数、评论次数,并将相关元数据存储到 crawler_article 表的对应字段里。
2024年02月28日
5 阅读
0 评论
0 点赞
2024-02-28
酷壳(coolshell.cn)镜像站建设经验分享之二——整站内容爬取
在前面的数据准备章节咱们已经在 magic-boot 基础工程上整合了 jsoup,而 jsoup 正是 java 领域数据爬取及 html 解析的神器,对于 html 的处理如同 JS 中的 jQuery 一般神一样的存在,而且一些 API 跟 jQuery 也比较接近,使用起来不得不说那叫一个丝滑。废话少说,咱们开始 show me the code。
2024年02月28日
3 阅读
0 评论
0 点赞
2024-02-25
酷壳(coolshell.cn)镜像站建设经验分享之一——准备工作
看酷壳网的一些评论,也有不少人像我一样,在担心以后域名或服务器到期后网站不能访问,为了解决这个后顾之忧,我便在工作之余开启了酷壳网站镜像之路,这一系列的文章也就是将这一过程做一个记录,也便于帮助有需要的朋友能够做到举一反三。
2024年02月25日
21 阅读
0 评论
0 点赞
2024-02-21
magic-boot 整合 Clickhouse 及在 magic-api 中的基本使用
项目有用到 Clickhouse 作为数仓, magic-boot 作为万金油般的存在,肯定是需要整合 Clickhouse 获取数据的,下面我们就开始吧。一、整合 clickhouse-jdbc 驱动根据clickhouse 官方文档的指引,在项目的 Maven 依赖管理文件(pom.xml)中的 dependencies 节点添加如下依赖项: <dependency> <groupId>com.clickhouse</groupId> <artifactId>clickhouse-jdbc</artifactId> <classifier>all</classifier> <version>0.6.0</version> </dependency>注:dependency 中一定要添加 <classifier>all</classifier>,否则会出现找不到依赖的className的异常二、magic-api 中添加数据源在 magic-api 主界面右侧的 DataSource 面板中,单击「+」按钮,打开「创建数据源」弹出层,如下图所示:相关表单项填写如下:名称:任意,只要自己能区别数据源即可Key:为便于在代码中引用,尽量采用简写URL:jdbc:(ch|clickhouse)[:<protocol>]://endpoint1,endpoint2,...?param1=value1¶m2=value2用户名:用户名密码:密码驱动类:com.clickhouse.jdbc.ClickHouseDriver类型:com.zaxxer.hikari.HikariDataSource。用Hikari 和 Druid 连接池测试都没碰到问题。本次测试填写后的连接池示例如下图所示:在 magic-api 中写测试代码进行功能验证创建数据表db['CH'].update(""" CREATE TABLE test_for_magic_boot ( `id` UUID, `user_name` String, `real_name` String, `birthday` Date, `gender` String ) ENGINE = MergeTree ORDER BY birthday SETTINGS index_granularity = 8192; """);添加测试数据// 添加数据要使用 update方法,使用insert 方法会报错。 // https://gitee.com/ssssssss-team/magic-api/issues/I4SQYW db['CH'].update(`insert into test_for_magic_boot(id,user_name,real_name,birthday,gender) values(#{uuid()},'shiyu', '时羽','1991-12-15', 'F'),(#{uuid()},'lint', '李宁涛','1985-11-19', 'M'),(#{uuid()},'gaowz', '高文中','1968-01-23', 'M')`)修改测试数据db['CH'].update(`update test_no_index set real_name='时大款' where user_name='shiyu'`) Clickhouse 更新操作有一些限制索引列不能进行更新分布式表不能进行更新不适合频繁更新或point更新查询数据return db['CH'].select('select * from test_for_magic_boot')删除数据db['CH'].update(`delete from test_for_magic_boot where user_name='lint'`)删除测试数据表db['CH'].update('drop table test_for_magic_boot');
2024年02月21日
32 阅读
0 评论
0 点赞
2024-01-04
[转载]你写文档吗?你为什么应该写文档?
本文主要是结合作者的项目实践来说明文档对于一个团队开发的重要性, 以及在提高效率节省时间方面的意义, 并且指出如何在实践开发中写文档与维护文档.
2024年01月04日
3 阅读
0 评论
0 点赞
2023-12-26
推荐一款统计代码行数的VSCode插件
基本介绍在撰写软著文档或做项目结项时我们一般都需要统计工程的代码行数,如果逐个文件进行统计的话,不仅费时费力,还不一定能得到准确的数据,所以我就在做这工作的时候找了下相关的工具,发现 VSCode 有一款好用的插件:Lines of Code(LOC)插件市场链接:https://marketplace.visualstudio.com/items?itemName=lyzerk.linecounterGithub 链接:https://github.com/alimozdemir/vscode-linecounter该插件当前的介绍信息见截图:基本使用安装后,可使用 Ctrl+Shift+P 打开 命令面板,然后输入 LineCount,显示如下选项:相关选项对应的是:LineCount:Count Workspace files:统计当前工作区所有文件的代码行数LineCount:Count current file:统计当前文件的代码行数选择 LineCount:Count Workspace files 后,系统将进行相关统计操作,统计完后会在当前工程的out目录生成 linecounter.txt 和 linecounter.json 文件,其中 linecounter.txt 文件内容如下:=============================================================================== EXTENSION NAME : linecounter EXTENSION VERSION : 0.2.7 ------------------------------------------------------------------------------- count time : 2023-12-27 09:21:44 count workspace : d:\GitRoot\SE\console\console-ui total files : 563 total code lines : 72734 total comment lines : 3925 total blank lines : 3554 statistics | extension| total code| total comment| total blank|percent| ------------------------------------------------------------------------- | .devIstio| 44| 2| 7| 0.060| | .stage| 34| 0| 0| 0.047| | .production| 41| 0| 0| 0.056| | | 193| 74| 42| 0.27| | .development| 36| 0| 0| 0.049| | .js| 13156| 1998| 752| 18| | .html| 2347| 6| 333| 3.2| | .md| 1660| 243| 419| 2.3| | .svg| 1743| 0| 107| 2.4| | .json| 3403| 7| 5| 4.7| | .yaml| 4153| 17| 626| 5.7| | .stg| 9| 2| 5| 0.012| | .blsc| 10| 2| 7| 0.014| | .conf| 103| 5| 27| 0.14| | .key| 30| 0| 0| 0.041| | .yml| 71| 0| 4| 0.098| | .crt| 28| 0| 0| 0.038| | .scss| 1778| 83| 312| 2.4| | .vue| 43126| 1467| 715| 59| | .css| 769| 19| 193| 1.1| ------------------------------------------------------------------------- .browserslistrc, code is 3, comment is 0, blank is 0. .editorconfig, code is 9, comment is 0, blank is 1. .env, code is 19, comment is 0, blank is 0. .env.blsc.development, code is 20, comment is 0, blank is 0. .env.blsc.devIstio, code is 20, comment is 0, blank is 0. .env.blsc.production, code is 23, comment is 0, blank is 0. .env.blsc.stage, code is 21, comment is 0, blank is 0. .env.development, code is 16, comment is 0, blank is 0. .env.devIstio, code is 14, comment is 0, blank is 0. .env.production, code is 18, comment is 0, blank is 0. .env.stage, code is 13, comment is 0, blank is 0. .eslintignore, code is 1, comment is 4, blank is 0. .eslintrc.js, code is 196, comment is 129, blank is 0. .gitignore, code is 20, comment is 0, blank is 2. .prettierrc, code is 6, comment is 0, blank is 0. admin.html, code is 21, comment is 2, blank is 0. cspell.json, code is 96, comment is 7, blank is 2. default.conf, code is 14, comment is 0, blank is 2. ... 统计的文件列表 src\views\userCenter\modals\WarningCcLimitModal.vue, code is 218, comment is 5, blank is 1. vite.config.js, code is 183, comment is 30, blank is 5. ===============================================================================从上面的信息不难看出当前工程内的文件总数及总代码行数, 包括注释行数及空行数。其中该插件有不少配置信息,可以在统计钱根据实际情况进行文件排除等场景,配置信息示例数据如下:{ "LineCount.showStatusBarItem": true, "LineCount.statistics": true, "LineCount.includes": [ "**/*" ], "LineCount.excludes": [ "**/.vscode/**", "**/node_modules/**" ], "LineCount.output": { "txt": true, "json": true, "csv": true, "md": true, "outdir":"out" }, "LineCount.sort": "filename", "LineCount.order": "asc", "LineCount.comment":[ { "ext": ["c","cpp","java"], "separator": { "linecomment": "//", "linetol":false, "blockstart": "/*", "blockend": "*/", "blocktol": false, "string":{ "doublequotes": true, "singlequotes": true } } }, { "ext": ["html"], "separator": { "blockstart": "<!--", "blockend": "-->", } } ] }
2023年12月26日
89 阅读
0 评论
0 点赞
2023-12-13
Jeepay开源版使用过程中踩过的坑
1、商户系统登录问题添加商户的时候有设置登录名,但是没有设置账号密码的位置,好不容易找到对商户重置密码的地方,但是那个勾选重置密码的复选框又超级容易被理解为用户下次登录需重置密码的配置项。勾选后有提示重置为默认密码,但是又没有说明默认密码是什么,然后非得要查看源码才知道是通过常量设置的默认密码为:jeepay6662、证书文件不存在问题好不容易登录商户系统了,进行支付测试的功能验证,提示证书文件不存在:整个应用部署过程完全是基于官方提供的 docker-compose.yml 文件,最后发现默认配置的 /home/jeepay/upload 目录根本就没有挂载到宿主机,修改 docker-compose.yml ,payment、manager、merchant 应用的 volumes 应用均挂载 /home/jeepay 目录,如: volumes: - ./jeepayData:/home/jeepay3、支付测试不显示二维码的问题支付测试时不显示支付二维码,发现HTTP请求中有个 404 请求:检查代码,确定应用存在对应的接口路径:查看docker 日志复现如下 error 信息:基于该信息可得知,nginx在接收到二维码图片请求时根本就没有请求到 jeepay-payment 这个后端服务,而是直接请求了root 目录中的文件,由此我们调整一下 代理的api接口的优先级,修改 jeepay-ui 根目录下的 default.conf.template 文件,在/api/ 前添加 ^~ ,nginx的路径匹配规则如下:/:通用匹配,任何请求都可以匹配=:用于不含正则表达式的uri前,要求请求字符串与uri严格匹配,如果匹配成功,就停止继续向下搜索并立即处理该请求。~:用于表示uri包含正则表达式,并且区分大小写。~*:用于表示uri包含正则表达式,并且不区分大小写。^~:用于不包含正则表达式的uri前,要求nginx服务器找到标识uri和请求字符串匹配度最高的location后,立即使用此location处理请求,而不再使用location块中的正则uri与请求字符串做匹配。!~和!~*:分别表示区分大小写不匹配和不区分大小写不匹配的正则优先级:= --> ^~ --> /* #当有多个包含/进行正则匹配时,选择正则表达式最长的location配置执行。多个location配置的情况下匹配顺序为: 首先匹配 =,其次匹配^~, 其次是按文件中顺序的正则匹配,最后是交给 /通用匹配。当有匹配成功时候,停止匹配,按当前匹配规则处理请求。注意:如果uri包含正则表达式,则必须要有 ~ 或者 ~ 标识。修改后的 default.conf.template 文件如下所示:server { listen 80; listen [::]:80; server_name localhost; root /workspace/; try_files $uri $uri/ /index.html; location ^~ /api/ { proxy_next_upstream http_502 http_504 error timeout invalid_header; proxy_pass http://$BACKEND_HOST; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade"; } # favicon.ico location = /favicon.ico { log_not_found off; access_log off; } # robots.txt location = /robots.txt { log_not_found off; access_log off; } # assets, media location ~* \.(?:css(\.map)?|js(\.map)?|jpe?g|png|gif|ico|cur|heic|webp|tiff?|mp3|m4a|aac|ogg|midi?|wav|mp4|mov|webm|mpe?g|avi|ogv|flv|wmv)$ { expires 7d; access_log off; } # svg, fonts location ~* \.(?:svgz?|ttf|ttc|otf|eot|woff2?)$ { add_header Access-Control-Allow-Origin "*"; expires 7d; access_log off; } # gzip gzip on; gzip_vary on; gzip_proxied any; gzip_comp_level 6; gzip_types text/plain text/css text/xml application/json application/javascript application/rss+xml application/atom+xml image/svg+xml; } 4、公众号/小程序支付的URL多了一级/cashier应用部署完毕,进行支付测试时,采用「微信支付二维码」的方式已支付成功,但是采用「公众号/小程序」的支付方式,在扫码后,发现扫码后的页面显示空白,进一步排查,发现是由于页面的css和js资源文件404导致的,进一步排查,是由于请求的资源多了一级path,一下是问题排查过程:系统配置中的支付网关地址填写的是 https://jeepay-cashier.work.zhuzhilong.com:但是在使用支付测试功能,支付方式采用「公众号/小程序」进行支付测试时,生成的二维码如下:二维码识别后的地址为:https://jeepay-cashier.work.zhuzhilong.com/cashier/index.html#/hub/78d439f3140fe4047c7f8f6cda1048313636890021b83c0c167270dbce4fc2ff根据应用部署情况,比预期的访问路径多了 /cashier,查看源代码后,发现这个路径是写死在 com.jeequan.jeepay.core.model.DBApplicationConfig.java中的:去掉相关方法中的 /cashier 后,根据 docker-compose.yml 重新构建镜像及重启服务后,可正常支付。
2023年12月13日
15 阅读
0 评论
0 点赞
2023-10-31
magic-boot/magic-api 使用随记
magic-api 是一款非常优秀的快速开发框架,在做大屏的过程中找到的宝贝应用,可以用类JS 语法快速开发接口,能非常方便的操作数据库及处理一些复杂的业务逻辑,而 magic-boot 是基于 magic-api 开发的一款快速开发平台,提供了基本的用户鉴权、后台管理等功能。在实际项目过程中我基于 magic-boot 做了如下事项:1、通过Matomo的API定时同步数据至数仓做大数据分析2、采集coolshell.cn整站数据3、每周五定时推送企业微信消息,提醒同事写周报更多功能待进一步挖掘……下面是我在项目中有用到的技术点的一个记录,会在项目过程中不断更新,便于后续有其他项目用到的话,能快速查找运用。获取 系统设置/配置中心 模块设置的配置项import '@/configure/getBykey' as configure; var baseURL = configure('matomo.base-url'); var authToken = configure('matomo.auth-token');http请求数据示例import cn.hutool.json.JSONUtil import org.springframework.util.StringUtils import http; import log; import '@/configure/getBykey' as configure; // 从配置中心获取接口所需数据 var baseURL = configure('matomo.base-url'); var authToken = configure('matomo.auth-token'); // 组转请求URL var reqURL = `${baseURL}?module=API&method=${method}&format=JSON&token_auth=${authToken}`; if (!StringUtils.isEmpty(params)) { reqURL += `&${params}`; } log.info(`reqURL:${reqURL}`); // 请求数据 var resData = http.connect(reqURL).contentType('application/json').get().getBody(); return resData;在原基础上增加http请求出错重试机制import cn.hutool.json.JSONUtil import org.springframework.util.StringUtils import cn.hutool.core.date.DateUtil import cn.hutool.core.thread.ThreadUtil import http import log import '@/configure/getBykey' as configure; // 从配置中心获取接口所需数据 var baseURL = configure('matomo.base-url'); var authToken = configure('matomo.auth-token'); // 组转请求URL var reqURL = `${baseURL}?module=API&method=${method}&format=JSON&token_auth=${authToken}`; if (!StringUtils.isEmpty(params)) { reqURL += `&${params}`; } // log.info(`reqURL:${reqURL}`); var requestStartTime = DateUtil.now(); var successFlag = '' var exceptionContent = '' // 请求数据 var resData = '' // 最大重试次数 var MAX_RETRY_COUNT = 5 // 当前重试次数 var retryCount = 0; while(retryCount < MAX_RETRY_COUNT && successFlag !== 'Y') { try { resData = http.connect(reqURL).contentType('application/json').get().getBody(); successFlag = 'Y' } catch(e) { successFlag = 'N' ThreadUtil.sleep(1000); retryCount++ exceptionContent = e.getMessage(); } } var requestEndTime = DateUtil.now(); db.table('matomo_sync_log').insert({ 'apiMethod' : method, 'requestParams' : params, 'responseContent': resData.asString(), 'exceptionContent': exceptionContent, 'requestTime':requestStartTime, 'responseTime': requestEndTime, 'retryCount':retryCount, 'successFlag': successFlag}); return resData;分页获取数据在获取一些详情数据的时候,存在数据量超大的情况,一次性获取所有数据极有可能会导致数据库及应用挂掉,即便不挂掉的情况下,也会超长事件才会响应结果,所以采用分页获取还是很有必要的。下面的代码是在实际项目中分页调用Matomo的接口获取输入然后将接口返回的数据,结构化处理后,保存到本地数据库。 import cn.hutool.json.JSONUtil import log; import '@/dmcfns/sendMatomoRequest' as getMatomoData; var PAGE_SIZE=10 // 每页获取记录数,获取后批量入库 var currentPage = 0// 当前页 var needLoad = true // 继续加载数据标识,当当前页加载的内容小于PAGE_SIZE时则不再加载 while(needLoad) { var resData = getMatomoData('Live.getLastVisitsDetails', `period=day&date=${date}&idSite=${siteId}&doNotFetchActions=1&filter_offset=${PAGE_SIZE * currentPage}&filter_limit=${PAGE_SIZE}`) if (resData.asString().startsWith("[")) { var siteDatas = JSONUtil.parseArray(resData); var siteData = []; for (index, site in siteDatas) { var siteObj = siteDatas.getJSONObject(index); var visitId = siteObj.getStr("idVisit"); var visitorId = siteObj.getStr("visitorId"); var visitIp = siteObj.getStr("visitIp"); var longitude = siteObj.getStr("longitude"); var latitude = siteObj.getStr("latitude"); var userId = siteObj.getStr("userId"); var country = siteObj.getStr("country"); var referrerName = siteObj.getStr("referrerName"); var visitProps = JSONUtil.toJsonStr(siteObj); db.table('matomo_daily_visit').insert({ date, siteId, visitId, visitorId, visitIp, longitude, latitude, userId, country, referrerName, visitProps }) } if (siteDatas.size() === PAGE_SIZE) { currentPage++ } else { needLoad = false } } }使用多数据源操作数据库db['ZR'].table('crawler_list').insert({ pageURL: linkURL, articleTitle:linkTitle })根据主键更新部分字段内容var updateMap = { id: visitItem.id, ipCountry: country, ipProvince: province, ipCity: city } db.table('matomo_daily_visit').primary('id').update(updateMap)修改某个字段的值db.table("sys_user").column("isLogin", isLogin).where().eq("id",id).update()推送消息至企业微信机器人import http import log // 测试机器人 // var ROBOT_URL = 'https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=xxx' // 超算云研发部2023 微信群的eHour 机器人 var ROBOT_URL = 'https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=xxxx' var msg = { "msgtype": "text", "text": { "content": """为便于公司开展项目成本核算相关工作,请各位同事及时登录eHour系统录入本周工作工时,如有系统使用相关问题可联系 XXX,感谢配合[抱拳][抱拳]\neHour系统链接如下:http://172.18.3.xxx/""", "mentioned_list":["@all"] } } http.connect(ROBOT_URL).body(msg).post(); log.info('eHour消息推送成功') // 以下cron表达式为每周五16:30分执行 00 30 16 * * 05读取Excel文件并转换为jsonimport cn.hutool.poi.excel.ExcelUtil import request import log var datas = ExcelUtil.getReader(new ByteArrayInputStream(request.getFile('file').getBytes())).readAll() var sourceDatas = datas::stringify::json
2023年10月31日
34 阅读
0 评论
0 点赞
2023-10-30
docker compose 部署Umami
很长一段时间是用的cnzz做的网站访问统计,功能强大,分析结果对于小白用户也超级友好,自从被阿里收购后,整合成Umeng的一部分勉强还能用,但是自从开启收费(收割用户)模式后,高昂的价格,无疑把我们这种小白个人用户完全隔离在外了。然后用了一段时间的百度统计,感觉也是不尽如人意,然后就只好另辟蹊径,调研了市面主流的流量统计工具(也就调研了Matomo 和Umami)后,选择了Umami 作为个人流量统计工具,主要是Matomo不少明细数据是存储的Binary数据,不便于通过SQL直观的查看,相对于Matomo而言,Umami 算是轻量级别的,UI 界面也更现代化。Umami 支持 PostgreSQL 和 MySQL 两种数据库,分别对应不同的Docker 镜像。ProstgreSQL:docker pull ghcr.io/umami-software/umami:postgresql-latestMySQL:docker pull ghcr.io/umami-software/umami:mysql-latest由于我的服务器上面已安装 MySQL 客户端,就直接采用 MySQL 的镜像。ghcr.io 是 GitHub 的 Docker 镜像仓库,国内环境可能在pull 时会碰到些网络方面的问题,我是通过一台境外的服务器pull 后,然后 push 到本人的 Docker 私服进行下载的,也可以采用导出备份后在导入的方式。如果你在pull过程中也存在这方面网络的问题的话,也推荐使用这个方式。docker-compose.ymlversion: "3.8" services: umami: image: ghcr.io/umami-software/umami:mysql-latest # image: hub.work.zhuzhilong.com/apps/umami:mysql container_name: umami restart: unless-stopped volumes: - ../hosts:/etc/hosts environment: - DATABASE_URL=mysql://DB_USERNAME:DB_PASSWORD@DB_HOST:DB_PORT/umami - DATABASE_TYPE=mysql - APP_SECRET=umami2023 - TZ=Asia/Shanghai networks: - net-zzl ports: - 8202:3000 networks: net-zzl: name: bridge_zzl external: true 使用 docker compose up -d 启动后,可使用默认的管理员账号登录:用户名:admin密码:umami登录后即可修改密码及添加站点了。以下是整合后的部分界面截图:顺便说下,umami的表结构比较简单,访问用户的IP信息都没有存表,如果有复杂运营场景的话,还是推荐使用 Matomo 之类的功能更强大的工具。当前版本(2.8.0)只有11张表:{mtitle title="2023-12-18更新"/}应用升级近日登录umami时提示最新发布了2.9.0 版本,而根据更新日志中的内容有提到可以查看访客的城市信息了,便及时更新了下,使用docker compose 的方式更新超级简单,主要执行如下命令:docker compose pull docker compose up --force-recreate提示数据库更新成功:然后重启应用即可
2023年10月30日
43 阅读
0 评论
0 点赞
2023-10-30
docker compose 部署 中微子代理(NeutrinoProxy)
近期在开源中国有看到 Neutrino-proxy 的一些介绍,了解到 NeutrinoProxy 是一款基于 Netty 的内网穿透工具,官方的介绍信息如下:基本介绍中微子代理 (neutrino-proxy) 是一款基于 netty 的内网穿透神器。该项目采用最为宽松的 MIT 协议,因此您可以对它进行复制、修改、传播并用于任何个人或商业行为。Gitee 地址:https://gitee.com/dromara/neutrino-proxy官网地址:http://neutrino-proxy.dromara.org服务端管理后台截图:主要特点:1、流量监控:首页图表、报表管理多维度流量监控。全方位掌握实时、历史代理数据。2、用户 / License:支持多用户、多客户端使用。后台禁用实时生效。3、端口池:对外端口统一管理,支持用户、License 独占端口。4、端口映射:新增、编辑、删除、禁用实时生效。5、Docker:服务端支持 Docker 一键部署。6、SSL 证书:支持 SSL,保护您的信息安全。7、域名映射:支持绑定子域名,方便本地调试三方回调8、采用最为宽松的 MIT 协议,免去你的后顾之忧之前一直有在用 FRP 作为内网穿透工具,用了很多年也确实非常好用,不过 FRP 在可视化管理方面比较欠缺,虽然提供了dashboard,但是只提供了代理端口查看及浏览统计方面的功能,不能提供多用户方面的管控。而 Neutrino 正好弥补了这方面的不足。以下是使用 docker compose 部署相关的代码,仅作为记录。服务端:docker-compose.ymlversion: '3.8' services: app: image: registry.cn-hangzhou.aliyuncs.com/asgc/neutrino-proxy:latest container_name: neutrino-proxy restart: always networks: - net-zzl ports: - 9000-9200:9000-9200/tcp - 9201:8888 volumes: - ./config:/root/neutrino-proxy/config networks: net-zzl: name: bridge_zzl external: true ./config/app.ymlneutrino: data: db: type: mysql # 自己的数据库实例,创建一个空的名为'neutrino-proxy'的数据库即可,首次启动服务端会自动初始化 url: jdbc:mysql://DB_HOST:3306/neutrino-proxy?useUnicode=true&characterEncoding=UTF-8&allowMultiQueries=true&useAffectedRows=true&useSSL=false driver-class: com.mysql.jdbc.Driver # 数据库帐号 username: DB_USERNAME # 数据库密码 password: DB_PASSWORD客户端官网文档不推荐使用docker的方式部署,但是考虑到要部署Java 环境之类的,对宿主机而言是挺麻烦的,还是试着通过docker 的方式部署的客户端,经过验证也是 OK 的。docker-composeversion: '3.8' services: app: image: aoshiguchen/neutrino-proxy-client:latest container_name: neutrino-proxy-client restart: always network_mode: host volumes: - ./config:/root/neutrino-proxy/config./config/app.ymlneutrino: proxy: logger: # 日志级别 level: ${LOG_LEVEL:info} tunnel: # 线程池相关配置,用于技术调优,可忽略 thread-count: 50 # 隧道SSL证书配置 key-store-password: ${STORE_PASS:123456} jks-path: ${JKS_PATH:classpath:/test.jks} # 服务端IP,这里替换成主机IP或域名 server-ip: ${SERVER_IP:proxy.xxx.com} # 服务端端口(对应服务端app.yml中的tunnel.port、tunnel.ssl-port) server-port: ${SERVER_PORT:9002} # 是否启用SSL(注意:该配置必须和server-port对应上) ssl-enable: ${SSL_ENABLE:true} # 客户端连接唯一凭证,这里替换成key license-key: ${LICENSE_KEY:ec7e9906cXXXXXX6430895c37fec75cd4e11} # 客户端唯一身份标识(可忽略,若不设置首次启动会自动生成) client-id: ${CLIENT_ID:workServer} # 是否开启隧道传输报文日志(日志级别为debug时开启才有效) transfer-log-enable: ${CLIENT_LOG:false} # 重连设置 reconnection: # 重连间隔(秒) interval-seconds: 10 # 是否开启无限重连(未开启时,客户端license不合法会自动停止应用,开启了则不会,请谨慎开启) unlimited: false client: udp: # 线程池相关配置,用于技术调优,可忽略 boss-thread-count: 5 work-thread-count: 20 # udp傀儡端口范围 puppet-port-range: 10000-10500 # 是否开启隧道传输报文日志(日志级别为debug时开启才有效) transfer-log-enable: ${CLIENT_LOG:false}实现后的效果截图:
2023年10月30日
29 阅读
0 评论
0 点赞
2023-10-19
Mybatis-Plus 分页功能实现流程
Mybatis 自带分页功能,但是该分页功能是基于内存的分页,也就是会讲所有符合条件的数据查询出来,然后在从内存中获取当前页的信息,这种方式在数据量大的情况下会存在严重的性能问题。我们通过 Mybatis-Plus 自带的分页插件可以很好的解决这个问题,实现步骤记录如下:1、 添加配置类,示例内容如下:package com.paratera.protect.config; import com.baomidou.mybatisplus.annotation.DbType; import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor; import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; /** * Mybatis Plus 配置类,主要用于继承分页插件 * @author 朱治龙 * @date 2023-10-19 23:14:00 */ @Configuration public class MyBatisPlusConfig { @Bean public MybatisPlusInterceptor mybatisPlusInterceptor() { MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor(); // 分页插件 interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL)); return interceptor; } } 2、使用分页方法查询2.1、selectPage2.1.1 示例代码 @Test void testSelectPage() { QueryWrapper<Staff> qw = Wrappers.query(); qw.ge("age", 30); Page<Staff> page = new Page<Staff>(1, 2); // 不查记录数 // page.setSearchCount(false); Page<Staff> pageData = staffMapper.selectPage(page, qw); System.out.println("总页数:" + pageData.getPages()); System.out.println("总记录数:" + pageData.getTotal()); pageData.getRecords().forEach(System.out::println); }2.1.2 查询结果DEBUG==> Preparing: SELECT COUNT(*) AS total FROM staff WHERE (age >= ?) DEBUG==> Parameters: 30(Integer) TRACE<== Columns: total TRACE<== Row: 4 DEBUG<== Total: 1 DEBUG==> Preparing: SELECT id,name,age,email,mobile,manager_id,create_time FROM staff WHERE (age >= ?) LIMIT ? DEBUG==> Parameters: 30(Integer), 2(Long) TRACE<== Columns: id, name, age, email, mobile, manager_id, create_time TRACE<== Row: 1087982257332887553, 大boss, 40, 001@paratera.com, 13888886666, null, 2019-01-11 14:20:20 TRACE<== Row: 1094590409767661570, 张雨琪, 31, zjq@blsc.com, 18684700070, 1088248166370832385, 2019-01-14 09:15:15 DEBUG<== Total: 2 总页数:2 总记录数:4 Staff(id=1087982257332887553, name=大boss, age=40, email=001@paratera.com, mobile=13888886666, managerId=null, createTime=2019-01-11T14:20:20) Staff(id=1094590409767661570, name=张雨琪, age=31, email=zjq@blsc.com, mobile=18684700070, managerId=1088248166370832385, createTime=2019-01-14T09:15:15) 2.2、selectMapsPage2.2.1 示例代码 @Test void testSelectMapsPage() { QueryWrapper<Staff> qw = Wrappers.query(); qw.ge("age", 30); Page<Map<String, Object>> page2 = new Page<>(1, 2); Page<Map<String, Object>> pageData2 = staffMapper.selectMapsPage(page2, qw); System.out.println("总页数:" + pageData2.getPages()); System.out.println("总记录数:" + pageData2.getTotal()); pageData2.getRecords().forEach(System.out::println); }2.2.2 查询结果DEBUG==> Preparing: SELECT COUNT(*) AS total FROM staff WHERE (age >= ?) DEBUG==> Parameters: 30(Integer) TRACE<== Columns: total TRACE<== Row: 4 DEBUG<== Total: 1 DEBUG==> Preparing: SELECT id,name,age,email,mobile,manager_id,create_time FROM staff WHERE (age >= ?) LIMIT ? DEBUG==> Parameters: 30(Integer), 2(Long) TRACE<== Columns: id, name, age, email, mobile, manager_id, create_time TRACE<== Row: 1087982257332887553, 大boss, 40, 001@paratera.com, 13888886666, null, 2019-01-11 14:20:20 TRACE<== Row: 1094590409767661570, 张雨琪, 31, zjq@blsc.com, 18684700070, 1088248166370832385, 2019-01-14 09:15:15 DEBUG<== Total: 2 总页数:2 总记录数:4 {create_time=2019-01-11T14:20:20, name=大boss, mobile=13888886666, id=1087982257332887553, age=40, email=001@paratera.com} {create_time=2019-01-14T09:15:15, manager_id=1088248166370832385, name=张雨琪, mobile=18684700070, id=1094590409767661570, age=31, email=zjq@blsc.com} 3、附加说明使用分页插件查询时默认是会执行两条 SQL,一条获取当前页的数据,一条获取总记录数。在某些场景下(如瀑布流模式),只需要获取当前页的内容即可,不需要总记录数相关分页数值,此时可在创建 Page 时,第三个参数给值为 false:Page(long current, long size, boolean searchCount)。也可使用 page.setSearchCount(false);
2023年10月19日
39 阅读
0 评论
0 点赞
2023-10-18
Mybatis-Plus 自定义SQL
有的时候使用条件构造器自定义SQL满足不了我们的需求,我们既想使用 Wrapper,又想使用SQL,MP 对这种方式也提供了支持,MP 版本号应≥3.0.7。下面是该方案的实现记录:实现方案一:Mapper接口中使用@Select注解1、Mapper 示例代码如下:import com.baomidou.mybatisplus.core.conditions.Wrapper; import com.baomidou.mybatisplus.core.mapper.BaseMapper; import com.baomidou.mybatisplus.core.toolkit.Constants; import com.paratera.protect.entity.Staff; import org.apache.ibatis.annotations.Param; import org.apache.ibatis.annotations.Select; import java.util.List; /** * @author 朱治龙 * @date 2023-10-17 11:46:00 */ public interface StaffMapper extends BaseMapper<Staff> { @Select("select * from staff ${ew.customSqlSegment}") List<Staff> selectAll(@Param(Constants.WRAPPER)Wrapper<Staff> wrapper); }2、调用示例代码: @Test void testSelfSQL() { LambdaQueryWrapper<Staff> lqw = Wrappers.lambdaQuery(Staff.class); lqw.eq(Staff::getName, "朱治龙").and(wq2 -> wq2.lt(Staff::getAge, 40).or().isNotNull(Staff::getEmail)); List<Staff> staffList = staffMapper.selectAll(lqw); staffList.forEach(System.out::println); }3、输出结果DEBUG==> Preparing: select * from staff WHERE (name = ? AND (age < ? OR email IS NOT NULL)) DEBUG==> Parameters: 朱治龙(String), 40(Integer) TRACE<== Columns: id, name, age, email, mobile, manager_id, create_time TRACE<== Row: 1714166763984199681, 朱治龙, 36, zhuzl@blsc.cn, 15084978453, 1088248166370832385, 2023-10-17 14:29:38 DEBUG<== Total: 1 Staff(id=1714166763984199681, name=朱治龙, age=36, email=zhuzl@blsc.cn, mobile=15084978453, managerId=1088248166370832385, createTime=2023-10-17T14:29:38)实现方案二:使用xml1、配置xml文件的存放路径。再application.yml中添加 xml 文件的引用路径配置mybatis-plus: mapper-locations: - classpath:/mapper/*Mapper.xml2、在resources目录下添加 mapper 目录,并新建 mapper 文件,如文件名为 StaffMapper.xml,示例内容为:<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <mapper namespace="com.paratera.protect.dao.StaffMapper"> <select id="selectByXml" resultType="com.paratera.protect.entity.Staff"> select * from staff ${ew.customSqlSegment} </select> </mapper>3、Mapper 接口中添加方法,示例代码如下:package com.paratera.protect.dao; import com.baomidou.mybatisplus.core.conditions.Wrapper; import com.baomidou.mybatisplus.core.mapper.BaseMapper; import com.baomidou.mybatisplus.core.toolkit.Constants; import com.paratera.protect.entity.Staff; import org.apache.ibatis.annotations.Param; import java.util.List; /** * @author 朱治龙 * @date 2023-10-17 11:46:00 */ public interface StaffMapper extends BaseMapper<Staff> { List<Staff> selectByXml(@Param(Constants.WRAPPER)Wrapper<Staff> wrapper); }4、调用示例代码: @Test void testSelfSQL2() { LambdaQueryWrapper<Staff> lqw = Wrappers.lambdaQuery(Staff.class); lqw.eq(Staff::getName, "朱治龙").and(wq2 -> wq2.lt(Staff::getAge, 50).or().isNotNull(Staff::getEmail)); List<Staff> staffList = staffMapper.selectByXml(lqw); staffList.forEach(System.out::println); }5、输出结果DEBUG==> Preparing: select * from staff WHERE (name = ? AND (age < ? OR email IS NOT NULL)) DEBUG==> Parameters: 朱治龙(String), 50(Integer) TRACE<== Columns: id, name, age, email, mobile, manager_id, create_time TRACE<== Row: 1714166763984199681, 朱治龙, 36, zhuzl@blsc.cn, 15084978453, 1088248166370832385, 2023-10-17 14:29:38 DEBUG<== Total: 1 Staff(id=1714166763984199681, name=朱治龙, age=36, email=zhuzl@blsc.cn, mobile=15084978453, managerId=1088248166370832385, createTime=2023-10-17T14:29:38)
2023年10月18日
9 阅读
0 评论
0 点赞
2023-10-16
使用 TDesign Starter 初始化工程
背景介绍Java 开发有着丰富的生态,开源社区有非常多的快速开发平台,由于在之前的工作中有过开发公司自主研发的快速开发平台的经验,对开源生态相关的软件关注也比较多,国内常用的基于Java开发且有前后端完整代码的快速开发平台有: RuoYi、jeecg-boog、maku-boot、eladmin、小诺/Snowy、smart-admin。本次着重调研的是 magic-boot,这个快速开发平台,貌似算比较小众的,主要是在去年的工作中,有调研大屏展示应用,发现该团队开发的 magic-api非常的灵活好用,然后进一步了解该团队的开源作品,才发现的这块快速开发平台。经过初步了解,虽然这款快速开发平台的受众用户不多,但是用于快速开发,自带的功能已经非常齐备了。系统自带的ui工程:magic-boot-naive,是基于vue3 + vite + naive-ui 实现的,由于不怎么喜欢 naive-ui 的风格及布局,便打算基于 tdesign starter 搭建一套共用后端接口,且相同功能的前端界面。初始化TDesign 自带了一套TDesign Starter 及相关的脚手架工具(tdesign-starter-cli),基于该脚手架可以非常快速的创建一个带后台功能模块的前端工程。创建脚本如下:>td-starter init ***************************** _____ ____ _ ____ _ _ |_ _| | _ \ ___ ___ (_) __ _ _ __ / ___| | |_ __ _ _ __ | |_ ___ _ __ | | | | | | / _ \ / __| | | / _` | | '_ \ \___ \ | __| / _` | | '__| | __| / _ \ | '__| | | | |_| | | __/ \__ \ | | | (_| | | | | | ___) | | |_ | (_| | | | | |_ | __/ | | |_| |____/ \___| |___/ |_| \__, | |_| |_| |____/ \__| \__,_| |_| \__| \___| |_| |___/ ***************************** √ 构建环境正常! ? 请输入项目名称: magic-boot-ui-tdesign ? 请输入项目描述: another ui for magic-boot ? 选择模板类型: PC【Vue3】模板 ? 生成代码版本: 通用模板版本 ? 选择包含模块: 自定义选择 ? 选择您需要生成的模块内容 列表页 👉 开始构建,请稍侯... \ 正在构建模板... √ 构建成功! 👏 初始化项目完成!👏 命令提示: # 进入项目 $ cd ./magic-boot-ui-tdesign # 安装依赖 $ npm install # 运行 $ npm run dev初始化过程截图如下:程序运行后,访问 http://localhost:3002/ 显示如下图所示的界面则表示工程初始化OK.
2023年10月16日
23 阅读
0 评论
0 点赞
1
2
...
6