在程序的开发调试过程及发布运行后的状态监控中,日志都有着极其重要的分量,通过在关键逻辑节点将关键数据记录到日志文件当中能帮助我们尽快找到程序问题所在。网上有不少专业成熟的日志组件可用,比如log4net和nlog等,由其专业及受欢迎程度可见日志在一个程序中的重要性。

我只用过log4net,而在用log4net写日志的过程中慢慢觉着太繁琐了点,不就写个日志吗?为毛搞得那么复杂?各种配置让我有点抓狂。

于是我就想,自己来吧!

首先分析一下一个基本的日志类库应该具有的基本功能及实现的时候需要注意和解决的问题:

1.关于日志文件的写入

写日志并不是简单的打开一个文件然后写入数据然后关闭了事,无论是web程序还是桌面程序,首要问题是多线程争抢写入一个日志文件的访问控制,次要问题是要允许其它进程在写入进程未释放日志文件时日志文件能被读取——试想如果日志在写的时候不能被读取那日志将毫无价值。

为了解决多线程写入的问题,多线程写入的数据将被缓存在一个StringBuilder对象中,而后由一个专门的写文件线程来负责取出数据写入到日志文件,以此来保证只有一个线程对日志文件进行写操作,如果再解决在文件流未关闭的情况下让其它进程或线程能读取日志内容,那问题就都不是问题了,而在文件流未关闭的情况下要让其它进程或线程能读取日志内容只需要在打开或创建日志文件的FileStream时指定System.IO.FileShare参数为Read即可。

2.关于日志文件的读取

文件写入成功后会有读取进行查看及分析的需求。内容较少的时候直接记事本打开即可,但是日志较大的时候就费劲了,虽然也有一些专门的软件能打开大文本文件,可打开日志文件有时并不是只为了看上一眼而已,很可能需要提取一些受关注的数据做个统计分析,比如提取某个操作的耗时来做瓶颈参考,因此有必要实现对大文本文件的读取,在读取过程中进行数据的留存分析。

对大文本文件的读取当然要按块来读取,比如一次读取10M字节,这样即便是几个G的文件也没几次可读的,重要的是不能截断单词和宽字符,所以每读取到指定字节数(如10M字节)的数据后需要根据指定的参考字符(如换行符、空格、逗号、句号等)做偏移计算。

对文件的读取在创建文件的读取流的时候必须要指定System.IO.FileShare参数为ReadWrite,否则对正在被写入或未被释放的文件的访问将被拒绝,因为写入的进程已经获得了写入权限,作为后来的读取者一定要允许其它进程可以对文件读写,要不然冲突就是一定的了。

3.关于日志的清理

随着程序常年运行,日志积累得越来越多,而日志应该都有一定的时效性,过了时效期后的日志就没有什么价值了,所以应该对日志做定时的清理操作,因此写日志的时候应该有一个默认的时效值,使日志在到期之后自动删除,以免无限增多浪费了磁盘空间,毕竟磁盘空间是十分有限的。

下面开始上代码:

新建一个 .Net Standard 类库,命名 Logger ,在类库中添加一个 Core 文件夹,在 Core 文件夹添加以下文件:

  1. ILog.cs 接口
  2. Log.cs 密封的接口实现类(不对程序集外提供访问)
  3. TextFileReader.cs 文本文件读取
  4. Factory.cs 工厂类(生产和维护日志对象)
 namespace Logger.Core
{
public interface ILog
{
void Write(string logInfo);
void WriteFormat(string format, params object[] args);
void SaveLogToFile();
void ClearLogFile();
}
}

