flutter开发实战-log日志存储zip上传,发送钉钉机器人消息

flutter开发实战-log日志存储zip上传,发送钉钉机器人消息

当我们需要Apk上传的时候,我们需要将日志打包并上传到七牛,上传之后通过钉钉通知我们日志下载地址。
这里我使用的是loggy来处理日志

一、引入loggy日志格式插件

在工程的pubspec.yaml中引入插件

  loggy: ^2.0.2

使用loggy,当引入loggy后,可以在main方法中进行初始化

import 'package:loggy/loggy.dart';main() {Loggy.initLoggy();
}

之后在需要的地方进行输入相关日志

import 'package:loggy/loggy.dart';class DoSomeWork {DoSomeWork() {logDebug('This is debug message');logInfo('This is info message');logWarning('This is warning message');logError('This is error message');}
}

二、日志存储到本地

查看loggy源码看到,日志没有存储到本地,在日志的目录下可以看到printers目录,LoggyPrinter为printer的抽象类
在这里插入图片描述

part of loggy;/// Printer used to show logs, this can be easily swapped or replaced
abstract class LoggyPrinter {const LoggyPrinter();void onLog(LogRecord record);
}

当然我们需要通过继承该类来实现将日志内容写入到本地文件中,这时候我们定义了一个FilePrinter,实现onLog方法将日志写入文件中。

  • 创建日志File

我们需要指定log日志所在目录,可以使用path_provider来获取document、tmp等目录。

Future<String> createDirectory(String appTAG) async {final Directory directory = await getApplicationDocumentsDirectory();var file = Directory("${directory.path}/$appTAG");try {bool exist = await file.exists();if (exist == false) {await file.create();}} catch (e) {print("createDirectory error");}return file.path;
}

创建日志File

Future<void> getDirectoryForLogRecord() async {String currentDay = getCurrentDay();if (currentDay != _currentDate) {final String fileDir = await createDirectory(this.appTAG);file = File('$fileDir/$currentDay.log');_sink = file!.openWrite(mode: overrideExisting ? FileMode.writeOnly : FileMode.writeOnlyAppend,encoding: encoding,);_currentDate = currentDay;}}
  • 处理日志跨天的情况

每一天的日志对应一个date.log文件,如20240101.log
如果出现正好跨天的情况下,需要生成新的File来处理。这里定义了一个定时器,10分钟检测一次,如果日期不一致,则重新创建File

// 定时器void startTimer() {timerDispose();_timer = Timer.periodic(dateCheckDuration!, (timer) {getDirectoryForLogRecord();});}void timerDispose() {_timer?.cancel();_timer = null;}
  • IOSink写入文件

写入文件,我们需要用到IOSink,

写入的文件file调用openWrite即可获得IOSink对象。
openWrite方法如下

IOSink openWrite({FileMode mode: FileMode.write, Encoding encoding: utf8});

默认情况下写入是会覆盖整个文件的,但是可以通过下面的方式来更改写入模式

IOSink ioSink = logFile.openWrite(mode: FileMode.append);

IOSink写入文件流程如下

var logFile = File('log.txt');
var sink = logFile.openWrite();
sink.write('FILE ACCESSED ${DateTime.now()}\n');
await sink.flush();
await sink.close();

通过onLog方法输入的record

@overridevoid onLog(LogRecord record) async {_sink?.writeln('${record.time} [${record.level.toString().substring(0, 1)}] ${record.loggerName}: ${record.message}');}

通过sink将文件保存到文件中。

完整的FilePrinter如下

