# springboot-vue **Repository Path**: javafdx/spriingboot-vue ## Basic Information - **Project Name**: springboot-vue - **Description**: springboot - **Primary Language**: Unknown - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 2 - **Forks**: 0 - **Created**: 2021-07-08 - **Last Updated**: 2023-05-29 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README ### 修改启动图案 banner.txt 生成图案的网址 http://patorjk.com/software/taag/#p=display&f=Graffiti&t=Type%20Something%20 ### 分层-Controller ### 新建一个接口类 1. @RestController 返回字符串 2. @Controller 返回页面 3. @ResponseBody 用来返回字符串或json对象 4. @RestController其实就是@Controller和@ResponseBody的结合 5. @RequestMapping 接口 如果只是简单的用@RequestMapping注解,表示这个接口支持所有的请求方式 (GET POST PUT DELETE) 6. 只支持GET请求用@GetMapping 以此类推@PostMapping @PutMapping @DeleteMapping 7. 或者可以: @RequestMapping(value = "/hello", method = RequestMethod.GET) ### 请求方法不对 页面报405 @PostMapping("/hello") 前端页面报错: `Whitelabel Error Page This application has no explicit mapping for /error, so you are seeing this as a fallback. Thu Jul 08 13:50:08 CST 2021 There was an unexpected error (type=Method Not Allowed, status=405).` 后台打印: Request method 'GET' not supported] ### 扫描包 @SpringBootApplication进入 @ComponentScan 这个注解作用:扫描项目所有的包 ### 重构启动类 新建config,将启动类移动到此文件夹下 hello接口就访问不到了-- 原因: @ComponentScan只会扫描这个类下面的子包(启动类被重构到了Config文件下了) 解决: 在启动类加注解--@ComponentScan("com.jiawa") 支持扫描多个包 {"包1", "包2"} jiawa不是正常的单词,可以保存到项目字典中,就不会有波浪线警告了 alt+enter 保存到字典 ### 常见前端报错404 Whitelabel Error Page This application has no explicit mapping for /error, so you are seeing this as a fallback. Thu Jul 08 13:58:02 CST 2021 There was an unexpected error (type=Not Found, status=404). ### 使用HTTP Client测试接口 --IDEA自带的测试接口 (总是用浏览器测试接口,会影响开发效率,浏览器不能发送post请求;postman工具也是要窗口切换) 1. 工具Tools-->HTTP Client 2. 新建文件夹http test.http 缩写gtr ptrp post请求 ### Live Templates IDEA设置项中: 对于常用的代码块,可以写成模板 ### 接受post请求参数 ` public String helloPost(String name){ return "post请求接受参数:"+ name; }` 直接写name参数,和http测试的参数名字一样,Sping会自动映射 ### 结构验证脚本 类似单元测试 `> {% client.test("test-hello", function() { client.log("测试/hello接口"); client.log(response.body); client.log(JSON.stringify(response.body)); // 虽然idea没有提示JSON,但是可以用 client.assert(response.status === 200, "返回码不是200"); client.assert(response.body === "Hello World", "结果验证失败"); }); %}` ### idea自动去掉不引用依赖 设置-->auto-->勾选Optimize 快速添加清晰的导入(Add unambiguous import on the fly) 即时优化导入(Optimize imports on the fly) ###配置文件application.properties 1. 下边两种文件方式,都能识别 config-->application.properties(重构) application.yml(改格式) server: port: 8882 yml格式网站:https://toyaml.com/index.html 2. 自定义配置: test.hello = Hello 使用配置项: @Value("${test.hello:TEST}") // :TEST默认配置值 private String testHello; 优先读取配置文件,没有就用默认值。 ### 集成热部署 1. 引入依赖 ` org.springframework.boot spring-boot-devtools ` 2. 配置idea 设置-->Complier(编译器)勾选Build project automatically(自动构建项目) 3. 查找操作-- Ctrl + Shift + Alt + / Registry(注册表) 勾选 compiler.automake.allow.when.app.running -> 自动编译 4.顶部菜单 Run- >Edit Configurations->SpringBoot插件->目标项目->勾选热更新。 5. 或者按ctrl+f9 工具栏-->小锤子键 ### 新建数据库 数据库名:wiki 字符集:utf8mb4 utf8mb4是真正的utf8,可以存放表情符号. utf8是伪utf8,三个字节,不支持表情符号 排序规则:utf8mb4_general_ci ### 新建数据库用户 不能使用超级用户 1. Navcat点击用户--新建用户 2. 配置常规 3. 配置权限-->添加权限-->选择数据库-->全部勾选 ### 链接数据库 1. idea打开数据库Database-->Data Source-->MySql 2. 首次链接需要下载驱动 drop table if exists `test`; create table `test` ( `id` bigint not null comment 'id', `name` varchar(50) comment '名称', primary key (`id`) ) engine=innodb default charset=utf8mb4 comment='测试'; ### 集成持久层Mybatis 跟数据库交互的这一层就叫持久层 1. ` org.mybatis.spring.boot mybatis-spring-boot-starter 2.1.3 mysql mysql-connector-java 8.0.22 ` 2. 配置数据源 application.properties(不配置,运行就会报错,因为mybatis会去读取配置) spring.datasource.url=jdbc:mysql://localhost:3306/wiki?characterEncoding=UTF8&autoReconnect=true&serverTimezone=Asia/Shanghai&allowMultiQueries=true spring.datasource.username=wiki spring.datasource.password=123456 spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver ---------------------- MySQL连接数据库时,添加语句:“allowMultiQueries=true”的作用: 1.可以在sql语句后携带分号,实现多语句执行。 2.可以执行批处理,同时发出多个SQL语句。 ------------------------- UTC代表的是全球标准时间 ,但是我们使用的时间是北京时区也就是东八区,领先UTC八个小时。 1. 常用设置 //北京时间东八区即GMT+8 serverTimezone=GMT%2B8 //或者使用上海时间 serverTimezone=Asia/Shanghai ---------------------------- autoReconnect 当数据库连接异常中断时,是否自动重新连接? ----------------------------- ### 创建实体类 可以叫:domain,或entity.或POJO 总之这一层实体类就是和数据库表一一映射。 快捷键: alt+insert 生成get,set ### 持久层Mapper层 持久层叫Mapper层。即广为人知的Dao层。因为后续要用官方代码生成器,其生成的代码就是xxxMapper 新建接口Interface-->TestMapper `public List list(); ` ### 新建sql脚本文件 1. resources-->mapper-->TestMapper.xml 不用去背 ` ` 2. 安装插件Free MyBatis plugin 用来代码跳转 3. 启动类添加注解--扫描注解,扫描持久层 @MapperScan("com.jiawa.wikiwork.mapper") 4. 配置application.properties # 配置mybatis所有Mapper.xml所在的路径 mybatis.mapper-locations=classpath:/mapper/**/*.xml ### service层--使用Mapper持久层 新建service-->TestService 使用@Service注解,将这个Service交给Spring来管理(扫描) 使用@Resource(jdk自带)或者@Autowired(spring自带) 引入TestMapper Controller层----写入 @Resource private TestService testService; 测试调用: GET http://localhost:8880/test/list Accept: application/json ### 集成MyBatis官方代码生成器 好处:单表的增删改查不用自己写代码; 1. 引入依赖 ` org.mybatis.generator mybatis-generator-maven-plugin 1.4.0 src/main/resources/generator/generator-config.xml true true mysql mysql-connector-java 8.0.22 ` 2. 新建generator-->generator-config.xml 直接粘贴代码,校对 数据库配置 ,和包名 ` ` 在数据库中增加一个demo表 3. 编辑运行配置:Run/Debug Configurations 增加一个Maven, Name:mybatis-generator Command line: mybatis-generator:generate -e 4. 运行:mybatis-generator 打印:BUILD SUCCESS 查看:自动生成-->(强烈提醒,四个文件不要修改) domian-->Demo,DemoExample mapper-->DemoMapper mapper-->DemoMapper.xml ### 新建DemoService ctrl + r 区分大小写,将 Test test 分别替换为 Demo demo `return demoMapper.selectByExample(null); // 查询所表所有数据 相当于where` ### 新建DemoController 同上,替换单词 新建测试demo.http 发送请求 ### 将url统一归属注解 @RequestMapping("/demo") ### 电子书列表接口开发 1. 创建ebook表,插入数据 2. mybatis生成代码 `
` 3. 新增EbookService,EbookController ### 规范返回值(统一格式) 后端会有很多接口,为了让前端能够统一处理逻辑(登录校验,权限校验), 需要统一后端的返回值。 1. 新建resp(response缩写) -->CommonResp(通用返回类) 2. 修改接口EbookController new CommonResp<>(); ctrl + alt +v 快速生成变量 小提示: 实际工作中,有些项目会在CommonResp里加上其他通用的属性, 比如接口版本号,返回码等 ### 分装请求参数节返回参数 1. 根据名称模糊查询电子书 模糊匹配:like 2. 修改Service ` EbookExample ebookExample = new EbookExample(); // createCriteria:相当于Where条件 ebookExample.createCriteria() EbookExample.Criteria criteria = ebookExample.createCriteria(); criteria.andNameLike("%" + name + "%"); return ebookMapper.selectByExample(ebookExample);` 测试接口: GET http://localhost:8880/ebook/list?name=Spring ### 请求的参数封装类 1. 新建req--(将Ebool类拷贝) 重命名快捷键(shift+f6) 修改参数名EbookReq req 2.同理修改返回的参数 新建resp-->EbookResp 修改Service 持久层返回List需要转成List,再返回controller ` List respList = new ArrayList<>(); // 两种for循环模板 fori,iter for (Ebook ebook : ebookList) { EbookResp ebookResp = new EbookResp(); //ebookResp.setId(ebook.getId()); // 不用这个方法,用下方的属性 // 从source拷贝到target ---方法ccopyProperties(source,target) BeanUtils.copyProperties(ebook, ebookResp); respList.add(ebookResp); }` ### 制作CopyUtil封装BeanUtils 为解决单个属性赋值和整个list的赋值 新建util-->CopyUtil 直接粘贴 使用: `List respList = CopyUtil.copyList(ebookList, EbookResp.class); // 列表赋值 EbookResp ebookResp = CopyUtil.copy(ebook, EbookResp.class); // 单个对象赋值` ------------------------ ### vue项目 vue create web 1. Manually select features 2. Choose Vue version, Babel, TypeScript, Router, Vuex, Linter/Formatter 3. 3.x 4. n n y 5. ESLint with error prevention only 6. >(*) Lint on save 7. > In dedicated config files 8. y ### 安装Ant-design npm install ant-design-vue@next --save @next:安装最新的未正式发布版本 单独安装图标库 npm install --save @ant-design/icons-vue 全局引用: `import * as Icons from '@ant-design/icons-vue'; // 全局使用图标 const icons: any = Icons; for (const i in icons) { app.component(i, icons[i]); } ` ### vue3语法 setup:记住他,Vue3新增的初始化方法 (没有vue2中的data和生命周期函数,methods,全部被setup方法给包括了) 请求不要直接放在setup中。setup执行的时候界面还没有渲染好,这时候如果去操作界面元素会报错 要引入onMounted并在setup中使用; `setup(){ onMounted(() => { }) }` ### 安装axios npm install axios --save `import axios from 'axios'; // 引入axios axios.get("GET http://localhost:8880/ebook/list?name=Spring").then((response) => { console.log('请求的结果') console.log(response) }) ` 会发生跨域错误: `Access to XMLHttpRequest at 'http://localhost:8880/ebook/list?name=Spring' from origin 'http://localhost:8080' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.` 跨域可以这样理解,来自一个IP端口的页面(vue项目), 要访问另一个IP端口的资源(springboot请求接口),会产生跨域访问。 解决: 新建:config-->CorsConfig `.maxAge(3600); // 1小时内不需要再预检(发OPTIONS请求)` 在调用电子书接口之前,会先发一个OPTIONS请求;(不会去处理逻辑,只会校验有没有这个接口) ### 绑定数据 1. 响应式数据:ref关键字 const ebooks = ref(); // 定义变量 2. 赋值: ebooks.value = data.content; 3. html代码要拿到响应式变量,需要在setup最后return `return { ebooks }` 第二种方法: 利用reactive (vue3新增方法) `const ebooks1 = reactive({books: []}); ebooks1.books = data.content; return { ebooks2: toRef(ebooks1,"books") }` ### vueCLI多环境配置 新建开发环境文件(.env.dev): `NODE_ENV=development VUE_APP_SERVER=http://localhost:8880` 生产环境(.env.prod) `NODE_ENV=production VUE_APP_SERVER=http://wiki-server.courseimooc.com VUE_APP_WS_SERVER=ws://wiki-server.courseimooc.com ` 修改package.json ` "serve": "vue-cli-service serve --mode dev",` 读取环境: console.log('环境:', process.env.NODE_ENV); console.log('服务端地址:', process.env.VUE_APP_SERVER); 可修改前端启动端口 ` "serve": "vue-cli-service serve --mode dev --port 8081",` 全局引入域名 `import axios from 'axios' axios.defaults.baseURL = process.env.VUE_APP_SERVER;` ### 使用axios拦截器打印前端日志 `/** * axios拦截器 */ axios.interceptors.request.use(function (config) { console.log('请求参数:', config); return config; }, error => { return Promise.reject(error); }); axios.interceptors.response.use(function (response) { console.log('返回结果:', response); return response; }, error => { console.log('返回错误:', error); return Promise.reject(error); });` ### SpringBoot过滤器的使用 配置:打印接口耗时--- 是一个非常重要的监控点,可以看出来你应用的处理能力。 1. 新建文件夹filter LogFilter.java(固定粘贴) 使用Intellij的格式化快捷键”Ctrl+ALT+L”即可进行格式化。 ### SpringBoot拦截器的使用 配置:打印接口耗时--- interceptor-->LogInterceptor.java(固定粘贴) 拦截器还需要增加一个配置类 config-->SpringMvcConfig.java(引用LogInterceptor类) `.excludePathPatterns("/login");` 排除接口不做拦截 总结: 过滤器先开始--然后是拦截器 ### SpringBoot AOP的使用 配置aop,打印接口耗时,请求参数,返回参数 新建aspect-->LogAspect.java(固定粘贴) 引入依赖: ` org.springframework.boot spring-boot-starter-aop com.alibaba fastjson 1.2.70 ` 切点加通知就等于切面Aspect filter过滤器,拦截器,aop使用一个就可以了 ### 电子书管理功能开发 ### 使用PageHelper实现后端分页 集成PageHepler插件 依赖 ` com.github.pagehelper pagehelper-spring-boot-starter 1.2.13 ` // service层分页 PageHelper.startPage(1,3); # 打印所有的sql日志:sql, 参数, 结果 logging.level.com.jiawa.wiki.mapper=trace ### 编辑打印快捷键 快捷键:logi 设置idea private static final Logger LOG = LoggerFactory.getLogger($CLASS_NAME$.class); ### 打印总行数和总页数 ` // 使用PageInfo PageInfo pageInfo = new PageInfo<>(ebookList); LOG.info("总行数{}", pageInfo.getTotal()); LOG.info("总页数{}", pageInfo.getPages());` ### 封装分页请求参数和返回参数 新建req-->PageReq 测试-->GET http://localhost:8880/ebook/list?page=1&size=4 ### post表单提交 @RequestBody 相当于Content-Type: application/json 如果是x-www-form-urlencoded就不用写注解 ### 雪花算法 其实是一个工具类。是用来生成数据库id的。 ### 参数校验Validation 依赖 ` org.springframework.boot spring-boot-starter-validation ` 1. 在实体类中加入校验规则注解 2. 开启规则 在Controller中加入@Valid 3. 引入统一校验异常处理的类(返回给前端) ControllerExceptionHandler.java ### 新建分类数据库表category MyBatis生成代码 ### 集成富文本wangEditor https://www.wangeditor.com/ 基本使用 下载 npm 安装 npm i wangeditor --save CDN 链接 https://cdn.jsdelivr.net/npm/wangeditor@latest/dist/wangEditor.min.js ### 新建文档内容表 content ### 新建用户表 user 唯一键名字login_name_unique 对应的字段login_name 作用:对于一个系统来说,登录名是不能重复的; unique key `login_name_unique` (`login_name`) ### 自定义异常类 新建exception 使用:抛出异常 // 用户名已存在 throw new BusinessException(BusinessExceptionCode.USER_LOGIN_NAME_EXIST); ### 密码加密 工具类 public-->js-->md5.js ### 单点登陆 token+redis 注解@NotEmpty 表示空字符串也会拦截 ### 引入redis依赖 ` org.springframework.boot spring-boot-starter-data-redis ` 将登陆信息保存到 `RedisTemplate` 需要将类序列化一下,做完成远程传输,还要再取出来,所以要序列化一下 下列方法二者选其一 `public class UserLoginResp implements Serializable {}` `redisTemplate.opsForValue().set(token.toString(), JSONObject.toJSONString(userLoginResp), 3600 * 24, TimeUnit.SECONDS);` ### 本机安装redis https://blog.csdn.net/weixin_41381863/article/details/88231397?utm_term=windows%E9%85%8D%E7%BD%AEredis&utm_medium=distribute.pc_aggpage_search_result.none-task-blog-2~all~sobaiduweb~default-0-88231397&spm=3001.4430 ` # REDIS (RedisProperties) # Redis数据库索引(默认为0) spring.redis.database=0 # Redis服务器地址 spring.redis.host=localhost # Redis服务器连接端口 spring.redis.port=6379 # Redis服务器连接密码(默认为空) spring.redis.password= # 连接池最大连接数(使用负值表示没有限制) spring.redis.jedis.pool.max-active=8 # 连接池最大阻塞等待时间(使用负值表示没有限制) spring.redis.jedis.pool.max-wait=-1 # 连接池中的最大空闲连接 spring.redis.jedis.pool.max-idle=8 # 连接池中的最小空闲连接 spring.redis.jedis.pool.min-idle=0 # 连接超时时间(毫秒) spring.redis.timeout=5000` 报错 `rg.springframework.data.redis.RedisConnectionFailureException: Unable to connect to Redis; nested exception is io.lettuce.core.RedisConnectionException: Unable to connect to localhost:6379 at org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory$ExceptionTranslatingConnectionProvider.translateException(LettuceConnectionFactory.java:1621)` 1.找到bind 127.0.0.1,把它进行注释 2.找到protected-mode yes 把它改成no。 修改密码之后,需要重新启动redis ### 测试redis接口 TestController.java 测试接口: ` GET http://localhost:8880/test/redis/set/123/test Accept: application/json GET http://localhost:8880/redis/get/15271595887169536 Accept: application/json ` ### 前端集成本地缓存 session-storage.js `` ### 自定义sql sql语句(用户阅读数量加1) `update doc set view_count = view_count + 1 where id = 1` 复制TestMapper.xml改为DocMapperCust.xml 复制TestMapper.java改为DocMapperCust.java ### 电子书信息更新方案 更新方式: 1.实时更新 2.定时批量更新:(优点,改动的地方少,缺点,数据实时性差) 1.统计某一个电子书数据(文档数,点赞数,阅读数) `select count(1), sum(view_count), sum(cote_count) from doc where ebook_id = 1;` 2. 批量分组查询group by `select ebook_id, count(1), sum(view_count), sum(vote_count) from doc group by ebook_id;` 3. 两张表的关联更新 `update ebook t1, (select ebook_id, count(1) doc_count, sum(view_count) view_count, sum(vote_count) vote_count from doc group by ebook_id) t2 set t1.doc_count = t2.doc_count, t1.view_count = t2.view_count, t1.vote_count = t2.vote_count where t1.id = t2.ebook_id;` ### 定时任务 1. 启动定时器,不需要引入依赖 2. 两种定时器写法 在启动类中加入注解(启动定时任务) @EnableScheduling 定时每秒,或者指定时间 `在线cron表达式生成器` ### 日志流水号 方便日志查看 `%d{ss.SSS} %highlight(%-5level) %blue(%-30logger{30}:%-4line) %thread %green(%-18X{LOG_ID}) %msg%n` LOG_ID可以在线程中随时设置 ` MDC.put("LOG_ID", String.valueOf(snowFlake.nextId()));` ### WebSocket 1. 定时轮询(前端定时器) 2. 被动通知(websocket)一直会占用服务器链接 依赖 ` org.springframework.boot spring-boot-starter-websocket ` 声明WebSocketConfig.java 开启websocket( websocket-- WebsocketServer.java) ### websocket推送消息 ` webSocketServer.sendInfo("【" + docDb.getName() + "】被点赞!");` ### 使用异步化解耦点赞通知功能 启动类加注解(@EnableAsync) 作用:防止点赞功能收到websocket出错的影响 ### 事务 加注解(@Transactional) 同时对两张表有增删改的操作,就要考虑加事务, 否则会造成数据不准确。当然也有不加事务的场景,不能一概而论。 一张表成功,一张表失败,就会出现异常。这两张表具有关联性。 要做到:同时成功,同时失败 ### 使用MQ解耦点赞通知 1. 使用RocketMQ 解决问题---不能使用Async注解(业务量大的时候,单线程阻塞) 2. MQ是消息队列, 第三方服务,需要下载(https://www.apache.org/dyn/closer.cgi?path=rocketmq/4.9.0/rocketmq-all-4.9.0-bin-release.zip) 启动应用:bin-->mqnamesrv.cmd(注册中心) bin-->mqbroker.cmd(链接服务端) 配置环境变量 :系统变量 ROCKETMQ_HOME 添加:E:\RocketMQ\rocketmq-all-4.9.0-bin-release 进入bin目录: `cmd: mqnamesrv.cmd cmd: mqbroker.cmd -n 127.0.0.1:9876 autoCreateTopicEnable=true` 3. 服务端: 加入依赖: ` org.apache.rocketmq rocketmq-spring-boot-starter 2.0.3 ` 配置: ` rocketmq.name-server=127.0.0.1:9876 rocketmq.producer.group=default` 使用(参数:主题加内容) ` rocketMQTemplate.convertAndSend("VOTE_TOPIC","【" + docDb.getName() + "】被点赞!");` 4. 消费端: 消费端一般会写到另一个专门监听MQ的应用,我们只有一个应用,就写到一起了。 rocketmq-->VoteTopicConsumer.java ### 快照表 1. 方案二(ID连续) --为所有的电子书生成一条今天的记录,如果还没有 --更新总阅读数,总点赞数 --更新今日阅读数,今日点赞数 `insert into ebook_snapshot(ebook_id, `date`, view_count, vote_count, view_increase, vote_increase) select t1.id, curdate(), 0, 0, 0, 0 from ebook t1 where not exists (select 1 from ebook_snapshot t2 where t1.id = t2.ebook_id and t2.`date` = curdate());` 第二步更新: `update ebook_snapshot t1, ebook t2 set t1.view_count = t2.view_count, t1.vote_count = t2.vote_count where t1.`date` = curdate() and t1.ebook_id = t2.id;` 第三部:(获取昨天的数据) `select t1.ebook_id, view_count, vote_count from ebook_snapshot t1 where t1.`date` = date_sub(curdate(), interval 1 day);` 更新 `update ebook_snapshot t1, (select ebook_id, view_count, vote_count from ebook_snapshot where `date` = date_sub(curdate(), interval 1 day)) t2 set t1.view_increase = (t1.view_count - t2.view_count), t1.vote_increase = (t1.vote_count - t2.vote_count) where t1.ebook_id = t2.ebook_id and t1.`date` = curdate();` ### 前端引入echarts ### 生产打包,部署 1. 购买RDS配置生产数据库 2. 购买ECS(服务器),安装JDK,nginx 3. SpringBoot项目发布,多环境配置 4. Vue项目发布 5. 域名配置 6. ECS镜像 ### 链接ECS 工具Tools-->部署Deployment-->配置Configuration 新增SFTP 启动SSH会话--链接服务器命令窗口 ### 创建prod配置 多环境打包配置 编辑配置-->环境Environment-->虚拟机选项VM options -Dspring.profiles.active=prod(加这个配置项,启动就是prod环境) ### 打包Jar maven-->项目名-->Lifecycle生命周期-->install 在target目录下生成 wikiwork-0.0.1.SNAPSHOT.jar 不要版本号 加上 ` ${artifactId} ` ### 启动 java -jar wikiwork.jar 开发 生产 java -jar -Dspring.profiles.active=prod wikiwork.jar 自动启动脚本 doc-->deploy.sh 放到服务器中 vim deploy.sh :set ff=unix :wq 运行deploy.sh 查看进程 ps -ef | grep java 查看日志 cd log tail -100 error.log tail -100f trace.log