QT-自定义参数设计框架软件

QT-自定义参数设计框架软件


前言

常用本地数据参数通常使用的是xml等文本的格式,进行本地的数据参数的存储。这种参数的保存方式有个致命的一点,就是可以存在参数的丢失。特别是在软件异常退出的情况下发生。

针对此等情况,我们现在使用的是sqllite的本地存储方式进行设计,并且尽量将参数的格式通用化。

一、演示效果

二、使用步骤

1.应用进行参数注册

代码如下:

c 复制代码
void QSettingTools::registerParams()
{
	if (1)
	{
		QString strGroupName = u8"用户参数";
		cParamOperator::getInstance()->registerQString(strGroupName, "User", "sa", QString(u8"用户名称"));
		cParamOperator::getInstance()->registerQString(strGroupName, "Passord", "123456", QString(u8"用户密码"));
		cParamOperator::getInstance()->registerInt(strGroupName, "Index", 0, 0, 10000, QString(u8"序号"));
		cParamOperator::getInstance()->registerFloat(strGroupName, "Time", 1.5, 0, 10000, QString(u8"时间,单位秒"));
	}

	if (1)
	{
		QString strGroupName = u8"系统参数";
		cParamOperator::getInstance()->registerQString(strGroupName, "FactoryId", "123433ad", QString(u8"工厂ID"));
	}


	cParamOperator::getInstance()->autoRemoveInvialParams();

}

2.数据库操作单例对象

代码如下:

c 复制代码
#include "ParamOperator.h"
#include "SqlLiteDatabase.h"
#include <QFile>
#include <QMutex>
#include <QXmlStreamWriter>
#include <QDebug>
#include <QCoreApplication>
#include <QDomDocument>
#include <QApplication>

cParamOperator g_userParam;     // 用户参数

struct cAppBaserData
{
	QHash<QString, QString> strScrTranHash;
};

cParamOperator::cParamOperator()
	: d_ptr(new cParamManagerData)
{
	m_strConfig = "Setting.db";
}

cParamOperator::~cParamOperator()
{
	delete d_ptr;
	d_ptr = nullptr;
}

// 获取一个实例
cParamOperator *cParamOperator::getInstance()
{
	static cParamOperator manager;
	return &manager;
}

// 设置app安装目录
void cParamOperator::setAppExePath(QString strPath)
{
	m_strAppExePath = strPath;
	cSqlLiteDatabase::getInstance()->setAppExePath(strPath);
	initialParams();
}

// 注册整形参数
void cParamOperator::registerInt(QString strGroup, QString strName, int nMin, int nSet, int nMax, QString strDescribe)
{
	sParamItem_t paramItem;                                           
	paramItem.strGroupName = strGroup;
	paramItem.strParamType = PARAM_TYPE_INT;      
	paramItem.strName = strName;
	paramItem.strDescribe = strDescribe;
	paramItem.minVal = nMin;
	paramItem.setVal = nSet;
	paramItem.maxVal = nMax;

	registerParam(paramItem);
}

// 注册浮点型参数
void cParamOperator::registerFloat(QString strGroup, QString strName, float fMin, float fSet, float fMax, QString strDescribe)
{
	sParamItem_t paramItem;
	paramItem.strGroupName = strGroup;
	paramItem.strParamType = PARAM_TYPE_FLOAT;
	paramItem.strName = strName;
	paramItem.strDescribe = strDescribe;
	paramItem.minVal = fMin;
	paramItem.setVal = fSet;
	paramItem.maxVal = fMax;

	registerParam(paramItem);
}

// 注册字符串参数
void cParamOperator::registerQString(QString strGroup, QString strName, QString strSet, QString strDescribe)
{
	sParamItem_t paramItem;
	paramItem.strGroupName = strGroup;
	paramItem.strParamType = PARAM_TYPE_STRING;
	paramItem.strName = strName;
	paramItem.strDescribe = strDescribe;
	paramItem.minVal = "";
	paramItem.setVal = strSet;
	paramItem.maxVal = "";

	registerParam(paramItem);
}

// 修改参数值
void cParamOperator::setParam(QString strName, QVariant var)
{
	auto pParam = getParam(strName);
	if (pParam != nullptr)
	{
		pParam->setVal = var;
		g_userParam.updateParam(*pParam);
	}
}

// 获取整数
int cParamOperator::getParamInt(QString strName)
{
	auto param = getParam(strName);
	if (param != nullptr)
	{
		return param->setVal.toInt();
	}

	return -12345;
}