import 'dart:async';
import 'dart:convert';
import 'dart:io';import 'package:loggy/loggy.dart';
import 'package:path_provider/path_provider.dart';
import 'package:common_utils/common_utils.dart';Future<String?> getDirectory(String appTAG) async {final Directory directory = await getApplicationDocumentsDirectory();var file = Directory("${directory.path}/$appTAG");try {bool exist = await file.exists();if (exist == true) {return file.path;}} catch (e) {print("createDirectory error");}return null;
}Future<String> createDirectory(String appTAG) async {final Directory directory = await getApplicationDocumentsDirectory();var file = Directory("${directory.path}/$appTAG");try {bool exist = await file.exists();if (exist == false) {await file.create();}} catch (e) {print("createDirectory error");}return file.path;
}// 输出的文本文件, 开启定时器处理跨天的log存储问题
class FilePrinter extends LoggyPrinter {final bool overrideExisting;final Encoding encoding;final String appTAG;// 检查日期时长,可能出现跨天的情况,比如十分钟检测一次,Duration? dateCheckDuration;IOSink? _sink;File? file;String? _currentDate;// 定时器Timer? _timer;FilePrinter(this.appTAG, {this.overrideExisting = false,this.encoding = utf8,this.dateCheckDuration = const Duration(minutes: 10),}) {directoryLogRecord(onCallback: () {// 开启定时器startTimer();});}void directoryLogRecord({required Function onCallback}) {getDirectoryForLogRecord().whenComplete(() {onCallback();});}Future<void> getDirectoryForLogRecord() async {String currentDay = getCurrentDay();if (currentDay != _currentDate) {final String fileDir = await createDirectory(this.appTAG);file = File('$fileDir/$currentDay.log');_sink = file!.openWrite(mode: overrideExisting ? FileMode.writeOnly : FileMode.writeOnlyAppend,encoding: encoding,);_currentDate = currentDay;}}String getCurrentDay() {String currentDate =DateUtil.formatDate(DateTime.now(), format: "yyyyMMdd");return currentDate;}// 文件删除后重新设置logFuture<void> resetLogFile() async {_currentDate = null;getDirectoryForLogRecord();}@overridevoid onLog(LogRecord record) async {_sink?.writeln('${record.time} [${record.level.toString().substring(0, 1)}] ${record.loggerName}: ${record.message}');}dispose() {timerDispose();}// 定时器void startTimer() {timerDispose();_timer = Timer.periodic(dateCheckDuration!, (timer) {getDirectoryForLogRecord();});}void timerDispose() {_timer?.cancel();_timer = null;}
}// 输出到ConsolePrinter
class ConsolePrinter extends LoggyPrinter {const ConsolePrinter() : super();@overridevoid onLog(LogRecord record) {print('${record.time} [${record.level.toString().substring(0, 1)}] ${record.loggerName}: ${record.message}');}
}// 多种同时使用的printer
class MultiPrinter extends LoggyPrinter {MultiPrinter({required this.consolePrinter,required this.filePrinter,});final LoggyPrinter consolePrinter;final LoggyPrinter filePrinter;@overridevoid onLog(LogRecord record) {consolePrinter.onLog(record);filePrinter.onLog(record);}
}

三、日志log压缩成zip

将日志log压缩成zip,打包成zip时候,我们需要用到archive插件

在工程的pubspec.yaml中引入插件

  archive: ^3.3.7

archive是一个Dart库,用于对各种存档和压缩格式进行编码和解码。

该archive使用示例如下

import 'dart:io';
import 'package:archive/archive_io.dart';Future<void> main() async {// Read the Zip file from disk.final bytes = File('test.zip').readAsBytesSync();// Decode the Zip filefinal archive = ZipDecoder().decodeBytes(bytes);// Extract the contents of the Zip archive to disk.for (final file in archive) {final filename = file.name;if (file.isFile) {final data = file.content as List<int>;File('out/$filename')..createSync(recursive: true)..writeAsBytesSync(data);} else {Directory('out/$filename').createSync(recursive: true);}}// Encode the archive as a BZip2 compressed Tar file.final tarData = TarEncoder().encode(archive);final tarBz2 = BZip2Encoder().encode(tarData);// Write the compressed tar file to disk.final fp = File('test.tbz');fp.writeAsBytesSync(tarBz2);// Zip a directory to out.zip using the zipDirectory convenience methodvar encoder = ZipFileEncoder();await encoder.zipDirectoryAsync(Directory('out'), filename: 'out.zip');// Manually create a zip of a directory and individual files.encoder.create('out2.zip');await encoder.addDirectory(Directory('out'));await encoder.addFile(File('test.zip'));encoder.closeSync();
}

压缩log,我这里创建一个log_archive类

  • 首先创建zip目录
