Merge tag '0.4.1.3@mvn' into release

This commit is contained in:
A.C.Sukazyo Eyre 2021-12-24 18:30:54 +08:00
commit fcea3d51b7
Signed by: Eyre_S
GPG Key ID: EFB47D98FE082FAD
18 changed files with 263 additions and 126 deletions

View File

@ -7,23 +7,26 @@ plugins {
} }
group 'cc.sukazyo' group 'cc.sukazyo'
version '0.4.0.1' version VERSION
project.ext.archiveBaseName = 'Coeur_Morny_Cono' project.ext.archiveBaseName = 'Coeur_Morny_Cono'
project.ext.artifactId = 'morny-coeur' project.ext.artifactId = 'morny-coeur'
mainClassName = 'cc.sukazyo.cono.morny.ServerMain' mainClassName = 'cc.sukazyo.cono.morny.ServerMain'
repositories { repositories {
mavenCentral() mavenCentral()
maven { name '-ws'; url 'https://mvn.sukazyo.cc' }
} }
dependencies { dependencies {
compileOnlyApi "com.github.spotbugs:spotbugs-annotations:4.5.0" compileOnlyApi "com.github.spotbugs:spotbugs-annotations:${libSpotbugsVersion}"
implementation 'com.github.pengrad:java-telegram-bot-api:5.4.0' api "cc.sukazyo:messiva:${libMessivaVersion}"
testImplementation 'org.junit.jupiter:junit-jupiter-api:5.8.2' implementation "com.github.pengrad:java-telegram-bot-api:${libJavaTelegramBotApiVersion}"
testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.8.2'
testImplementation "org.junit.jupiter:junit-jupiter-api:${libJunitVersion}"
testRuntimeOnly "org.junit.jupiter:junit-jupiter-engine:${libJunitVersion}"
} }

13
gradle.properties Normal file
View File

@ -0,0 +1,13 @@
## Core
VERSION = 0.4.1.3
# dependencies
libSpotbugsVersion = 4.5.0
libMessivaVersion = 0.1.0.1
libJavaTelegramBotApiVersion = 5.5.0
libJunitVersion = 5.8.2

View File

@ -4,6 +4,6 @@ package cc.sukazyo.cono.morny;
* the final field that will be updated by gradle automatically. * the final field that will be updated by gradle automatically.
*/ */
public class GradleProjectConfigures { public class GradleProjectConfigures {
public static final String VERSION = "0.4.0.1"; public static final String VERSION = "0.4.1.3";
public static final long COMPILE_TIMESTAMP = 1638938903400L; public static final long COMPILE_TIMESTAMP = 1639476313268L;
} }

View File

@ -0,0 +1,18 @@
package cc.sukazyo.cono.morny;
import cc.sukazyo.messiva.Logger;
import cc.sukazyo.messiva.appender.ConsoleAppender;
/**
* Morny log 管理器
*/
public class Log {
/**
* Morny Logger 实例
* messiva 更新
* @since 0.4.1.1
*/
public static final Logger logger = new Logger(new ConsoleAppender());
}

View File