ILog.cs

 namespace Logger.Core
{
internal class Log : ILog
{
private System.Text.StringBuilder logSource = null;
private string logFilePre = string.Empty;
private System.IO.FileStream fileStream = null;
private DateTime logFileScanLastTime = DateTime.Now;
private int logFileRetentionDays = ; public Log(string logFilePre)
: this(logFilePre, )
{ }
public Log(string logFilePre, int logFileRetentionDays)
{
this.logFilePre = logFilePre;
this.logSource = new System.Text.StringBuilder();
if (logFileRetentionDays < )
{
logFileRetentionDays = ;
}
this.logFileRetentionDays = logFileRetentionDays;
Factory.SetFileThreadStart();
} private System.IO.FileStream GetFileStream()
{
if (!System.IO.Directory.Exists(Factory.logsDirPath))
{
System.IO.Directory.CreateDirectory(Factory.logsDirPath);
}
System.IO.FileStream fs;
string FilePath = System.IO.Path.Combine(Factory.logsDirPath, this.logFilePre + DateTime.Now.ToString("yyyyMMdd") + ".log");
if (!System.IO.File.Exists(FilePath))
{
if (fileStream != null)
{
fileStream.Close();
}
fileStream = fs = new System.IO.FileStream(FilePath, System.IO.FileMode.CreateNew, System.IO.FileAccess.Write, System.IO.FileShare.Read, , true);
}
else
{
if (fileStream != null)
{
fs = fileStream;
}
else
{
fileStream = fs = new System.IO.FileStream(FilePath, System.IO.FileMode.Open, System.IO.FileAccess.Write, System.IO.FileShare.Read, , true);
}
}
return fs;
}
private string GetLogText()
{
string s = "";
if (logSource.Length > )
{
lock (logSource)
{
s = logSource.ToString();
logSource.Clear();
}
}
return s;
} public void Write(string logInfo)
{
try
{
if (logSource == null)
{
logSource = new System.Text.StringBuilder();
}
lock (this)
{
logSource.AppendFormat("{0} {1}{2}", DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss,fff"), logInfo, System.Environment.NewLine);
}
}
catch { }
}
public void WriteFormat(string format, params object[] args)
{
try
{
if (logSource == null)
{
logSource = new System.Text.StringBuilder();
}
lock (this)
{
logSource.AppendFormat("{0} ", DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss,fff"));
logSource.AppendFormat(format, args);
logSource.Append(System.Environment.NewLine);
}
}
catch { }
}
public void SaveLogToFile()
{
try
{
string logInfo = GetLogText();
if (logInfo.Length > )
{
System.IO.FileStream fs = GetFileStream();
byte[] buffer = System.Text.UTF8Encoding.UTF8.GetBytes(logInfo);
long lockBegin = fs.Length;
long lockEnd = buffer.Length;
fs.Position = lockBegin;
fs.Lock(lockBegin, lockEnd);
//fs.WriteAsync(buffer, 0, buffer.Length);
fs.Write(buffer, , buffer.Length);
fs.Unlock(lockBegin, lockEnd);
fs.Flush();
//fs.Close();
}
}
catch { }
}
public void ClearLogFile()
{
if ((DateTime.Now - logFileScanLastTime).TotalMinutes < )
{
return;
}
logFileScanLastTime = DateTime.Now;
System.IO.DirectoryInfo directoryInfo = new System.IO.DirectoryInfo(Factory.logsDirPath);
if (!directoryInfo.Exists)
{
return;
}
System.IO.FileInfo[] files = directoryInfo.GetFiles(this.logFilePre + "*.log", System.IO.SearchOption.TopDirectoryOnly);
if (files == null || files.Length < )
{
return;
}
DateTime time = DateTime.Now.AddDays( - logFileRetentionDays);
foreach (System.IO.FileInfo file in files)
{
try
{
if (file.CreationTime < time)
{
file.Delete();
}
}
catch { }
}
} }
}

Log.cs