// 获取浮点数
float cParamOperator::getParamFloat(QString strName)
{
	auto param = getParam(strName);
	if (param != nullptr)
	{
		return param->setVal.toFloat();
	}

	return -12345;
}

// 获取字符串
QString cParamOperator::getParamString(QString strName)
{
	auto param = getParam(strName);
	if (param != nullptr)
	{
		return param->setVal.toString();
	}

	return "Error";
}

// 获取参数组名链表
QStringList cParamOperator::getGroupNameList()
{
	QStringList strNameList;
	auto paramList = params();
	for (size_t i = 0; i < paramList->size(); i++)
	{
		auto item = paramList->at(i);
		if (!strNameList.contains(item.strGroupName))
		{
			strNameList << item.strGroupName;
		}
	}

	return strNameList;
}

// 获取某个组的所有参数
QVector<sParamItem_t> cParamOperator::getGroupParam(QString strGroupName)
{
	QVector<sParamItem_t> list;
	auto paramList = params();
	for (size_t i = 0; i < paramList->size(); i++)
	{
		auto item = paramList->at(i);
		if (item.strGroupName == strGroupName)
		{
			list << item;
		}
	}

	return list;
}

// 初始化一个参数,插入内存中
int cParamOperator::initialParamItem(sParamItem_t &param)
{
	// 注册过的就直接返回就行了
	if (d_ptr->mapName.contains(param.strName))
		return d_ptr->mapName.value(param.strName);

	// 链表没有,插入链表记录
	auto index = d_ptr->vecParam.count();
	d_ptr->vecParam.append(param);
	d_ptr->mapName.insert(param.strName, index);

	// 如果有组名,直接插入到组名对应得链表里面
	if (d_ptr->mapGroup.contains(param.strGroupName))
	{
		for (auto iter = d_ptr->mapGroup.begin(); iter != d_ptr->mapGroup.end(); iter++)
		{
			if (iter.key() == param.strGroupName)
			{
				iter.value().insert(param.strName, index);
				break;
			}
		}
	}
	else
	{
		// 重新建立一个组名链表
		QHash<QString, int> groupItem;
		groupItem.insert(param.strName, index);
		d_ptr->mapGroup.insert(param.strGroupName, groupItem);

	}

	return index;
}

// 刷新参数
bool cParamOperator::updateParam(sParamItem_t &param, bool bUpdateAll)
{
	bool bRet = false;
	if (!param.strName.isEmpty())
	{
		if (bUpdateAll)
		{
			// 整个项都更新
			cSqlLiteDatabase::getInstance()->update(d_ptr->strTableName, g_strTagName, param.strName, g_strTagSetVal, param.setVal.toString());
			cSqlLiteDatabase::getInstance()->update(d_ptr->strTableName, g_strTagName, param.strName, g_strTagGroupName, param.strGroupName);
			cSqlLiteDatabase::getInstance()->update(d_ptr->strTableName, g_strTagName, param.strName, g_strTagType, param.strParamType);
			cSqlLiteDatabase::getInstance()->update(d_ptr->strTableName, g_strTagName, param.strName, g_strTagMinVal, param.minVal.toString());
			cSqlLiteDatabase::getInstance()->update(d_ptr->strTableName, g_strTagName, param.strName, g_strTagMaxVal, param.maxVal.toString());


		}
		else
		{
			cSqlLiteDatabase::getInstance()->update(d_ptr->strTableName, g_strTagName, param.strName, g_strTagSetVal, param.setVal.toString());
		}
	}

	return bRet;
}

// 通过参数名获取参数
sParamItem_t *cParamOperator::getParam(QString strName)
{
	return getParam(d_ptr->mapName.value(strName, -1));
}

// 通过索引获取参数
sParamItem_t *cParamOperator::getParam(int nIndex)
{
	if (nIndex >= 0 && nIndex < d_ptr->vecParam.count())
		return &d_ptr->vecParam[nIndex];

	return nullptr;
}

// 所有的参数
QVector<sParamItem_t>* cParamOperator::params()
{
	return &d_ptr->vecParam;
}

// 删除参数
bool cParamOperator::removeParam(QString strName)
{
	for (int i = 0; i < d_ptr->vecParam.size(); i++)
	{
		if (d_ptr->vecParam[i].strName == strName)
		{
			d_ptr->vecParam.removeAt(i);
			break;
		}
	}
	return cSqlLiteDatabase::getInstance()->remove(m_strTableName, g_strTagName, strName);
}