@ -1,80 +0,0 @@
package cc.sukazyo.cono.morny;
import cc.sukazyo.cono.morny.util.StringUtils;
import javax.annotation.Nonnull;
/**
* Morny 的简单控制台 log 记录器
*/
public class Logger {
/** Morny 的控制台 logger 实例 */
public static final Logger logger = new Logger();
/**
* [INFO] 级别的 log 消息
* @see #formatMessage(String, String) 输出整理规则
* @param message 消息文本支持多行
*/
public void info(@Nonnull String message) {
System.out.println(formatMessage(message, "INFO"));
}
/**
* [WARN] 级别的 log 消息<br>
* {@link #warning(String)} 的重写
* @see #formatMessage(String, String) 输出整理规则
* @see #warning(String) 源方法
* @param message 消息文本支持多行
*/
public void warn (@Nonnull String message) {
warning(message);
}
/**
* [WARN] 级别的 log 消息
* @see #formatMessage(String, String) 输出整理规则
* @see #warn(String) 别名:warn
* @param message 消息文本支持多行
*/
public void warning (@Nonnull String message) {
System.out.println(formatMessage(message, "WARN"));
}
/**
* [ERRO] 级别的 log 消息
* @see #formatMessage(String, String) 输出整理规则
* @param message 消息文本支持多行
*/
public void error (@Nonnull String message) {
System.out.println(formatMessage(message, "ERRO"));
}
/**
* 将传入的消息和消息元数据重新整理为固定格式<br>
* <br>
* 这个方法会{@link System#currentTimeMillis() 获取当前时间}{@link Thread#currentThread() 当前线程}的名称
* 然后将数据整理为 {@code [<timestamp>][<thread-name>][<level>]<data>} 格式<br>
* 如果消息是多行的则每行的开头都会被加入 {@code [<timestamp>][<thread-name>][<level>]} 这样的前缀
* 不过前缀中 {@code [<timestamp>][<thread-name>]} 会被转换为等长的 {@code '} 字串
* <br>
* 最终的 format 格式将会类似于以下的模样<pre><code>
[1019284827][EVT388223][INFO]Something message got:
'''''''''''''''''''''''[INFO] - data source: 19773
'''''''''''''''''''''''[INFO] - message raw: noh2q0jwd9j-jn-9jq92-ed
* </code></pre>
*
* @param message 消息文本支持多行
* @param level log级别考虑到对齐推荐使用四位窄字元
* @return 整理后的字符串
*/
@Nonnull
private String formatMessage (@Nonnull String message, @Nonnull String level) {
final String prompt = String.format("[%s][%s]", System.currentTimeMillis(), Thread.currentThread().getName());
final String levelStr = String.format("[%s]", level);
final String newline = "\n" + StringUtils.repeatChar('\'', prompt.length()) + levelStr;
return prompt + levelStr + message.replaceAll("\\n", newline);
}
}

View File

@ -9,11 +9,10 @@ import com.pengrad.telegrambot.request.GetMe;
import javax.annotation.Nonnull; import javax.annotation.Nonnull;
import javax.annotation.Nullable; import javax.annotation.Nullable;
import static cc.sukazyo.cono.morny.Logger.logger; import static cc.sukazyo.cono.morny.Log.logger;
/** /**
* Morny Cono 核心<br> * Morny Cono 核心<br>
* <br>
* - 的程序化入口类保管着 morny 的核心属性<br> * - 的程序化入口类保管着 morny 的核心属性<br>
*/ */
public class MornyCoeur { public class MornyCoeur {
@ -37,6 +36,11 @@ public class MornyCoeur {
* 会根据这里定义的时间戳取消掉比此时间更早的事件链 * 会根据这里定义的时间戳取消掉比此时间更早的事件链
*/ */
public static long latestEventTimestamp; public static long latestEventTimestamp;
/**
* morny 主程序启动时间<br>
* 用于统计数据
*/
public static final long coeurStartTimestamp = System.currentTimeMillis();
/** /**
* bot 启动入口执行 bot 初始化 * bot 启动入口执行 bot 初始化

View File

@ -1,25 +1,15 @@
package cc.sukazyo.cono.morny; package cc.sukazyo.cono.morny;
import javax.annotation.Nonnull; import cc.sukazyo.cono.morny.util.CommonFormatUtils;
import java.time.Instant;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.ZoneOffset;
import java.time.format.DateTimeFormatter;
import static cc.sukazyo.cono.morny.Logger.logger; import javax.annotation.Nonnull;
import static cc.sukazyo.cono.morny.Log.logger;
/** /**
* 程序启动入口<br> * 程序启动入口<br>
* <br> * <br>
* 会处理程序传入的参数和选项等数据并执行对应的启动方式<br> * 会处理程序传入的参数和选项等数据并执行对应的启动方式<br>
* <br>
* - 第一个参数({@code args[0]})必序传递值为 telegram-bot api-token<br>
* - 第二个参数可选 {@code --no-hello} {@code --only-hello}
* 前者表示不输出{@link MornyHello#MORNY_PREVIEW_IMAGE_ASCII 欢迎标语}
* 后者表示只输出{@link MornyHello#MORNY_PREVIEW_IMAGE_ASCII 欢迎标语}而不运行程序逻辑<br>
* <br>
* 或者在第一个参数处使用 {@code --version} 来输出当前程序的版本信息
* *
* @since 0.4.0.0 * @since 0.4.0.0
*/ */
@ -120,9 +110,7 @@ public class ServerMain {
MornySystem.VERSION, MornySystem.VERSION,
MornySystem.getJarMd5(), MornySystem.getJarMd5(),
GradleProjectConfigures.COMPILE_TIMESTAMP, GradleProjectConfigures.COMPILE_TIMESTAMP,
DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss:SSS").format(LocalDateTime.ofInstant( CommonFormatUtils.formatDate(GradleProjectConfigures.COMPILE_TIMESTAMP, 0)
Instant.ofEpochMilli(GradleProjectConfigures.COMPILE_TIMESTAMP),
ZoneId.ofOffset("UTC", ZoneOffset.UTC)))
)); ));
return; return;