 namespace Logger.Core
{
public class TextFileReader
{
bool _readStart = false;
bool _readEnd = false;
System.IO.FileStream _stream = null;
System.Text.Encoding _code = null;
long _fileLength = ;
long _currentPosition = ;
string _readStr = string.Empty;
int _readBytes = ;
string _filePath = "";
readonly string[] _defaultOffsetStrArray = new string[] { System.Environment.NewLine, " ", ",", ".", "!", "?", ";", ",", "。", "!", "?", ";" };
string[] _offsetStrArray = null; public string ReadStr {
get { return _readStr; }
}
public string FilePath {
get { return _filePath; }
set { _filePath = value; }
}
public int ReadBytes {
get { return _readBytes < ? : _readBytes; }
set { _readBytes = value; }
}
public string[] OffsetStrArray {
get { return (_offsetStrArray == null|| _offsetStrArray.Length < )? _defaultOffsetStrArray : _offsetStrArray; }
set { _offsetStrArray = value; }
} public TextFileReader() {
_offsetStrArray = _defaultOffsetStrArray;
}
public TextFileReader(string FilePath)
{
this.FilePath = FilePath;
_offsetStrArray = _defaultOffsetStrArray;
}
private int GetPosition(string readStr, string[] offsetStrArray)
{
int position = -;
for (int i = ; i < offsetStrArray.Length; i++)
{
position = readStr.LastIndexOf(offsetStrArray[i]);
if (position > )
{
break;
}
}
return position;
}
public bool Read()
{
if (!_readStart)
{
//System.IO.FileShare.ReadWrite:允许其它程序读写文件(重要,否则很可能会与负责写入的程序冲突而被拒绝访问)
_stream = new System.IO.FileStream(this.FilePath, System.IO.FileMode.Open, System.IO.FileAccess.Read, System.IO.FileShare.ReadWrite);
_code = GetType(this.FilePath);
_currentPosition = ;
_fileLength = _stream.Length;
_readStart = true;
}
if (_currentPosition < _fileLength)
{
byte[] readBuffer = new byte[this.ReadBytes];
//设置读取位置
_stream.Seek(_currentPosition, System.IO.SeekOrigin.Begin);
//本次实际读到的字节数
int currentReadBytes = _stream.Read(readBuffer, , readBuffer.Length);
//读取位置偏移
_currentPosition += currentReadBytes; //实际读到的字节少于指定的字节数(在读到最后一批时)
if (currentReadBytes < _readBytes)
{
byte[] temp = new byte[currentReadBytes];
int index = ;
while (index < currentReadBytes)
{
temp[index] = readBuffer[index];
index++;
}
readBuffer = temp;
}
_readStr = _code.GetString(readBuffer);
//如果没有读到最后一个字节则计算位置偏移
if (_currentPosition < _fileLength)
{
int offsetStrPosition = GetPosition(_readStr, this.OffsetStrArray);
if (offsetStrPosition > )//找到内容则计算位置偏移
{
//提取将被移除的内容
string removeStr = _readStr.Substring(offsetStrPosition + );
//移除内容
_readStr = _readStr.Remove(offsetStrPosition + );
//位置后退
_currentPosition = _currentPosition - _code.GetBytes(removeStr).Length;
}
}
}
else
{
_readEnd = true;
_stream.Dispose();
}
return !_readEnd;
} public static System.Text.Encoding GetType(string fullname)
{
System.IO.FileStream fs = new System.IO.FileStream(fullname, System.IO.FileMode.Open, System.IO.FileAccess.Read, System.IO.FileShare.ReadWrite);
System.Text.Encoding r = GetType(fs);
fs.Close();
return r;
}
public static System.Text.Encoding GetType(System.IO.FileStream fs)
{
byte[] Unicode = new byte[] { 0xFF, 0xFE, 0x41 };
byte[] UnicodeBIG = new byte[] { 0xFE, 0xFF, 0x00 };
byte[] UTF8 = new byte[] { 0xEF, 0xBB, 0xBF };
System.Text.Encoding reVal = System.Text.Encoding.Default; System.IO.BinaryReader r = new System.IO.BinaryReader(fs, System.Text.Encoding.Default);
int i;
int.TryParse(fs.Length.ToString(), out i);
byte[] ss = r.ReadBytes(i);
if (IsUTF8Bytes(ss) || (ss[] == 0xEF && ss[] == 0xBB && ss[] == 0xBF))
{
reVal = System.Text.Encoding.UTF8;
}
else if (ss[] == 0xFE && ss[] == 0xFF && ss[] == 0x00)
{
reVal = System.Text.Encoding.BigEndianUnicode;
}
else if (ss[] == 0xFF && ss[] == 0xFE && ss[] == 0x41)
{
reVal = System.Text.Encoding.Unicode;
}
r.Close();
return reVal;
}
private static bool IsUTF8Bytes(byte[] data)
{
int charByteCounter = ;
byte curByte;
for (int i = ; i < data.Length; i++)
{
curByte = data[i];
if (charByteCounter == )
{
if (curByte >= 0x80)
{
while (((curByte <<= ) & 0x80) != )
{
charByteCounter++;
}
if (charByteCounter == || charByteCounter > )
{
return false;
}
}
}
else
{
if ((curByte & 0xC0) != 0x80)
{
return false;
}
charByteCounter--;
}
}
if (charByteCounter > )
{
throw new Exception("非预期的byte格式");
}
return true;
} }
}

TextFileReader.cs