// 注册参数
int cParamOperator::registerParam(sParamItem_t &param)
{
	// 缓存起来
	if (!m_strResisterNameList.contains(param.strName))
		m_strResisterNameList << param.strName;

	// 注册过,组名不同得话就重新修改所属得组名,如果没有就直接返回
	if (d_ptr->mapName.contains(param.strName))
	{
		auto paramTemp = getParam(param.strName);

		// 如果组名不同,就换新注册得组名
		if (paramTemp->strGroupName != param.strGroupName)
		{
			auto itGroup = d_ptr->mapGroup.find(paramTemp->strGroupName);
			if (itGroup != d_ptr->mapGroup.end())
			{
				// 删掉旧的,再插入新得
				auto qTempHash = itGroup.value();
				d_ptr->mapGroup.erase(itGroup);
				
				d_ptr->mapGroup.insert(param.strGroupName, qTempHash);
			}

			// 更新为最新注册得
			paramTemp->strGroupName = param.strGroupName;
			cSqlLiteDatabase::getInstance()->update(d_ptr->strTableName, g_strTagName, param.strName, g_strTagGroupName, param.strGroupName);
		}

		
		cSqlLiteDatabase::getInstance()->update(d_ptr->strTableName, g_strTagName, param.strName, g_strTagMinVal, param.minVal.toString());
		cSqlLiteDatabase::getInstance()->update(d_ptr->strTableName, g_strTagName, param.strName, g_strTagMaxVal, param.maxVal.toString());

		auto pItem = getParam(param.strName);
		if (pItem != nullptr)
		{

			pItem->strDescribe = param.strDescribe;
			pItem->minVal = param.minVal;
			pItem->maxVal = param.maxVal;
			if (pItem->strDescribe.indexOf("《****")!= -1)
			{
				param.strRemarks = "";
			}
			pItem->strRemarks = param.strRemarks;
		}

		return d_ptr->mapName.value(param.strName);
	}

	// 链表没有,插入链表记录
	auto index = d_ptr->vecParam.count();
	d_ptr->vecParam.append(param);
	d_ptr->mapName.insert(param.strName, index);
	QHash<QString, QString> itemHash;

	// 同时插入数据表
	itemHash.insert(g_strTagGroupName, param.strGroupName);
	itemHash.insert(g_strTagType, param.strParamType);
	itemHash.insert(g_strTagName, param.strName);
	itemHash.insert(g_strTagSetVal, param.setVal.toString());
	itemHash.insert(g_strTagMinVal, param.minVal.toString());
	itemHash.insert(g_strTagMaxVal, param.maxVal.toString());
	itemHash.insert(g_strTagDescribe, param.strDescribe);
	cSqlLiteDatabase::getInstance()->insert(d_ptr->strTableName,itemHash);

	// 如果有组名,直接插入到组名对应得链表里面
	if (d_ptr->mapGroup.contains(param.strGroupName))
	{
		for (auto iter = d_ptr->mapGroup.begin(); iter != d_ptr->mapGroup.end(); iter++)
		{
			if (iter.key() == param.strGroupName)
			{
				iter.value().insert(param.strName, index);
				break;
			}
		}

	}
	else
	{
		// 重新建立一个组名链表
		QHash<QString, int> groupItem;
		groupItem.insert(param.strName, index);
		d_ptr->mapGroup.insert(param.strGroupName, groupItem);
	}



	return index;
}

// 全部注册后,删除无效参数(注意:所有的参数要全部注册完成后调用)
void cParamOperator::autoRemoveInvialParams()
{

	for (auto itSqlName:m_strSqlParamNameList)
	{
		// 如果程序注册的链表不存在就需要删除数据库的参数
		if (!m_strResisterNameList.contains(itSqlName))
		{
			removeParam(itSqlName);
		}
	}

	// 重新排序,按注册顺序显示
	QVector<sParamItem_t> tempAllParam = d_ptr->vecParam;
	d_ptr->vecParam.clear();
	d_ptr->mapName.clear();
	d_ptr->mapGroup.clear();
	
	for (auto itRegister:m_strResisterNameList)
	{
		for (auto& itFind : tempAllParam)
		{
			if (itFind.strName == itRegister)
			{
				initialParamItem(itFind);
				break;
			}
		}
	}


	
}