View File

@ -59,4 +59,8 @@ public abstract class EventListener {
return false; return false;
} }
public boolean onChatJoinRequest (@Nonnull Update update) {
return false;
}
} }

View File

@ -8,7 +8,7 @@ import java.util.Arrays;
import java.util.List; import java.util.List;
import java.util.function.Function; import java.util.function.Function;
import static cc.sukazyo.cono.morny.Logger.logger; import static cc.sukazyo.cono.morny.Log.logger;
public class EventListenerManager { public class EventListenerManager {
@ -93,4 +93,8 @@ public class EventListenerManager {
new EventPublisher(update, x -> x.onChatMemberUpdated(update)).start(); new EventPublisher(update, x -> x.onChatMemberUpdated(update)).start();
} }
public static void publishChatJoinRequestEvent (@Nonnull Update update) {
new EventPublisher(update, x -> x.onChatJoinRequest(update)).start();
}
} }

View File

@ -37,14 +37,17 @@ public class InputCommand {
return new InputCommand(cx.length == 1 ? null : cx[1], cx[0], args); return new InputCommand(cx.length == 1 ? null : cx[1], cx[0], args);
} }
@Nullable
public String getTarget () { public String getTarget () {
return target; return target;
} }
@Nonnull
public String getCommand () { public String getCommand () {
return command; return command;
} }
@Nonnull
public String[] getArgs () { public String[] getArgs () {
return args; return args;
} }
@ -54,6 +57,7 @@ public class InputCommand {
} }
@Override @Override
@Nonnull
public String toString() { public String toString() {
return String.format("{{%s}@{%s}#{%s}}", command, target, Arrays.toString(args)); return String.format("{{%s}@{%s}#{%s}}", command, target, Arrays.toString(args));
} }

View File

@ -49,6 +49,9 @@ public class OnUpdate {
if (update.chatMember() != null) { if (update.chatMember() != null) {
EventListenerManager.publishChatMemberUpdatedEvent(update); EventListenerManager.publishChatMemberUpdatedEvent(update);
} }
if (update.chatJoinRequest() != null) {
EventListenerManager.publishChatJoinRequestEvent(update);
}
} }
return UpdatesListener.CONFIRMED_UPDATES_ALL; return UpdatesListener.CONFIRMED_UPDATES_ALL;
} }

View File