 namespace Logger.Core
{
public static class Factory
{
private static object setFileThreadCreateLockObj = new object();
private static object loggerCreateLockObj = new object();
public static readonly string logsDirPath = System.IO.Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "logs");
internal static readonly System.Collections.Generic.Dictionary<string, ILog> loggerDic = new System.Collections.Generic.Dictionary<string, ILog>();
internal static System.Threading.Thread setFileThread = null;
internal static void SetFileThreadStartFunc(object obj)
{
while (true)
{
try
{
foreach (string key in loggerDic.Keys)
{
loggerDic[key].SaveLogToFile();
loggerDic[key].ClearLogFile();
}
System.Threading.Thread.Sleep();
}
catch { }
}
}
public static void SetFileThreadStart()
{
if (setFileThread == null)
{
lock (setFileThreadCreateLockObj)
{
if (setFileThread == null)
{
setFileThread = new System.Threading.Thread(new System.Threading.ParameterizedThreadStart(SetFileThreadStartFunc));
setFileThread.IsBackground = true;
setFileThread.Start(null);
}
}
}
}
public static ILog GetLogger()
{
return GetLogger("Trace");
}
public static ILog GetLogger(string LogFilePre)
{
return GetLogger(LogFilePre, );
}
public static ILog GetLogger(string logFilePre, int logFileRetentionDays)
{
logFilePre = GetLogFilePre(logFilePre);
if (loggerDic.ContainsKey(logFilePre))
{
return loggerDic[logFilePre];
}
else
{
lock (loggerCreateLockObj)
{
if (loggerDic.ContainsKey(logFilePre))
{
return loggerDic[logFilePre];
}
else
{
ILog _logger = new Log(logFilePre, logFileRetentionDays);
loggerDic.Add(logFilePre, _logger);
return _logger;
}
}
}
}
public static string GetLogFilePre(string logFilePre)
{
if (string.IsNullOrEmpty(logFilePre)) {
logFilePre = "Trace";
}
logFilePre = logFilePre.ToLower();
if (!logFilePre.EndsWith("-"))
{
logFilePre = logFilePre + "-";
}
logFilePre = logFilePre.Substring(, ).ToUpper() + logFilePre.Substring();
return logFilePre;
}
public static System.Collections.Generic.List<string> GetLogFilePreList()
{
System.Collections.Generic.List<string> reval = new System.Collections.Generic.List<string>();
foreach(string key in loggerDic.Keys)
{
reval.Add(key);
}
return reval;
} }
}

Factory.cs

以上是实现日志功能的核心代码,下面在类库项目下直接添加两个静态类:

  1. LogWriter.cs 负责写,定义了常规的 Fatal , Error , Info , Debug 等方法及默认的日志时效期
  2. LogReader.cs 负责读,如获取日志类型列表,获取日志文件列表,或取日志文件的TextFileReader对象等
 namespace Logger
{
public static class LogWriter
{
public static Core.ILog Debug()
{
return Core.Factory.GetLogger("Debug", );
}
public static Core.ILog Debug(string logInfo)
{
Core.ILog logger = Debug();
logger.Write(logInfo);
return logger;
}
public static Core.ILog Debug(string format, params object[] args)
{
Core.ILog logger = Debug();
logger.WriteFormat(format, args);
return logger;
}
public static Core.ILog Info()
{
return Core.Factory.GetLogger("Info", );
}
public static Core.ILog Info(string logInfo)
{
Core.ILog logger = Info();
logger.Write(logInfo);
return logger;
}
public static Core.ILog Info(string format, params object[] args)
{
Core.ILog logger = Info();
logger.WriteFormat(format, args);
return logger;
}
public static Core.ILog Error()
{
return Core.Factory.GetLogger("Error", );
}
public static Core.ILog Error(string logInfo)
{
Core.ILog logger = Error();
logger.Write(logInfo);
return logger;
}
public static Core.ILog Error(string format, params object[] args)
{
Core.ILog logger = Error();
logger.WriteFormat(format, args);
return logger;
}
public static Core.ILog Fatal()
{
return Core.Factory.GetLogger("Fatal", );
}
public static Core.ILog Fatal(string logInfo)
{
Core.ILog logger = Fatal();
logger.Write(logInfo);
return logger;
}
public static Core.ILog Fatal(string format, params object[] args)
{
Core.ILog logger = Fatal();
logger.WriteFormat(format, args);
return logger;
}
}
}

LogWriter.cs

 namespace Logger
{
public static class LogReader
{
public static System.Collections.Generic.List<string> GetLogFilePreList()
{
return Core.Factory.GetLogFilePreList();
}
public static System.IO.FileInfo[] GetLogFiles(string logFilePre)
{
System.IO.DirectoryInfo dir = new System.IO.DirectoryInfo(Core.Factory.logsDirPath);
if (!dir.Exists)
{
return new System.IO.FileInfo[] { };
}
logFilePre = Core.Factory.GetLogFilePre(logFilePre);
System.IO.FileInfo[] fis = dir.GetFiles(logFilePre + "*.log", System.IO.SearchOption.TopDirectoryOnly);
if (fis == null)
{
fis = new System.IO.FileInfo[] { };
}
return fis;
}
public static Core.TextFileReader GetTextFileReader(System.IO.FileInfo logFileInfo)
{
Core.TextFileReader textFileReader = new Core.TextFileReader(logFileInfo.FullName);
textFileReader.ReadBytes = * * ;
return textFileReader;
}
}
}

LogReader

新建一个控制台程序来测试一下,测试代码:

 class Program
{
static void Main(string[] args)
{
Writer();
Reader();
}
static void Writer()
{
for (var i = ; i < ; i++)
{
System.Threading.Thread thread = new System.Threading.Thread(new System.Threading.ParameterizedThreadStart(WriterFunc));
thread.IsBackground = true;
thread.Start(i);
}
}
static void WriterFunc(object num)
{
int threadNum = (int)num;
while (true)
{
Logger.LogWriter.Info("这是线程{0}", threadNum);
Logger.LogWriter.Error("这是线程{0}", threadNum);
Logger.LogWriter.Fatal("这是线程{0}", threadNum);
System.Threading.Thread.Sleep();
}
}
static void Reader()
{
string cmd = "";
while (cmd != "r")
{
Console.Write("输入 r 读取日志:");
cmd = Console.ReadLine();
} System.Collections.Generic.List<string> preList = Logger.LogReader.GetLogFilePreList();
if (preList.Count < )
{
Console.ForegroundColor = ConsoleColor.Red;
Console.WriteLine("未能检索到日志记录!");
Console.ResetColor();
Reader();
}
Console.WriteLine("-----------------------------------------------------------"); Console.WriteLine("编号\t类型前缀");
Console.ForegroundColor = ConsoleColor.Red;
for (var i = ; i < preList.Count; i++)
{
Console.WriteLine("{0}\t{1}", i + , preList[i]+"*");
}
Console.ResetColor();
Console.WriteLine("-----------------------------------------------------------"); Console.Write("输入编号读取日志文件列表:");
int preNum = GetInputNum(, preList.Count); var files = Logger.LogReader.GetLogFiles(preList[preNum-]);
if (files.Length < )
{
Console.ForegroundColor = ConsoleColor.Red;
Console.WriteLine("未能检索到日志文件!");
Console.ResetColor();
Reader();
}
Console.WriteLine("-----------------------------------------------------------"); Console.WriteLine("编号\t日志文件");
Console.ForegroundColor = ConsoleColor.Red;
for (var i = ; i < files.Length; i++)
{
Console.WriteLine("{0}\t{1}", i + , System.IO.Path.GetFileName(files[i].FullName));
}
Console.ResetColor();
Console.WriteLine("-----------------------------------------------------------"); Console.Write("输入编号读取日志:");
int fileNum = GetInputNum(, files.Length);
Console.WriteLine("-----------------------------------------------------------"); var reader = Logger.LogReader.GetTextFileReader(files[fileNum - ]);
while (reader.Read())
{
Console.Write(reader.ReadStr);
} Console.WriteLine(); Reader(); }
static int GetInputNum(int min, int max)
{
int num = -;
while (true)
{
string inputNum = Console.ReadLine();
bool flag = false;
if (System.Text.RegularExpressions.Regex.IsMatch(inputNum, @"^\d{1,9}$"))
{
num = Convert.ToInt32(inputNum);
flag = num <= max && num >= min;
}
if (!flag)
{
Console.Write("输入不合法,请重新输入:");
num = -;
}
else
{
break;
}
}
return num;
}
}

Program.cs

程序运行截图:

至此,一个日志类库就算完成了。

鉴于个人水平问题,不敢妄言更高效或更优雅,但是可以集成到其它项目中工作了,该代码作者在公司的实际项目中有使用。

不用各种繁杂的配置,想写就写,如果想要添加一个其它类型的日志只要在LogWriter.cs中增加方法即可。

(^_^)大神莫笑,小菜莫怕,欢迎善意的沟通和交流!

通过编写一个简单的日志类库来加深了解C#的文件访问控制的更多相关文章

  1. Linux下一个简单的日志系统的设计及其C代码实现

    1.概述 在大型软件系统中,为了监测软件运行状况及排查软件故障,一般都会要求软件程序在运行的过程中产生日志文件.在日志文件中存放程序流程中的一些重要信息, 包括:变量名称及其值.消息结构定义.函数返回 ...

  2. 用Python编写一个简单的Http Server

    用Python编写一个简单的Http Server Python内置了支持HTTP协议的模块,我们可以用来开发单机版功能较少的Web服务器.Python支持该功能的实现模块是BaseFTTPServe ...

  3. 编写一个简单的C++程序

    编写一个简单的C++程序 每个C++程序都包含一个或多个函数(function),其中一个必须命名为main.操作系统通过调用main来运行C++程序.下面是一个非常简单的main函数,它什么也不干, ...

  4. 使用Java编写一个简单的Web的监控系统cpu利用率,cpu温度,总内存大小

    原文:http://www.jb51.net/article/75002.htm 这篇文章主要介绍了使用Java编写一个简单的Web的监控系统的例子,并且将重要信息转为XML通过网页前端显示,非常之实 ...

  5. 编写一个简单的Web Server

    编写一个简单的Web Server其实是轻而易举的.如果我们只是想托管一些HTML页面,我们可以这么实现: 在VS2013中创建一个C# 控制台程序 编写一个字符串扩展方法类,主要用于在URL中截取文 ...