// 初始化数据参数
int cParamOperator::initialParams(QString strTableName)
{

	if (strTableName.isEmpty())
		return -1;

	// 先打开数据库
	cSqlLiteDatabase::getInstance()->openDb(m_strConfig);


	m_strTableName = strTableName;

	static QMutex mutex;
	mutex.lock();

	// 创建数据库表
	QStringList strList;
	strList << g_strTagGroupName << g_strTagType
		<< g_strTagName << g_strTagSetVal
		<< g_strTagMinVal << g_strTagMaxVal
		<< g_strTagDescribe  << g_strTagIsTemp;

	d_ptr->strTableName = strTableName;
	cSqlLiteDatabase::getInstance()->createTable(strTableName, strList);


	// 查找数据库表的所有参数
	auto dataHashList = cSqlLiteDatabase::getInstance()->select(strTableName);
	for (int i = 0; i < dataHashList.size(); i++)
	{
		auto hash = dataHashList[i];
		sParamItem_t paramItem;
		paramItem.strGroupName = hash.find(g_strTagGroupName) != hash.end() ? hash.find(g_strTagGroupName).value() : "";
		paramItem.strParamType = hash.find(g_strTagType) != hash.end() ? hash.find(g_strTagType).value() : "";
		paramItem.strName = hash.find(g_strTagName) != hash.end() ? hash.find(g_strTagName).value() : "";
		paramItem.setVal = hash.find(g_strTagSetVal) != hash.end() ? hash.find(g_strTagSetVal).value() : "";
		paramItem.minVal = hash.find(g_strTagMinVal) != hash.end() ? hash.find(g_strTagMinVal).value() : "";
		paramItem.maxVal = hash.find(g_strTagMaxVal) != hash.end() ? hash.find(g_strTagMaxVal).value() : "";
		paramItem.strDescribe = hash.find(g_strTagDescribe) != hash.end() ? hash.find(g_strTagDescribe).value() : "";

		// 缓存数据库的参数名称
		if (!m_strSqlParamNameList.contains(paramItem.strName))
			m_strSqlParamNameList << paramItem.strName;


		initialParamItem(paramItem);
	}

	mutex.unlock();

	return 0;
}

3.参数操作单例对象

代码如下:

cpp 复制代码
#include "SqlLiteDatabase.h"
#include <QDir>
#include <QDateTime>
#include <QApplication>

cSqlLiteDatabase::cSqlLiteDatabase(QObject *parent)
	: QObject(parent)
{

}

cSqlLiteDatabase::~cSqlLiteDatabase()
{
	closeDb();
}

// 设置appExe安装目录
void cSqlLiteDatabase::setAppExePath(QString strPath)
{
	m_strExePath = strPath;
	createDir();
}

// 获取实例
cSqlLiteDatabase *cSqlLiteDatabase::getInstance()
{
	static cSqlLiteDatabase obj;
	return &obj;
}

// 创建配置文件夹
void cSqlLiteDatabase::createDir()
{
	QDir dir;

	m_strConfigPath = m_strExePath + QString("/Config");
	if (!dir.exists(m_strConfigPath))
		dir.mkpath(m_strConfigPath);

	m_strDbDirPath = m_strConfigPath;
}

// 打开数据库
QSqlDatabase cSqlLiteDatabase::openDb(QString strDatabaseName, QString strDbDirPath)
{
	if (!strDbDirPath.isEmpty())
		m_strDbDirPath = strDbDirPath;

	m_strDatabaseName = strDatabaseName;

	QSqlDatabase db;
	if (QSqlDatabase::contains(m_strDatabaseName))
		db = QSqlDatabase::database(m_strDatabaseName);
	else
	{
		QString strTempName = m_strDbDirPath + "/"+strDatabaseName;
		db = QSqlDatabase::addDatabase("QSQLITE", m_strDatabaseName);
		db.setDatabaseName(strTempName);
		db.setPassword("8888");
		db.setHostName("root");
		db.setUserName("root");
	}

	if (!db.open())
		qDebug() << db.lastError().text();
	else
		m_bConnected = true;

	return db;

}

// 关闭数据库
bool cSqlLiteDatabase::closeDb()
{
	QSqlDatabase::removeDatabase(m_strDatabaseName);
	return true;
}