@ -8,13 +8,15 @@ public class EventListeners {
public static final OnActivityRecord ACTIVITY_RECORDER = new OnActivityRecord(); public static final OnActivityRecord ACTIVITY_RECORDER = new OnActivityRecord();
public static final OnUserSlashAction USER_SLASH_ACTION = new OnUserSlashAction(); public static final OnUserSlashAction USER_SLASH_ACTION = new OnUserSlashAction();
public static final OnUpdateTimestampOffsetLock UPDATE_TIMESTAMP_OFFSET_LOCK = new OnUpdateTimestampOffsetLock(); public static final OnUpdateTimestampOffsetLock UPDATE_TIMESTAMP_OFFSET_LOCK = new OnUpdateTimestampOffsetLock();
public static final OnInlineQuery INLINE_QUERY = new OnInlineQuery();
public static void registerAllListeners () { public static void registerAllListeners () {
EventListenerManager.addListener( EventListenerManager.addListener(
ACTIVITY_RECORDER, ACTIVITY_RECORDER,
UPDATE_TIMESTAMP_OFFSET_LOCK, UPDATE_TIMESTAMP_OFFSET_LOCK,
COMMANDS_LISTENER, COMMANDS_LISTENER,
USER_SLASH_ACTION USER_SLASH_ACTION,
INLINE_QUERY
); );
} }

View File

@ -7,6 +7,7 @@ import cc.sukazyo.cono.morny.MornyTrusted;
import cc.sukazyo.cono.morny.bot.api.EventListener; import cc.sukazyo.cono.morny.bot.api.EventListener;
import cc.sukazyo.cono.morny.bot.api.InputCommand; import cc.sukazyo.cono.morny.bot.api.InputCommand;
import cc.sukazyo.cono.morny.bot.event.on_commands.GetUsernameAndId; import cc.sukazyo.cono.morny.bot.event.on_commands.GetUsernameAndId;
import cc.sukazyo.cono.morny.util.CommonFormatUtils;
import com.pengrad.telegrambot.model.Update; import com.pengrad.telegrambot.model.Update;
import com.pengrad.telegrambot.model.request.ParseMode; import com.pengrad.telegrambot.model.request.ParseMode;
import com.pengrad.telegrambot.request.SendMessage; import com.pengrad.telegrambot.request.SendMessage;
@ -14,13 +15,7 @@ import com.pengrad.telegrambot.request.SendSticker;
import javax.annotation.Nonnull; import javax.annotation.Nonnull;
import java.time.Instant; import static cc.sukazyo.cono.morny.Log.logger;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.ZoneOffset;
import java.time.format.DateTimeFormatter;
import static cc.sukazyo.cono.morny.Logger.logger;
public class OnCommandExecute extends EventListener { public class OnCommandExecute extends EventListener {
@ -28,6 +23,7 @@ public class OnCommandExecute extends EventListener {
private static final String HELLO_STICKER_ID = "CAACAgEAAxkBAAMnYYYWKNXO4ibo9dlsmDctHhhV6fIAAqooAAJ4_MYFJJhrHS74xUAiBA"; private static final String HELLO_STICKER_ID = "CAACAgEAAxkBAAMnYYYWKNXO4ibo9dlsmDctHhhV6fIAAqooAAJ4_MYFJJhrHS74xUAiBA";
private static final String EXIT_STICKER_ID = "CAACAgEAAxkBAAMoYYYWt8UjvP0N405SAyvg2SQZmokAAkMiAAJ4_MYFw6yZLu06b-MiBA"; private static final String EXIT_STICKER_ID = "CAACAgEAAxkBAAMoYYYWt8UjvP0N405SAyvg2SQZmokAAkMiAAJ4_MYFw6yZLu06b-MiBA";
private static final String EXIT_403_STICKER_ID = "CAACAgEAAxkBAAMqYYYa_7hpXH6hMOYMX4Nh8AVYd74AAnQnAAJ4_MYFRdmmsQKLDZgiBA"; private static final String EXIT_403_STICKER_ID = "CAACAgEAAxkBAAMqYYYa_7hpXH6hMOYMX4Nh8AVYd74AAnQnAAJ4_MYFRdmmsQKLDZgiBA";
private static final String NON_COMMAND_QUESTION_STICKER_ID = "CAACAgEAAx0CSQh32gABA966YbRJpbmi2lCHINBDuo1DknSTsbsAAqUoAAJ4_MYFUa8SIaZriAojBA";
@Override @Override
public boolean onMessage (@Nonnull Update event) { public boolean onMessage (@Nonnull Update event) {
@ -55,12 +51,27 @@ public class OnCommandExecute extends EventListener {
case "/version": case "/version":
onCommandVersionExec(event); onCommandVersionExec(event);
break; break;
case "/runtime":
onCommandRuntimeExec(event);
break;
default: default:
return false; // 无法解析的命令转交事件链后代处理 return nonCommandExecutable(event, command);
} }
return true; // 命令执行成功标记事件为已处理退出事件链 return true; // 命令执行成功标记事件为已处理退出事件链
} }
private boolean nonCommandExecutable (Update event, InputCommand command) {
if (command.getTarget() == null) return false; // 无法解析的命令转交事件链后代处理
else { // 无法解析的显式命令格式报错找不到命令
MornyCoeur.getAccount().execute(new SendSticker(
event.message().chat().id(),
NON_COMMAND_QUESTION_STICKER_ID
).replyToMessageId(event.message().messageId())
);
return true;
}
}
private void onCommandOnExec (@Nonnull Update event) { private void onCommandOnExec (@Nonnull Update event) {
MornyCoeur.getAccount().execute(new SendSticker( MornyCoeur.getAccount().execute(new SendSticker(
event.message().chat().id(), event.message().chat().id(),
@ -101,18 +112,59 @@ public class OnCommandExecute extends EventListener {
event.message().chat().id(), event.message().chat().id(),
String.format(""" String.format("""
version: version:
<code> </code><code>%s</code> - <code>%s</code>
core md5_hash: core md5_hash:
<code> </code><code>%s</code> - <code>%s</code>
compile timestamp: compile timestamp:
<code> </code><code>%d</code> - <code>%d</code>
<code> </code><code>%s [UTC]</code>""", - <code>%s [UTC]</code>""",
MornySystem.VERSION, MornySystem.VERSION,
MornySystem.getJarMd5(), MornySystem.getJarMd5(),
GradleProjectConfigures.COMPILE_TIMESTAMP, GradleProjectConfigures.COMPILE_TIMESTAMP,
DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss:SSS").format(LocalDateTime.ofInstant( CommonFormatUtils.formatDate(GradleProjectConfigures.COMPILE_TIMESTAMP, 0)
Instant.ofEpochMilli(GradleProjectConfigures.COMPILE_TIMESTAMP), )
ZoneId.ofOffset("UTC", ZoneOffset.UTC))) ).replyToMessageId(event.message().messageId()).parseMode(ParseMode.HTML));
}
private void onCommandRuntimeExec (@Nonnull Update event) {
MornyCoeur.getAccount().execute(new SendMessage(
event.message().chat().id(),
String.format("""
system:
- <code>%s</code>
- <code>%s</code>
- <code>%d</code> cores
java runtime:
- <code>%s</code>
- <code>%s</code>
memory:
- <code>%d</code> / <code>%d</code> MB
morny version:
- <code>%s</code>
- <code>%s</code>
- <code>%s [UTC]</code>
- [<code>%d</code>]
continuous
- <code>%s</code>
- [<code>%d</code>]""",
// system
System.getProperty("os.name"),
System.getProperty("os.version"),
Runtime.getRuntime().availableProcessors(),
// java
System.getProperty("java.vm.name"),
System.getProperty("java.version"),
// memory
Runtime.getRuntime().totalMemory() / 1024 / 1024,
Runtime.getRuntime().maxMemory() / 1024 / 1024,
// version
MornySystem.VERSION,
MornySystem.getJarMd5(),
CommonFormatUtils.formatDate(GradleProjectConfigures.COMPILE_TIMESTAMP, 0),
GradleProjectConfigures.COMPILE_TIMESTAMP,
// continuous
CommonFormatUtils.formatDuration(System.currentTimeMillis() - MornyCoeur.coeurStartTimestamp),
System.currentTimeMillis() - MornyCoeur.coeurStartTimestamp
) )
).replyToMessageId(event.message().messageId()).parseMode(ParseMode.HTML)); ).replyToMessageId(event.message().messageId()).parseMode(ParseMode.HTML));
} }

View File

@ -0,0 +1,33 @@
package cc.sukazyo.cono.morny.bot.event;
import cc.sukazyo.cono.morny.MornyCoeur;
import cc.sukazyo.cono.morny.bot.api.EventListener;
import cc.sukazyo.cono.morny.util.EncryptUtils;
import com.pengrad.telegrambot.model.Update;
import com.pengrad.telegrambot.model.request.InlineQueryResultArticle;
import com.pengrad.telegrambot.model.request.InputTextMessageContent;
import com.pengrad.telegrambot.model.request.ParseMode;
import com.pengrad.telegrambot.request.AnswerInlineQuery;
import org.jetbrains.annotations.NotNull;
/**
* @since 0.4.1.3
*/
public class OnInlineQuery extends EventListener {
/**
* @since 0.4.1.3
*/
@Override
public boolean onInlineQuery (@NotNull Update update) {
MornyCoeur.getAccount().execute(new AnswerInlineQuery(update.inlineQuery().id(), new InlineQueryResultArticle[]{
new InlineQueryResultArticle(
EncryptUtils.encryptByMD5(update.inlineQuery().query()),
"Raw Input",
new InputTextMessageContent(update.inlineQuery().query()).parseMode(ParseMode.MarkdownV2)
)
}));
return true;
}
}

View File

@ -9,7 +9,7 @@ public class OnUpdateTimestampOffsetLock extends EventListener {
@Override @Override
public boolean onMessage (@NotNull Update update) { public boolean onMessage (@NotNull Update update) {
return update.message().date() < MornyCoeur.latestEventTimestamp*1000; return update.message().date() < MornyCoeur.latestEventTimestamp/1000;
} }
} }