Future<String?> getZipDir() async {final Directory directory = await getApplicationDocumentsDirectory();var file = Directory("${directory.path}/zip");try {bool exist = await file.exists();if (exist == false) {await file.create();}} catch (e) {print("createDirectory error");}return file.path;
}Future<void> setZipPath({String? zipName}) async {if (!(zipName != null && zipName.isNotEmpty)) {String currentTime =DateUtil.formatDate(DateTime.now(), format: "yyyy_MM_dd_HH_mm_ss");zipName = "$currentTime.zip";}if (!zipName.endsWith(".zip")) {zipName = "$zipName.zip";}String? zipDir = await getZipDir();if (zipDir != null && zipDir.isNotEmpty) {String zipPath = "${zipDir}/${zipName}";this.zipPath = zipPath;}}
  • 创建zip文件

创建zip文件,需要用到ZipFileEncoder,如果有同名的zip文件,则删除后重新生成新的zip文件

Future<void> createZip() async {if (!(zipPath != null && zipPath!.isNotEmpty)) {return;}bool fileExists = await checkFileExists();if (fileExists == true) {// 文件存在// 删除后重新创建File file = File(zipPath!);await file.delete();}// zip文件重新生成zipFileEncoder.open(zipPath!);}
  • 添加File文件

zipFileEncoder生成zip后,添加File问价

Future<void> addFile(File file) async {bool fileExists = await checkFileExists();if (fileExists) {await zipFileEncoder.addFile(file);await close();}}
  • 最后调用close

zipFileEncoder添加File后,需要结束编码并关闭

Future<void> close() async {zipFileEncoder.close();}

log_archive完整代码如下

import 'dart:convert';
import 'dart:io';
import 'package:archive/archive.dart';
import 'package:archive/archive_io.dart';
import 'package:common_utils/common_utils.dart';
import 'package:path_provider/path_provider.dart';Future<String?> getZipDir() async {final Directory directory = await getApplicationDocumentsDirectory();var file = Directory("${directory.path}/zip");try {bool exist = await file.exists();if (exist == false) {await file.create();}} catch (e) {print("createDirectory error");}return file.path;
}// archive
class LogArchive {String? zipPath;late ZipFileEncoder zipFileEncoder;LogArchive() {zipFileEncoder = ZipFileEncoder();}Future<void> setZipPath({String? zipName}) async {if (!(zipName != null && zipName.isNotEmpty)) {String currentTime =DateUtil.formatDate(DateTime.now(), format: "yyyy_MM_dd_HH_mm_ss");zipName = "$currentTime.zip";}if (!zipName.endsWith(".zip")) {zipName = "$zipName.zip";}String? zipDir = await getZipDir();if (zipDir != null && zipDir.isNotEmpty) {String zipPath = "${zipDir}/${zipName}";this.zipPath = zipPath;}}Future<void> createZip() async {if (!(zipPath != null && zipPath!.isNotEmpty)) {return;}bool fileExists = await checkFileExists();if (fileExists == true) {// 文件存在// 删除后重新创建File file = File(zipPath!);await file.delete();}// zip文件重新生成zipFileEncoder.open(zipPath!);}Future<void> addFile(File file) async {bool fileExists = await checkFileExists();if (fileExists) {await zipFileEncoder.addFile(file);await close();}}Future<void> addFiles(List<File> files) async {bool fileExists = await checkFileExists();if (fileExists) {for (File file in files) {await zipFileEncoder.addFile(file);}await close();}}Future<void> close() async {zipFileEncoder.close();}Future<bool> checkFileExists() async {if (!(zipPath != null && zipPath!.isNotEmpty)) {return false;}try {File file = File(zipPath!);bool exist = await file.exists();if (exist == true) {return true;}} catch (e) {print("checkFileExists error");}return false;}// 删除单个zip文件Future<void> zipDelete(String aZipPath) async {if (aZipPath.isEmpty) {return;}final File file = File(aZipPath);bool exist = await file.exists();if (exist == false) {print("LogArchive 文件不存在");return;}await file.delete();}// 清空zip目录Future<void> zipClean() async {String? zipDir = await getZipDir();if (zipDir != null && zipDir.isNotEmpty) {var dir = Directory(zipDir);await dir.delete(recursive: true);}}Future<void> readZip(String zipDir) async {if (!(zipPath != null && zipPath!.isNotEmpty)) {return;}// Read the Zip file from disk.final File file = File(zipPath!);bool exist = await file.exists();if (exist == false) {print("LogArchive 文件不存在");return;}try {// InputFileStream only uses a cache buffer memory (4k by default), not the entire filevar stream = InputFileStream(zipPath!);// The archive will have the memory of the compressed archive. ArchiveFile's are decompressed on// demandvar zip = ZipDecoder().decodeBuffer(stream);for (var file in zip.files) {final filename = file.name;if (file.isFile) {final data = file.content as List<int>;final logFile = await File('${zipDir}/out/$filename')..create(recursive: true)..writeAsBytesSync(data);String logContent = await logFile.readAsString(encoding: utf8);print("readZip logContent:${logContent}");} else {await Directory('${zipDir}/out/' + filename).create(recursive: true);}// file.clear() will release the file's compressed memoryfile.clear();}} catch(e) {print("readZip e:${e.toString()}");}}
}

四、上传到七牛

文件上传的到七牛,需要用到七牛的qiniu_flutter_sdk插件

在工程的pubspec.yaml中引入插件

  qiniu_flutter_sdk: ^0.5.0

通过使用该插件上传示例

    // 创建 storage 对象storage = Storage();// 创建 Controller 对象putController = PutController();// 使用 storage 的 putFile 对象进行文件上传storage.putFile(File('./file.txt'), 'TOKEN', PutOptions(controller: putController,))
  • 七牛token获取

我这边使用定义个log_uploader类,由于七牛上传需要token,token需要从服务端获取,所以定义个一个抽象类LogTokenFetch
可以实现一个类来实现getToken。

  // 定义获取token接口
abstract class LogTokenFetch {Future<String> getToken();
}// 下面是示例, 请勿使用
class LogTokenFetchImpl implements LogTokenFetch {@overrideFuture<String> getToken() {// TODO: implement getTokenreturn Future.value('');}
}
  • 文件上传到七牛

我这边使用定义个log_uploader类,本质上还是套用qiniu_flutter_sdk插件的实现。

import 'dart:convert';
import 'dart:io';import 'package:qiniu_flutter_sdk/qiniu_flutter_sdk.dart';
import 'package:crypto/crypto.dart';import 'log_token_fetch.dart';// logUploader
class LogUploader {// 创建 storage 对象final Storage storage = Storage();final PutController putController = PutController();LogTokenFetch? tokenFetch;LogUploader(this.tokenFetch) {init();}init() {// 添加状态监听putController.addStatusListener((StorageStatus status) {print('LogUploader status:$status');});}Future<String?> uploadFile(File zipFile, {String? customKey}) async {if (tokenFetch != null) {String? token = await tokenFetch!.getToken();if (token != null && token.isNotEmpty) {print("token:${token}");String key = customKey??md5.convert(utf8.encode(zipFile.path)).toString();PutResponse putResponse = await storage.putFile(zipFile, token, options: PutOptions(key: key,controller: putController,));return putResponse.key;} else {return null;}} else {return null;}}
}

上传七牛过程中,可能会出现一下错误
StorageError [StorageErrorType.RESPONSE, 403]: {error: limited mimeType: this file type (application/octet-stream) is forbidden to upload}

当然需要确认token是否支持了对应的mimeType类型。

五、发送消息到钉钉机器人

经常会遇到钉钉的机器人消息,我们也可以调用其api实现发送消息。
请查考网址:https://open.dingtalk.com/document/orgapp/custom-robots-send-group-messages

这里使用的是插件dingtalk_bot_sender插件,当然dingtalk_bot_sender源码也是http请求实现了发送消息到钉钉机器人的API接口。

在工程的pubspec.yaml中引入插件

  dingtalk_bot_sender: ^1.2.0

发送消息,使用的是DingTalkSender,我们可以发送markdown、url、文本等等

使用示例如下

final sender = DingTalkSender(hookUrl: hookUrl,keyword: keyword,appsecret: appsecret,);await sender.sendText('1');final markdown = '''
markdown内容''';await sender.sendMarkdown(markdown);

我这边发送的是日志链接地址

final sender = DingTalkSender(hookUrl: hookUrl,keyword: keyword,appsecret: appsecret,);await sender.sendLink(title: "app日志", text: "下载地址:${url}", messageUrl: url);

到此,flutter开发实战-log日志存储zip上传,发送钉钉机器人消息完成。
在这里插入图片描述

六、小结

flutter开发实战-log日志存储zip上传,发送钉钉机器人消息。

学习记录,每天不停进步。

本文地址:https://brucegwo.blog.csdn.net/article/details/138672565

两款小游戏

战机长空小绳套牛
在这里插入图片描述在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/web/12194.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【经验总结】超算互联网服务器 transformers 加载本地模型

1. 背景 使用 超算互联网 的云服务&#xff0c;不能连接外网&#xff0c;只能把模型下载到本地&#xff0c;再上传上去到云服务。 2. 模型下载 在 模型中 https://huggingface.co/models 找到所需的模型后 点击下载 config.json pytorch_model.bin vocab.txt 3. 上传模型文…

IT行业找工作十面十败,不妨试试鸿蒙开发岗~

近期某脉上看到这样一则帖子&#xff0c;讨论的非常激烈&#xff01; 相信也有不少人有和他这情况类似&#xff0c;像他这种失业的状态&#xff0c;近两年大家或多或少都深有体验。由于互联网行业进过了十几年的快速发展&#xff0c;从2G→3G→4G→5G&#xff0c;在这个期间人们…

c++ 获取机器码

看到网上代码代码都没什么好的&#xff0c;自己备用一个 #include <iostream> #include <string> #include <sstream> #include <iomanip> #include <Windows.h> #include <iphlpapi.h> // 包含这个头文件以获取 PIP_ADAPTER_INFO #inclu…

elasticsearch-head 源码运行

1、下载安装nodejs 地址&#xff1a;Node.js — Run JavaScript Everywhere 2、git下载 elasticsearch-head 源码 地址&#xff1a;GitHub - mobz/elasticsearch-head: A web front end for an elastic search cluster 3、使用cmd 进入 elasticsearch-head 目录 4、依次执…

嵌入式学习-M4的基本定时器

基本介绍 框图分析 时钟选择 计数器结构 开启重装载值寄存器的影子寄存器的工作时序图 未开启重装载值寄存器的影子寄存器的工作时序图 更新事件以及中断 相关寄存器 相关库函数

Cesium+山海鲸:可视化技术的完美融合

在当今数字化浪潮中&#xff0c;可视化技术已经成为各个行业提升效率和优化决策的关键。特别是在地理信息系统&#xff08;GIS&#xff09;和数字孪生领域&#xff0c;这种技术的重要性更加凸显。而山海鲸可视化与Cesium的融合&#xff0c;无疑是这一领域的重大突破。 首先&am…

智能EDM邮件群发工具哪个好?

企业之间的竞争日益激烈&#xff0c;如何高效、精准地触达目标客户&#xff0c;成为每个市场战略家必须面对的挑战。在此背景下&#xff0c;云衔科技凭借其前沿的AI技术和深厚的行业洞察&#xff0c;匠心推出了全方位一站式智能EDM邮件营销服务平台&#xff0c;重新定义了邮件营…

半小时搞懂STM32面经知识——RCC

1. 时钟的概念 时钟是由电路产生的具有周期性的脉冲信号&#xff0c;相当于单片机的心脏&#xff0c;要想使用单片机的外设必须开启时钟。 时钟对单片机有什么作用&#xff1f; 1. 驱动外设的本质是寄存器&#xff0c;而寄存器需要时钟触发才能改写值。 2. 时钟频率越高&#…

安全风险 - 如何解决 setAccessible(true) 带来的安全风险?

可能每款成熟的金融app上架前都会经过层层安全检测才能执行上架&#xff0c;所以我隔三差五就能看到安全检测报告中提到的问题&#xff0c;根据问题的不同级别&#xff0c;处理的优先级也有所不同&#xff0c;此次讲的主要是一个 “轻度问题” &#xff0c;个人认为属于那种可改…

FinnConverter格式转换工具

FinnConverter简介 1. 简洁的操作界面 2. 支持多种格式相互转换 支持word转pdf&#xff1b;ppt转pdf&#xff1b;raw格式转png/jpng…&#xff1b;其他格式相互转换 2.1 输入格式支持 bmp、cr2、cr3、crw、cur、dcr、dng、doc、docx、gif、ico、jpeg、jpg、kdc、mos、nef、…

线程纵横:C++并发编程的深度解析与实践

hello &#xff01;大家好呀&#xff01; 欢迎大家来到我的Linux高性能服务器编程系列之《线程纵横&#xff1a;C并发编程的深度解析与实践》&#xff0c;在这篇文章中&#xff0c;你将会学习到C新特性&#xff0c;并发编程&#xff0c;以及其如何带来的高性能的魅力&#xff0…

使用XxlCrawler抓取全球航空公司ICAO三字码

目录 前言 一、数据源介绍 1、目标网站 2、页面渲染结构 二、XxlCrawler信息获取 1、创建XxlCrawler对象 2、定义PageVo对象 3、直接PageVO解析 4、自定义解析 总结 前言 长距离旅行或者出差&#xff0c;飞机一定是出行的必备方式。对于旅行达人或者出差人员而言&…

中国目前比较有影响力的人物颜廷利:物质与无知通音

既然是在中国优秀传统文化之根-汉语当中&#xff0c;汉字‘物质’二字跟‘无知’通音&#xff0c;因此&#xff0c;面对当前金钱肆虐、物欲横流的现实生活&#xff0c;当人类众生把‘物质’&#xff08;无知&#xff09;生活看的太真、太重时&#xff0c;那么&#xff0c;这就很…

Banana Pi BPI-F3, 进迭时空K1芯片设计,定位工业级应用,网络通信及工业自动化

香蕉派BPI-F3是一款工业级 8核RISC-V开源硬件开发板&#xff0c;它采用进迭时空&#xff08;SpacemiT&#xff09; K1 8核RISC-V芯片设计&#xff0c;CPU集成2.0 TOPs AI计算能力。4G DDR和16G eMMC。2个GbE以太网接口&#xff0c;4个USB 3.0和PCIe M.2接口&#xff0c;支持HDM…

Jenkins构建流程

Jenkins是DevOps【(Development和Operations的混成词&#xff09;是一种重视“软件开发人员&#xff08;Dev&#xff09;”和“IT运维技术人员&#xff08;Ops&#xff09;”之间沟通合作的文化、运动或惯例)】的重要一环&#xff0c;是一款开源的CI&CD软件。也就是持续集成…

【Viso画图】Viso导出与图形适配的pdf

step1:选中开发工具点击shapeSheet&#xff0c;选中页 step2&#xff1a;进入页面参数设置窗口&#xff0c;将下面框选的参数设为0,enter后保存 目前效果&#xff1a; step3:选中设计->大小&#xff0c;选择适应页面大小或者自己根据图片调整 目前效果&#xff1a; step4: 以…

【C++】priority_queues(优先级队列)和反向迭代器适配器的实现

目录 一、 priority_queue1.priority_queue的介绍2.priority_queue的使用2.1、接口使用说明2.2、优先级队列的使用样例 3.priority_queue的底层实现3.1、库里面关于priority_queue的定义3.2、仿函数1.什么是仿函数&#xff1f;2.仿函数样例 3.3、实现优先级队列1. 1.0版本的实现…

android自定义view仿微信联系人列表

说明&#xff1a;最近碰到一个需求&#xff0c;弄一个类似国家或省份列表&#xff0c;样式参照微信联系人 文件列表&#xff1a; step1:主界面 加载列表数据~\app\src\main\java\com\example\iosdialogdemo\MainActivity.java step2:右侧列表数据排序~\app\src\com\example\io…

6. 第K小的和-二分

6.第K小的和 - 蓝桥云课 (lanqiao.cn) #include <bits/stdc.h> #define int long long #define endl \n using namespace std; int n,m,k,an[100005],bm[100005]; int check(int x){int res0;//序列C中<x的数的个数for(int i0;i<n;i){//遍历数组A&#xff0c;对于每…

神级框架!!不要再封装各种 Util 工具类了【送源码】

这个工具类就比较厉害了&#xff0c;不过我在 Halo 当中用得最多的还是 HtmlUtil.encode&#xff0c;可以将一些字符转化为安全字符&#xff0c;防止 xss 注入和 SQL 注入&#xff0c;比如下面的评论提交。 comment.setCommentAuthor(HtmlUtil.encode(comment.getCommentAutho…