  6. javascript编写一个简单的编译器(理解抽象语法树AST)

    javascript编写一个简单的编译器(理解抽象语法树AST) 编译器 是一种接收一段代码,然后把它转成一些其他一种机制.我们现在来做一个在一张纸上画出一条线,那么我们画出一条线需要定义的条件如下: ...

  7. Java入门篇(一)——如何编写一个简单的Java程序

    最近准备花费很长一段时间写一些关于Java的从入门到进阶再到项目开发的教程,希望对初学Java的朋友们有所帮助,更快的融入Java的学习之中. 主要内容包括JavaSE.JavaEE的基础知识以及如何 ...

  8. 用 Go 编写一个简单的 WebSocket 推送服务

    用 Go 编写一个简单的 WebSocket 推送服务 本文中代码可以在 github.com/alfred-zhong/wserver 获取. 背景 最近拿到需求要在网页上展示报警信息.以往报警信息 ...

  9. 用C语言编写一个简单的词法分析程序

    问题描述: 用C或C++语言编写一个简单的词法分析程序,扫描C语言小子集的源程序,根据给定的词法规则,识别单词,填写相应的表.如果产生词法错误,则显示错误信息.位置,并试图从错误中恢复.简单的恢复方法 ...

随机推荐

  1. EDK II代码实例之Variable

    EFI_STATUS Status = EFI_SUCCESS; EFI_GUID OemOSTypeGuid = {0xd06a0bc7, 0x9feb, 0x4cbb, 0xbd, 0x78, 0 ...

  2. P2598 [ZJOI2009]狼和羊的故事(网络流)

    P2598 [ZJOI2009]狼和羊的故事 源点和所有狼连 $inf$ 的边 所有羊和汇点连 $inf$ 的边 所有点向四周连 $1$ 的边 这样所有狼和羊之间的边都被割掉了 统计最小割就好辣 #i ...

  3. 基于vue项目一键国际化通用方案: vue-i18n + i18n-vue-cli(命令行工具)

    鉴于公司有做的国际化需求,对于公司的vue项目,觉得页面还是挺多的.刚开始觉得很简单,就是把vue文件中的中文,替换成变量,提取成一个文件就可以了,谁知道人肉的提取的部分确实太痛苦了,而且容易出错.最 ...

  4. oracle中新建用户和赋予权限

    1.新建个用户 create user xxxxx(用户名) identified by "密码" alert user 用户名 identified by “新密码” --修改用 ...

  5. opencv学习之路(36)、运动物体检测(一)

    一.简介 二.背景减法 图片说明 #include "opencv2/opencv.hpp"using namespace cv; void main() { Mat img1 = ...

  6. JS(JavaScript)的初了解8(更新中···)

    1.函数都有返回值…… 而方法的本质也是函数,所以也有返回值. Document.getElementById() 返回的是获取的标签 getElementsByClassName()和getElem ...

  7. bzoj 3513: [MUTC2013]idiots FFT

    bzoj 3513: [MUTC2013]idiots FFT 链接 bzoj 思路 参考了学姐TRTTG的题解 统计合法方案,最后除以总方案. 合法方案要不好统计,统计不合法方案. \(a+b< ...

  8. centos 7安装phantomjs

    centos 7安装phantomjs wget https://bitbucket.org/ariya/phantomjs/downloads/phantomjs-2.1.1-linux-x86_6 ...

  9. CSS--block inline inline-block

    display:block block元素会独占一行,多个block元素会各自新起一行.默认情况下,block元素宽度自动填满其父元素宽度. block元素可以设置width,height属性.块级元 ...

  10. 大牛的IT经验,方法【跟,帮】

    学习方法一:实践,应用,坚持. [swoole-韩天峰] 我最开始工作也是在2家小公司,后来加入腾讯阿里,主要原因还是我坚持学习基础知识,从而得到了这个机会.有几个方面的基础知识,我建议每一位PHP程 ...