// 创建表
bool cSqlLiteDatabase::createTable(QString strTableName, QStringList strHeaderNameList)
{
	bool bRet = true;
	if (true)
	{
		if (strTableName.isEmpty() || strHeaderNameList.size() <= 0)
		{
			bRet = false;
			return bRet;
		}

		auto findItem = m_strTableNameHeaderHash.find(strTableName);
		if (findItem == m_strTableNameHeaderHash.end())
			m_strTableNameHeaderHash.insert(strTableName, strHeaderNameList);

		QString strCreateTable = QString(u8"CREATE TABLE %1(").arg(strTableName);
		for (int i = 0; i < strHeaderNameList.size(); i++)
		{
			if (i < (strHeaderNameList.size() - 1))
				strCreateTable = strCreateTable + strHeaderNameList[i] + QString(u8" VARCHAR(256)") + QString(",");
			else
				strCreateTable = strCreateTable + strHeaderNameList[i] + QString(u8" VARCHAR(256)") + QString(")");
		}
		bRet = excute(strCreateTable);
	}

	return bRet;
}

// 删除表
bool cSqlLiteDatabase::dropTable(QString strTableName)
{
	if (strTableName.isEmpty())
		return false;

	QString strDrop = QString("DROP TABLE %1").arg(strTableName);
	return excute(strDrop);
}

// 执行sql语句
bool cSqlLiteDatabase::excute(QString strSql)
{
	static QMutex mutex;
	mutex.lock();
	bool bRet = true;
	QSqlDatabase db = openDb(m_strDatabaseName);
	QString strConnectionName = db.connectionName();

	QSqlQuery query(db);
	query.prepare(strSql);
	bool success = query.exec(strSql);
	if (!success)
	{
		qDebug() << "Error:" << query.lastError();
		bRet = false;
	}


	closeDb();
	mutex.unlock();
	return bRet;
}

// 查找
QList<QStringList> cSqlLiteDatabase::seach(QString strTableName, QString strSql)
{
	QList<QStringList> temp;

	QSqlDatabase db = openDb(m_strDatabaseName);
	QString strConnectionName = db.connectionName();

	db.transaction(); // 开启事务查询
	QSqlQuery query("", db);
	query.exec(strSql);
	db.commit();  // 提交事务

	while (query.next())
	{
		QStringList keyValueHash;
		int nCount = query.record().count();
		for (size_t i = 0; i < nCount; i++)
			keyValueHash << query.record().value(i).toString();

		if (keyValueHash.size() > 0)
			temp << keyValueHash;
	}

	closeDb();
	return temp;
}

// 查找
QList< QHash<QString/*name*/, QString/*value*/> > cSqlLiteDatabase::select(QString strTableName, QString strName, QString strValue)
{
	QList< QHash<QString, QString> > temp;

	if (strTableName.isEmpty() || strTableName.isEmpty())
		return temp;

	QSqlDatabase db = openDb(m_strDatabaseName);
	QString strConnectionName = db.connectionName();

	QString strSelect("");
	if (!strValue.isEmpty()&& !strName.isEmpty())
		strSelect = QString("SELECT * FROM %1 WHERE %2 = '%3';").arg(strTableName).arg(strName).arg(strValue);
	else
		strSelect = QString("SELECT * FROM  %1;").arg(strTableName);
	
	db.transaction(); // 开启事务查询
	QSqlQuery query("", db);
	query.exec(strSelect);
	db.commit();  // 提交事务

	while (query.next())
	{
		QHash<QString, QString> keyValueHash;
		int nCount = query.record().count();
		for (size_t i = 0; i < nCount; i++)
			keyValueHash.insert(query.record().fieldName(i), query.record().value(i).toString());

		if (keyValueHash.size() > 0)
			temp << keyValueHash;

	}
	
	closeDb();
	return temp;
}

// 插入数据
bool cSqlLiteDatabase::insert(QString strTableName, QHash<QString/*name*/, QString/*value*/> dataHash)
{
	bool bRet = false;
	if (strTableName.isEmpty())
		return bRet;

	QStringList strHeaderList;
	auto findItem = m_strTableNameHeaderHash.find(strTableName);
	if (findItem == m_strTableNameHeaderHash.end())
		return bRet;
	else
		strHeaderList = m_strTableNameHeaderHash[strTableName];

	if (true)
	{
		QString strInsert = QString(u8"INSERT INTO %1 VALUES(").arg(strTableName);
		for (size_t j = 0; j < strHeaderList.size(); j++)
		{
			QString strName = strHeaderList[j];
			QString strValue = "";
			auto findName = dataHash.find(strName);
			if (findName != dataHash.end())
				strValue = findName.value();

			if (j < (strHeaderList.size() - 1))
				strInsert = strInsert + QString(u8"'%1'").arg(strValue) + QString(",");
			else
				strInsert = strInsert + QString(u8"'%1'").arg(strValue) + QString(")");
		}

		bRet = excute(strInsert);
	} 

	return bRet;
}