View File

@ -10,7 +10,7 @@ import java.util.HashMap;
import java.util.TreeSet; import java.util.TreeSet;
import java.util.concurrent.locks.ReentrantLock; import java.util.concurrent.locks.ReentrantLock;
import static cc.sukazyo.cono.morny.Logger.logger; import static cc.sukazyo.cono.morny.Log.logger;
public class TrackerDataManager { public class TrackerDataManager {
@ -40,7 +40,7 @@ public class TrackerDataManager {
} }
if (interrupted()) { if (interrupted()) {
postProcess = true; postProcess = true;
logger.warn("last tracker write in this processor!"); logger.info("CALLED TO EXIT! writing cache.");
} }
if (record.size() != 0) { if (record.size() != 0) {
logger.info("start writing tracker data."); logger.info("start writing tracker data.");

View File

@ -0,0 +1,28 @@
package cc.sukazyo.cono.morny.util;
import java.time.Instant;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.ZoneOffset;
import java.time.format.DateTimeFormatter;
public class CommonFormatUtils {
public static String formatDate (long timestamp, int utcOffset) {
return DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss:SSS").format(LocalDateTime.ofInstant(
Instant.ofEpochMilli(timestamp),
ZoneId.ofOffset("UTC", ZoneOffset.ofHours(utcOffset))
));
}
public static String formatDuration (long duration) {
StringBuilder sb = new StringBuilder();
if (duration > 1000 * 60 * 60 * 24) sb.append(duration / (1000*60*60*24)).append("d ");
if (duration > 1000 * 60 * 60) sb.append(duration / (1000*60*60) % 24).append("h ");
if (duration > 1000 * 60) sb.append(duration / (1000*60) % 60).append("min ");
if (duration > 1000) sb.append(duration / 1000 % 60).append("s ");
sb.append(duration % 1000).append("ms");
return sb.toString();
}
}

View File

@ -0,0 +1,61 @@
package cc.sukazyo.cono.morny.util;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
/**
* 用于数据加密或编码的工具类<br>
* <s>显然大部分代码是抄来的</s><br>
* <ul>
* <li><a href="https://blog.csdn.net/yu540135101/article/details/86765457">{@link #encryptByMD5} & {@link #byteToHex}
* & {@link #byteArrayToHex} 来源</a></li>
* </ul>
*/
public class EncryptUtils {
private final static String[] hexArray = {"0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "a", "b", "c", "d", "e", "f"};
/***
* 对指定的字符串进行MD5加密
*/
public static String encryptByMD5(String originString) {
try {
//创建具有MD5算法的信息摘要
MessageDigest md = MessageDigest.getInstance("MD5");
//使用指定的字节数组对摘要进行最后更新然后完成摘要计算
byte[] bytes = md.digest(originString.getBytes());
//将得到的字节数组变成字符串返回
String s = byteArrayToHex(bytes);
return s.toUpperCase();
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
}
return null;
}
/**
* 将字节数组转换成十六进制并以字符串的形式返回
* 128位是指二进制位二进制太长所以一般都改写成16进制
* 每一位16进制数可以代替4位二进制数所以128位二进制数写成16进制就变成了128/4=32位
*/
private static String byteArrayToHex(byte[] b){
StringBuilder sb = new StringBuilder();
for (byte value : b) {
sb.append(byteToHex(value));
}
return sb.toString();
}
/**
* 将一个字节转换成十六进制并以字符串的形式返回
*/
public static String byteToHex(byte b) {
int n = b;
if (n < 0)
n = n + 256;
int d1 = n / 16;
int d2 = n % 16;
return hexArray[d1]+hexArray[d2];
}
}