// 更新数据
bool cSqlLiteDatabase::update(QString strTableName, QString strWhereName, QString strWhereValue, QString strUpdateName, QString strUpdateValue)
{
	bool bRet = false;
	if (strTableName.isEmpty())
		return bRet;

	QStringList strHeaderList;
	auto findItem = m_strTableNameHeaderHash.find(strTableName);
	if (findItem == m_strTableNameHeaderHash.end())
		return bRet;

	if (true)
	{

		QString strUpdate = QString(u8"UPDATE %1 SET %2='%3' WHERE %4='%5';")
			.arg(strTableName)
			.arg(strUpdateName)
			.arg(strUpdateValue)
			.arg(strWhereName)
			.arg(strWhereValue);

		bRet = excute(strUpdate);
	}

	return bRet;
}

// 删除数据
bool cSqlLiteDatabase::remove(QString strTableName, QString strWhereName, QString strWhereValue)
{
	bool bRet = false;
	if (strTableName.isEmpty())
		return bRet;

	QStringList strHeaderList;
	auto findItem = m_strTableNameHeaderHash.find(strTableName);
	if (findItem == m_strTableNameHeaderHash.end())
		return bRet;

	if (true)
	{

		QString strUpdate = QString(u8"DELETE FROM %1 WHERE %2='%3';")
			.arg(strTableName)
			.arg(strWhereName)
			.arg(strWhereValue);

		bRet = excute(strUpdate);
	}

	return bRet;
}

// 模糊查询
QStringList cSqlLiteDatabase::fuzzySearch(QString strTableName, QString strName, QString strLike)
{
	QStringList temp;

	if (strTableName.isEmpty() || strTableName.isEmpty())
		return temp;

	QSqlDatabase db = openDb(m_strDatabaseName);
	QString strConnectionName = db.connectionName();

	QString strSelect("");
	if (!strLike.isEmpty())
		strSelect = QString("SELECT %1 FROM %2 WHERE %3 LIKE '%%4%' LIMIT 0,10;").arg(strName).arg(strTableName).arg(strName).arg(strLike);
	else
		return temp;

	db.transaction(); // 开启事务查询
	QSqlQuery query("", db);
	query.exec(strSelect);
	while (query.next())
	{
		QHash<QString, QString> keyValueHash;
		for (size_t i = 0; i < query.record().count(); i++)
		{
			QString strName = query.record().fieldName(i);
			QString strValue = query.record().value(i).toString();
			if (!temp.contains(strValue) && (strLike != strValue))
				temp << strValue;

		}
	}

	db.commit();  // 提交事务
	closeDb();
	return temp;
}

三、下载链接

https://download.csdn.net/download/u013083044/89053778

相关推荐
Scott9999HH5 小时前
【IIoT流量实战】蒸汽管道阀门全关却仍有流量?用 Python 实现涡街信号 FFT 频谱分析与温压全补偿积算网关,深度拆解靠谱的涡街流量计厂家硬核技术标准
开发语言·python
码智社6 小时前
AES加密原理详解及Java实现加解密实战
java·开发语言
AI云海6 小时前
python 列表、元组、集合和字典
开发语言·python
萧瑟余晖7 小时前
JDK 26 新特性详解
java·开发语言
马优晨7 小时前
Freemarker 完整讲解(后端 Java 模板引擎)
java·开发语言·freemarker·freemarker 完整讲解·freemarker模板引擎
人邮异步社区9 小时前
怎么把C语言学到精通?
c语言·开发语言
心平气和量大福大10 小时前
C#-WPF-控件-TextBox 数据绑定
开发语言·c#·wpf
ttwuai10 小时前
Cursor 生成 CRUD 后,Go 后台接口别只测 200:JWT、RBAC 和 tenant_id 怎么验
开发语言·后端·golang
এ慕ོ冬℘゜10 小时前
前端基础:什么是时间戳?JS获取时间戳三种方法与实战用途
开发语言·前端·javascript
执明wa10 小时前
LayoutInflater详解: XML是如何变成View的?
android·xml·开发语言·android studio