功能概述
1.1 模块概述
软件注册模块主要启到限制用户使用软件的时间,并保证软件只能在唯一电脑上使用,并为软件提供试用功能,当软件使用时间超过试用期限后,如果希望继续使用,将需要获取注册码完成软件的合理注册。
除了上述的功能之外,本模块还为用户提供产生注册码的机制,注册机可以通过用户输入的相关信息产生20位的注册码。
如果用户开发了一些有价值的作品,可以使用本模块对软件做一些限制。
1.2 功能结构
软件注册模块的功能结构图如图1所示。
图1 软件注册模块功能结构图
在本模块中主要包括软件试用、软件注册、注册机这3个部分,其中软件试用部分主要包括“软件试用时间限制”、“防止用户修改系统时间”两个功能,软件注册部分主要包括“软件注册时间限制”、“将注册信息写入注册表”、“加密注册信息”等功能,而注册机部分主要包括“生成注册码”功能。
1.3 程序预览
软件注册模块主要由3个窗体构成,主要包括注册导航窗体、软件注册窗体以及注册机窗体。
注册导航窗体主要为用户提供软件注册与软件试用选择功能,该窗体的运行效果如图2所示。
图2 注册导航窗体
软件注册窗体主要为用户提供软件注册功能,用户在试用注册机获取注册码后需要填写用户名与注册码,在注册码正确输入后,将弹出注册成功的提示对话框。该窗体的运行效果如图3所示。
图3 软件注册窗体
注册机窗体主要完成根据用户名生成注册码的功能,该窗体的运行效果如图4所示。
图4 注册机窗体
关键技术
2.1 读取客户端MAC地址
一般生成注册码都是将客户计算机的一些相关硬件信息作为注册码,获取MAC地址也是其中的一种方式。
获取MAC地址首先需要了解当前的操作系统,因为在不同的操作系统中CMD命令所在的位置不同,首先使用System类中getProperties(“os.name”)方法获取当前的操作系统,getProperties()方法可以确定当前系统属性,它的参数是一些固定的键值,表1描述了常用的固定键值与该值的表示意义。
表1System类中键值与值描述对应表
当确认CMD命令所在的位置后,使用Runtime类中的exec()方法来执行指定DOS命令,将放置在InputStream对象中,遍历结果中的每一行,获取带有“Physical Address”字样的那行,将后面MAC地址返回即可。
获取MAC地址的方法代码如下:
public static String getMAC() {
String macResult = null;
String osName = System.getProperty("os.name"); // 获取操作系统的名称
StringBuffer systemPathBuff = new StringBuffer(""); // 实例化StringBuffer对象
if (osName.indexOf("Windows") > -1) {
// Windows操作系统的cmd.exe的绝对路径
systemPathBuff.append("c:\\WINDOWS\\system32\\cmd.exe");
}
if (osName.indexOf("NT") > -1) {
// NT操作系统cmd.exe的绝对路径
systemPathBuff.append("c:\\WINDOWS\\command.com");
}
Process pro = null;
try {
// 此语句相当于在cmd下面执行dir命令.并得到命令执行完毕后的输出流
pro = Runtime.getRuntime().exec(
systemPathBuff.toString() + " /c dir"); // 执行DOS命令
InputStream is = pro.getInputStream();
BufferedReader br = new BufferedReader(new InputStreamReader(is));
// 读取第一行
String message = br.readLine();
String serNuResult = null;
int index = -1;
// 读取下一行
message = br.readLine();
// 此语句相当于在cmd下面执行 ipconfig/all 的命令.并得到命令执行完毕后的输出流
pro = Runtime.getRuntime().exec(
systemPathBuff.toString() + " /c ipconfig/all"); // 执行获取MAC地址的DOS命令
is = pro.getInputStream();
br = new BufferedReader(new InputStreamReader(is));
// 读取第一行
message = br.readLine();
while (message != null) {
if ((index = message.indexOf("Physical Address")) > 0) { // 找到MAC地址那行
macResult = message.substring(index + 36).trim(); // 将结果放置在macResult变量中
break;
}
// 读取下一行
message = br.readLine();
}
}catch(Exception e){
e.printStackTrace();
}
return macResult; // 将MAC地址返回
}
2.2 Java操作注册表
Java操作注册表无非就是对注册表进行读写操作。JDK1.4以及JDK1.4以上的版本中可以使用java.util.prefs包中的Preferences这个类对注册表内容进行读写操作,使用该类操作注册的表的步骤如下:
(1)获取Preferences对象,在实例化对象的同时,需要给出在注册表中哪个节点位置写入相关信息。
(2)使用put()方法或putInt()、putDouble()方法为注册表中的相关项赋值。
将读、写注册表内容代码分别封装在两个方法中,这两个方法的名称分别为WriteRegisty()方法以及WriteValue()方法,这两个方法的关键代码如下:
public class ReadWriteRegisty {
// 读取注册表中的值
public static String ReadValue(String node,String key){
Preferences pre=Preferences.systemRoot().node(node);
return pre.get(key, "注册表中没有该值");
}
// 批量写入注册表,参数为节点、键值、键值对应的值
public static void WriteValue(String node,String []keys,Object [] values){
Preferences pre = Preferences.systemRoot().node(node); // 获取Preferences对象
for(int i = 0; i < keys.length; i++){
pre.put(keys[i],String.valueOf(values[i])); // 将键值与对应的值写入注册表中
}
}
public static void WriteValue(String node,String keys,String values){
Preferences pre = Preferences.systemRoot().node(node); // 获取Preferences对象
pre.put(keys,values); // 将键值与对应的值写入注册表中
}
}
2.3 避免用户修改系统时间
有时用户会在软件试用到期时更改系统时间以便用户可以再次试用该软件,为了避免用户使用这样的方式继续试用软件,需要限制用户修改系统时间,当用户启动软件注册导航窗体时,程序将相关信息写入注册表,这些信息分别为用户第一次试用时间programe1、系统当前时间programe2以及用户使用该软件的状态programe3,为了避免用户修改系统时间,在程序中首先使用当前系统时间与programe2节点中记录的时间进行比较,如果用户修改系统时间,当前时间一定会小于programe2节点中记录的时间,这时将弹出错误提示对话框,并终止试用。这样就避免用户修改系统时间以享受更长的试用时间。
该技术的关键代码如下:
if(ReadWriteRegisty.ReadValue("myprograme", "programe1").equals("注册表中没有该值")){
// ZHUCE=-1代表试用时间满,ZHUCE=-2代表已注册。其余数字代表该软件使用的天数。
String[] key={"programe1","programe2","programe3"}; // 节点第一个字母不要设置为大写,否则在注册表中会添加一个“\”字样。
String d=dateFormate.format(new Date());
Object[] value={d,d,"sign1"};
// 注册表中programe1键值代表首次运行本软件时间
// Programe2键值代表当前运行软件时间
// Programe3代表当前软件状态,sign1代表软件在试用期中,sign2代表软件试用期满,sign3代表已经注册,sign4代表注册期满。
ReadWriteRegisty.WriteValue("myprograme", key,value);
}
else{
try {
// 如果修改系统日期,希望继续使用该软件
if(new Date().compareTo(df.parse(ReadWriteRegisty.ReadValue("myprograme", "programe2")))<=0){ // 比较两个时间的先后
JOptionPane.showMessageDialog(mainFrame.this, "软件已经试用到期,如果您想继续使用,请购买序列号进行注册使用");
ReadWriteRegisty.WriteValue("myprograme", "programe3", "sign2");
ZHUCE=-1;
}
} catch (ParseException e) {
e.printStackTrace();
}
}
2.4 右键单击弹出菜单
在软件注册窗体中,放置着4个承载着注册码的文本框,当用户在文本框中单击鼠标右键,将在鼠标单击处弹出菜单,运行效果如图1所示。
图1 右键弹出菜单
实现该功能的步骤如下:
(1)为4个文本框添加鼠标单击事件监听器。由于这4个文本框实现的事件相同,所以将该监听事件封装在一个继承于MouseAdaptor类的类中。这样在为该4个文本框添加事件监听时候将直接调用该类对象即可。
(2)在监听器事件中判断用户是否单击鼠标右键,将初始化一个弹出式菜单。
(3)编写弹出式菜单,为该菜单添加子菜单项。
(4)为子菜单项添加事件监听器,实现剪切、复制、粘贴功能。
该鼠标事件监听器的关键代码如下:
class JCopy extends MouseAdapter{
public void mouseDragged(MouseEvent arg0) {
super.mouseDragged(arg0);
}
public void mouseClicked(MouseEvent arg0) {
super.mouseClicked(arg0);
// 鼠标按键getButton()方法返回1表示按了左键盘,2表示按了中键盘,3表示按了右键盘
if(arg0.getButton()==3){ // 如果用户单击鼠标右键
final JPopupMenu popup=new JPopupMenu(); // 初始化弹出式菜单
JMenuItem itemcut=new JMenuItem("Cut"); // 初始化剪切菜单项
popup.add(itemcut); // 将菜单项添加到菜单中
popup.addSeparator(); // 添加分割线
JMenuItem itemcopy=new JMenuItem("Copy"); // 初始化拷贝菜单项
popup.add(itemcopy); // 将菜单项添加到菜单中
itemcopy.addActionListener(new ActionListener(){ // 为拷贝菜单项添加事件监听器
public void actionPerformed(ActionEvent arg0) {
t1.selectAll();
t2.selectAll();
t3.selectAll();
t4.selectAll();
JTextJP.requestFocus(true);
System.out.println(JTextJP.requestFocusInWindow());
String t1Selection=t1.getSelectedText();
String t2Selection=t2.getSelectedText();
String t3Selection=t3.getSelectedText();
String t4Selection=t4.getSelectedText();
popup.setVisible(false);
// 获取用户添加的注册码信息并以“-”相连接
String IntegrationString =t1Selection.concat("-").concat(t2Selection).concat("-").concat(t3Selection).concat("-").concat(t4Selection);
if(IntegrationString==null)
return; // 如果用户没有在文本框中填写内容,返
// 实例化StringSelection对象,它用于传递文本字符串
StringSelection clipString=new StringSelection(IntegrationString);
// Clipboard对象描述一个剪切板,这里将字符串放置在剪切板上
clipbd.setContents(clipString, clipString);
}
});
popup.addSeparator();
JMenuItem itempaste=new JMenuItem("Paste");
popup.add(itempaste);
……// 省略部分代码
// 设置菜单弹出的位置,这里获取鼠标的位置作为菜单弹出的位置
popup.show(arg0.getComponent(), arg0.getX(), arg0.getY());
}
}
}
2.5 一次性粘贴注册码
为了方便用户的使用,在填写注册码时,为用户提供一次性粘贴注册码的功能,也就是当用户复制一串带有“-”字符分割符的字符串时,可以一次性粘贴到软件注册窗体的4个文本框中,为了完成这一功能。首先需要了解Java中实现剪切板的技术。
使用Java可以实现一个简易的剪切板,在图形用户界面中,拷贝和复制是最有用的用户接口之一。可以通过剪切板将文字、图像从一个文档移动到另一个文档中。实现剪切板功能使用java.awt.datatransfer包。下面简要介绍这个包中主要类的使用。
l 实现剪切板的功能必须实现Transferable接口。
l Clipboard类描述了一个剪切板,它的对象代表剪切板上唯一能取走的项目。
l DataFlavor类是用来描述剪切板的风格。
l StringSelection类是一个实现transferable接口的实体类。
在本模块中可以实现在软件注册时将一个注册码一次复制,同时粘贴在4个不同的文本框的功能。它的实现代码如下:
try{
// 获取剪切板中字符串
String clipString=(String)clipData.getTransferData(DataFlavor.stringFlavor);
if(clipString.contains("-")){ // 如果获取的字符串中包含“-”字符
String [] a=clipString.split("-"); // 将字符串以“-”字符进行分割放入数组中
for(int i=0;i<a.length;i++)
System.out.println(a[i]);
t1.setText(a[0]); // 将数组中的每个元素放入文本框中
t2.setText(a[1]);
t3.setText(a[2]);
t4.setText(a[3]);
}else{
JOptionPane.showMessageDialog(RegisterFrame.this, "您粘贴的注册码格式不正确,请重新粘贴");
}
}catch(Exception e){
e.printStackTrace();
}
当在文本文件中复制一组数据,就可以一次粘贴到软件注册窗体的4个文本框中,具体设置如图2、图3所示。
图2 在文本文件中复制一组数据
图3 在软件注册窗体中进行一次性粘贴
2.6 获取两个时间的相隔天数
在软件注册模块的实现过程中,比较两个时间的情况比较多,例如避免用户修改系统时间,判断用户是否超过试用时间,验证用户的注册时间等。
在Java中获取两个时间的相差时间有两种方式,第一种是可以使用getTime()方法来获取两个时间的毫秒差,然后再将该时间换算为天数,另外一种是使用java.util.Calender类来计算两个时间的相隔天数。
在本模块中使用第二种方式来解决两个时间的相隔天数问题。使用java.util.Calender计算两个时间的相隔天数的步骤如下:
(1)首先使用SimpleDateFormat类格式化时间格式。
(2)格式化该时间格式获取相应的Date对象。
(3)获取Canlendar实例,使用setTime()方法设定该Canlendar时间。
(4)指定Canlendar属性对各个时间做差。
实现获取两个时间的相差天数的关键代码如下:
// 获取SimpleDateFormat实例,指定时间的格式
SimpleDateFormat df2=new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
// 格式化起始时间
Date date_start=df2.parse(new Date().toLocaleString());
// 格式化中止时间
Date date_end=df2.parse(ReadWriteRegisty.ReadValue("myprograme", "programe1"));
Calendar cal_start=Calendar.getInstance(); // 获取Calendar实例
cal_start.setTime(date_start); // 将时间指定给Calendar对象
Calendar cal_end=Calendar.getInstance();
cal_end.setTime(date_end);
// 调用自定义方法getDifferenceDays()方法
System.out.println("两个时间的相差天数为:"+getDifferenceDays(cal_start,cal_end));
在上述方法中自定义的getDifferenceDays()方法用于计算两个Canlendar中时间的相隔天数,由于Canlendar类中具有多个成员变量,其中有一个为DAY_OF_YEAR成员变量,使用它就可以获取Canlendar对象中表示一年中天数的时间,如果获取起始和结束时间做差就可以获取两个时间的相差天数。
这个方法的关键代码如下:
public static int getDifferenceDays(Calendar d1,Calendar d2){
if(d1.after(d2)){ // 比较两个时间的先后,将前面的时间赋予d1参数
java.util.Calendar swap=d1;
d1=d2;
d2=swap;
}
// 计算两个时间的天数差
int days=d2.get(java.util.Calendar.DAY_OF_YEAR)-d1.get(java.util.Calendar.DAY_OF_YEAR);
// 获取第二个参数的年数
int y2=d2.get(java.util.Calendar.YEAR);
// 如果第一个参数与第二个参数不为同一年
if(d1.get(java.util.Calendar.YEAR)!=y2){
do{
// 将days变量加上d1日历中的最大可能的天数
days+=d1.getActualMaximum(java.util.Calendar.DAY_OF_YEAR);
// 给d1日历加上一年,直到d1日历的年份与d2日历的年份相同
d1.add(java.util.Calendar.YEAR, 1);
}while(d1.get(java.util.Calendar.YEAR)!=y2);
}
return days;
}
上述代码就获取了两个时间相差天数,在计算这个天数之前,需要将大的日期放置在小的日期前面,然后进行相减操作,上述代码除了可以计算在同一年的两个时间的相差天数之外,还可以进行跨年计算,判断两个日期是否在同一年,如果不在,将前面的日期反复加1,直到与后面日期的年数相等为止,同时记得要把这一年的天数加到两个日期相差的天数上。
2.7 ini文件的读写
ini类型的文件用于存储一些配置信息,为了可以保存注册用户硬件相关信息,将该信息写入配置文件中,下面就介绍ini文件的读写。
Properties类位于java.util包中,操作它可以读写ini文件,Properties类表示一个持久的属性类,Properties可保存在流中或从流中加载,属性列表中每个键及其对应的值都是一个字符串。
使用Properties类操纵ini文件的步骤如下:
(1)使用put()方法将键与键值进行对应,然后使用store()方法将该Properties类对象存入ini类型的文件中。
(2)当需要获取该ini文件信息时,可以使用load()方法将该文件加载,然后使用Properties对象的get()方法获取相应字符串的值。
在本模块中Properties类操作ini文件的关键代码如下:
Properties pro = new Properties(); // 初始化Properties对象
// 使用put()方法将键与键值放入Properties对象中,这里是将该注册码加密后的信息添加到a.ini文件中
pro.put("register", String.valueOf(codeStringBigint));
try {
// 将信息保存在a.ini文件中
pro.store(new FileOutputStream("a.ini", true), null);
// 可以从a.ini中通过Properties.get方法读取配置信息
pro.load(new FileInputStream("a.ini"));
} catch (Exception ex) {
ex.printStackTrace();
}
使用Properties类读ini类型文件内容的关键代码如下:
try {
// 将信息包存在a.ini文件中
pro.store(new FileOutputStream("a.ini",true),null);
// 可以从a.ini中通过Properties.get()方法读取配置信息
pro.load(new FileInputStream("a.ini"));
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
// 获取a.ini文件中register键值的值
String ctext=String.valueOf(String.valueOf(pro.get("register")));
System.out.println(ctext);
// 将该值转型为BigInteger类型,该数据为加密后的注册码
BigInteger c=new BigInteger(ctext);
当执行上述代码后,在当前目录下将会出现一个名为a.ini的文件,打开该文件,运行效果如图4所示。
图4 a.ini文件
2.8 RSA加密解密算法
考虑到安全因素,将安装软件的硬件信息加密,然后写入配置文件中,这里使用RSA加密解密算法。
RSA算法是常用的非对称加密算法,它的基本原理就是基于大数的因子分解,将明文看作是一个无符号的大整数。RSA的密钥长度为40~2048位可变,加密时将明文分成块,块的大小可以改变,但每个块的长度不能超过密钥的长度,这就需要传输的数据分段传输,密钥的长度越长保密性越好,但密钥的长度越长加密与解密的效率越低。
使用RSA加密解密的步骤如下:
(1)生成RSA密钥对,使用java.security包中的一些相关类获取私钥与共钥,然后将公钥与私钥写入相应文件中。主部分关键代码如下:
public genKey() {
try {
// 创建密钥对生成器
KeyPairGenerator KPG=KeyPairGenerator.getInstance("RSA");
// 初始化该生成器
KPG.initialize(1024);
// 生成密钥对
KeyPair KP=KPG.genKeyPair();
// 获取共钥和私钥
PublicKey pbkey=KP.getPublic();
PrivateKey prkey=KP.getPrivate();
// 将共钥写进文件中
FileOutputStream filePbkey=new FileOutputStream("RSAPubkey.dat");
ObjectOutputStream filePbkeyStream=new ObjectOutputStream(filePbkey);
filePbkeyStream.writeObject(pbkey);
// 将密钥写进文件中
FileOutputStream filePrkey=new FileOutputStream("RSAPrikey.dat");
ObjectOutputStream filePrkeyStream=new ObjectOutputStream(filePrkey);
filePrkeyStream.writeObject(prkey);
} catch (Exception e) {
e.printStackTrace();
}
}
(2)根据密钥进行RSA加密处理。使用java.secrity.RSAPublicKey类中的getPublicExponent()方法和getModulus()方法可以得到RSA算法公式的两个参数,然后将明文转型为BigInteger类型,由于BigInteger类中的modPow()方法可执行RSA算法公式,所以将该上述两个参数作为modPow()方法的参数即可完成加密操作。程序中加密的关键代码如下。
try {
File f = new File("a.ini");
if (!f.exists()) {
new genKey();
// 读取公钥
FileInputStream FIS = new FileInputStream("RSAPubKey.dat");
ObjectInputStream OIS = new ObjectInputStream(FIS);
RSAPublicKey RSAPK = (RSAPublicKey) OIS.readObject();
// 得到两个公钥的参数
BigInteger e = RSAPK.getPublicExponent();
BigInteger n = RSAPK.getModulus();
// 将明文转为大整数
byte[] strByte = (str.concat(register)).getBytes("UTF8");
BigInteger m = new BigInteger(strByte);
// 进行加密操作
codeStringBigint = m.modPow(e, n);
System.out.println("加密后的密文为:" + codeStringBigint);
// 保存密文
Properties pro = new Properties();
pro.put("register", String.valueOf(codeStringBigint));
try {
// 将信息保存在a.ini文件中
pro.store(new FileOutputStream("a.ini", true), null);
// 可以从a.ini中通过Properties.get方法读取配置信息
pro.load(new FileInputStream("a.ini"));
} catch (Exception ex) {
ex.printStackTrace();
}
}
} catch (Exception e) {
e.printStackTrace();
}
在使用RSA对密文进行解密的操作中,原理基本上与加密时相同,需要获取解密密钥的两个参数,然后将密文转型为BigInteger类型,调用modPow()方法进行解密操作,将解密后的明文转型为Byte类型的数组,读取数组中的每个值,强制转换为字符形式。解密操作在程序中的关键代码如下:
String ctext=String.valueOf(String.valueOf(pro.get("register")));
System.out.println(ctext);
BigInteger c=new BigInteger(ctext);
// 获取私钥
FileInputStream f=new FileInputStream("RSAPrikey.dat");
ObjectInputStream b=new ObjectInputStream(f);
RSAPrivateKey prk=(RSAPrivateKey)b.readObject();
// 得到公钥的两个参数
BigInteger d=prk.getPrivateExponent();
BigInteger n=prk.getModulus();
// 解密处理
BigInteger m=c.modPow(d, n);
byte [] mt=m.toByteArray();
for(int i=0;i<mt.length;i++){
s+=(char)mt[i]; // 字符串s为解密后的明文
}
软件注册导航窗体的实现
3.1 窗体概述
软件注册导航窗体主要为用户提供试用与软件注册选择的功能,该窗体的运行效果如图1所示。
图1 软件注册导航窗体
在软件注册导航窗体中,如果用户选择“我想试用该软件”单选按钮,并单击“继续”按钮时,软件进入试用状态,如果用户选择“我有一个序列号,我想使用该软件” 单选按钮,并单击“继续”按钮时,软件进入软件注册窗体。
3.2 窗体界面设计
软件注册导航窗体的布局主要包括左方介绍面板以及右方选择面板,在这里使用到了JSplitPane组件,设定整个窗体以左右方式进行分割,整个窗体的设计的关键代码如下:
public mainFrame() {
super();
try {
// 设置窗体风格
UIManager.setLookAndFeel(new com.sun.java.swing.plaf.nimbus.NimbusLookAndFeel());
} catch (UnsupportedLookAndFeelException e1) {
e1.printStackTrace();
}
// 创建按钮组
ButtonGroup bg = new ButtonGroup();
final JRadioButton b1, b2;
JButton b3;
final JSplitPane splitPane = new JSplitPane(); // 初始化JSplitPane对象
splitPane.setDividerLocation(150); // 设置左右分割的位置
JPanel rightPanel = new JPanel(); // 初始化左侧面板
ImageIcon icon = CreatecdIcon.add("11.jpg"); // 获取ImageIcon对象
JLabel jl = new JLabel(); // 初始化放置图片的JLabel对象
jl.setLayout(null); // 设定绝对布局
jl.setIcon(icon); // 将图片添加到JLabel上
// 设置JLabel的位置与大小
jl.setBounds(0, 0, icon.getIconWidth(), icon.getIconHeight());
// 初始化单选按钮
b1 = new JRadioButton("我有一个序列号,我想使用该软件");
b1.setBackground(new Color(234, 241, 247));
b1.setSelected(true); // 设置该单选按钮
b2 = new JRadioButton("我想试用该软件");
jl.add(b1); // 将单选按钮添加到JLabel上
b1.setBounds(40, 60, 260, 30);
jl.add(b2);
b2.setBackground(new Color(234, 241, 247));
b2.setBounds(40, 100, 200, 30);
bg.add(b1); // 将单选按钮添加到按钮组中
bg.add(b2);
b3 = new JButton("【继续】"); // 初始化“继续”按钮
jl.add(b3);
b3.setBounds(200, 180, 90, 20);
jl.add(zhuce);
zhuce.setBounds(50, 130, 200, 30);
……// 省略部分代码
rightPanel.add(jl, new Integer(Integer.MIN_VALUE));
JPanel leftPanel = new JPanel();
leftPanel.setLayout(null);
leftPanel.setBackground(Color.WHITE);
JLabel image = new JLabel();
ImageIcon icon2 = CreatecdIcon.add("00002.jpg");
image.setIcon(icon2);
JLabel letter1 = new JLabel();
letter1.setText("<html><i>关于注册</i><br>注册需要用户名与注册码,用户可以在软件包装处进行寻找<br><br>如果您不想激活该软件,可以在试用状态下使用,并且可以在试用期内随时激活本软件。</html>")
JLabel letter2 = new JLabel();
leftPanel.add(image);
image.setBounds(20, 20, 130, 100);
leftPanel.add(letter1);
letter1.setBounds(10, 120, 130, 200);
leftPanel.add(letter2);
letter2.setBounds(10, 200, 130, 50);
splitPane.setLeftComponent(leftPanel); // 设置主窗体左方的面板
splitPane.setRightComponent(rightPanel); // 设置主窗体右方的面板
getContentPane().add(splitPane); // 将分割面板添加到容器中
setSize(new Dimension(570, 400));
// 设置窗体可以关闭
setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
setTitle("软件注册模块"); // 为窗体设置标题
}
3.3 软件试用部分的实现
在软件试用的实现部分中,首先需要实现的是将用户的试用时间以及当前时间和窗体的状态写入注册表,并避免用户修改系统时间延长试用期限。
实现软件试用部分的步骤如下:
(1)软件启动后首先判断注册表HKEY_LOCAL_MACHINE\Software\MyProgram子键下的3个键值中是否有值。如果没有值,说明用户是第一次试用该软件,将这3个键值的初始值写入注册表。其中programe1键值代表首次运行本软件的时间,programe2代表当前运行软件的时间,而programe3当前软件的使用状态,programe3键值对应的值分别为sign1、sign2、sign3、sign4,分别表软件在试用期间、软件试用期满、用户已注册、注册期满。
(2)如果用户已经试用该软件,需要判断用户是否超过试用期限。如果超过期限,将弹出错误提示对话框。并将注册表中试用该软件的状态修改为sign2。
(3)更新软件注册导航窗体中的试用剩于天数,这里试用天数放置在一个名为ZHUCE的全局变量中,这个变量依据读取注册表中的programe1键值而赋值,ZHUCE为-1说明试用期满,ZHUCE为-2说明软件已经注册,ZHUCE为其他数字表示试用剩余天数。在该窗体中将ZHUCE变量放置在标签中表示试用天数。
这部分关键代码如下:
int days = 30; // 试用允许使用的天数
final SimpleDateFormat dateFormate=new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
final DateFormat df=DateFormat.getDateInstance();
// 判断是否首次使用该软件
if(ReadWriteRegisty.ReadValue("myprograme", "programe1").equals("注册表中没有该值")){
// ZHUCE=-1代表试用时间满,ZHUCE=-2代表已注册。其余数字代表该软件使用的天数。
String[] key={"programe1","programe2","programe3"}; // 节点第一个字母不要设置为大写,否则在注册表中会添加一个“\”字样。
String d=dateFormate.format(new Date()); // 获取当前时间
Object[] value={d,d,"sign1"};
// 注册表中programe1键值代表首次运行本软件时间
// Programe2键值代表当前运行软件时间
// Programe3代表当前软件状态,sign1代表软件在试用期中,sign2代表软件试用期满,sign3代表已经注册,sign4代表注册期满。
ReadWriteRegisty.WriteValue("myprograme", key,value);
}
else{
try {
// 如果修改系统日期,希望继续使用该软件
if(new Date().compareTo(df.parse(ReadWriteRegisty.ReadValue("myprograme", "programe2")))<=0){ // 比较两个时间的先后
JOptionPane.showMessageDialog(mainFrame.this, "软件已经试用到期,如果您想继续使用,请购买序列号进行注册使用");
// 将当前软件状态添加到注册表中
ReadWriteRegisty.WriteValue("myprograme", "programe3", "sign2");
ZHUCE=-1; // 将注册天数设置为-1
}
} catch (ParseException e) {
e.printStackTrace();
}
// 如果注册表中当前软件的状态为sign1
if(ZHUCE>0||ReadWriteRegisty.ReadValue("myprograme", "programe3").equals("sign1")){
try {
// 将当前的时间写入注册表中
ReadWriteRegisty.WriteValue("myprograme", "programe2", dateFormate.format(new Date()));
// 比较当前时间与注册表中programe1中的时间相差的天数,以获取用户试用的天数
SimpleDateFormat df2=new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
Date date_start=df2.parse(new Date().toLocaleString());
Date date_end=df2.parse(ReadWriteRegisty.ReadValue("myprograme", "programe1"));
Calendar cal_start=Calendar.getInstance();
Calendar cal_end=Calendar.getInstance();
cal_end.setTime(date_end);
ZHUCE=days-getDifferenceDays(cal_start,cal_end);
} catch (ParseException e) {
e.printStackTrace();
}
}
}
// 如果注册时间小于0或则注册表中的标记为sign2
if(ZHUCE<=0||ReadWriteRegisty.ReadValue("myprograme", "programe3").equals("sign2")){
ZHUCE=0; // 将注册表试用天数清0
//ReadWriteRegisty.WriteValue("myprograme", "programe3", "sign2");
}
zhuce.setText("(剩余"+ZHUCE+"天)");
在软件注册导航窗体中除了将试用的相关内容写入注册表之外,还要设计“继续”按钮的事件监听器,这个按钮的事件监听器的关键代码如下:
b3.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent arg0) {
// 如果用户选择了“我有一个注册码,我想使用该软件”的单选按钮
if (b1.isSelected()) {
setVisible(false); // 将当前窗体隐藏
new RegisterFrame().setVisible(true); // 使软件注册窗体可见
}
// 如果用户选择了“我想试用该软件”单选按钮
if (b2.isSelected()) {
// 如果已到试用期限
if(ReadWriteRegisty.ReadValue("myprograme","programe3").equals("sign2")){
// 弹出提示对话框
JOptionPane.showMessageDialog(mainFrame.this, "软件已经试用到期,如果您想继续使用,请购买序列号进行注册使用");
return;
}
if(ZHUCE==0){
// 如果注册变量为0,则弹出提示对话框,并将注册表中的标示修改
JOptionPane.showMessageDialog(mainFrame.this, "软件已经试用到期,如果您想继续使用,请购买序列号进行注册使用");
ReadWriteRegisty.WriteValue("myprograme", "programe3", "sign2");
return;
}
}
}
});
软件注册窗体的实现
4.1 窗体概述
软件注册窗体为用户提供一个进行软件注册的功能,当用户在软件注册导航窗体中选择“我有一个注册码,我想使用该软件”的单选按钮,并单击“继续”按钮后,将进入软件注册窗体,该窗体的运行效果如图1所示。
图1 软件注册窗体
在软件注册窗体中,包括用户名文本框,还有4个用于填写注册码的文本框,同时该窗体还设置两个按钮分别为“后退”与“前进”按钮,“后退”按钮可以使该窗体返回到软件注册导航窗体中,而“前进”按钮将引导软件注册完成。
4.2 窗体界面设计
软件注册窗体与软件导航窗体的布局基本相同,都是由JSplitPane组件将该窗体进行左右分割设置,在窗体的左侧放置介绍该窗体的内容面板,而窗体右侧放置主要完成注册功能的功能组件。整个窗体的布局关键代码如下:
public RegisterFrame() {
super();
ButtonGroup bg=new ButtonGroup(); // 初始化一个按钮组
JRadioButton b1,b2; // 实例化两个单选按钮
JButton b3; // 实例化一个JButton实例
final JSplitPane splitPane = new JSplitPane(); // 初始化一个JSplitPane组件
splitPane.setDividerLocation(150); // 设置JSplitPane组件的分割位置
JPanel rightPanel=new JPanel(); // 初始化右方面板
ImageIcon icon = CreatecdIcon.add("11.jpg"); // 创建图标
jl= new JLabel();
jl.setLayout(null); // 设定标签为绝对布局
// 将描述文字写入JLabel组件中
JLabel j1=new JLabel("<html><font=14>请输入您的用户名与序列号</font></html>");
jl.add(j1); // 将文字标签添加到JLabel组件中
j1.setBounds(10, 20,160 , 30);
jl.setIcon(icon);
JLabel userNameJ=new JLabel("用户名:"); // 初始化文本框组件
final JTextField userNameT=new JTextField("wsy",40); // 为文本框赋值
jl.add(userNameJ);
jl.add(userNameT); // 将文本框与标签组件添加到标签组件中
userNameJ.setBounds(80, 80, 100, 25);
userNameT.setBounds(200, 80, 100, 25);
t1=new JTextField(10); // 初始化填写注册码的4个文本框
t2=new JTextField(10);
t3=new JTextField(10);
t4=new JTextField(10);
JTextJP=new JPanel(); // 初始化一个放置该4个文本框的面板
JTextJP.setBackground(new Color(234, 241, 247));
JTextJP.setLayout(null);
JTextJP.add(t1);
JTextJP.add(t2);
JTextJP.add(t3);
JTextJP.add(t4);
t1.setBounds(10, 15, 50, 25);
t2.setBounds(60,15, 50, 25);
t3.setBounds(110,15, 50, 25);
t4.setBounds(160,15, 50, 25);
// 为4个文本框添加鼠标事件监听器,用户弹出菜单
t1.addMouseListener(new JCopy());
t2.addMouseListener(new JCopy());
t3.addMouseListener(new JCopy());
t4.addMouseListener(new JCopy());
jl.add(JTextJP);
JTextJP.setBounds(80, 130, 220, 50);
JButton back=new JButton("【后退】"); // 初始化“后退”与“前进”按钮
JButton forwards=new JButton("【前进】");
jl.add(back); // 将这两个按钮添加到标签上
jl.add(forwards);
back.setBounds(130, 200, 90, 20);
forwards.setBounds(230, 200,90, 20);
……// 省略部分代码
// 按照背景图片的到校设置JLabel的位置和大小
jl.setBounds(0,0,icon.getIconWidth(),icon.getIconHeight());
rightPanel.add(jl,new Integer(Integer.MIN_VALUE)); // 在右侧面板中添加JLabel组件
JPanel leftPanel=new JPanel(); // 初始化窗体左侧面板
leftPanel.setLayout(null); // 设定左侧面板为绝对布局管理
leftPanel.setBackground(Color.WHITE); // 设定左侧面板的背景颜色为白色
JLabel image=new JLabel(); // 初始化放置图片的JLabel组件
ImageIcon icon2=CreatecdIcon.add("00002.jpg"); // 获取图标
image.setIcon(icon2); // 在标签上放置图片
JLabel letter1=new JLabel();
letter1.setText("<html>请您填写序列号</html>");
JLabel letter2=new JLabel();
leftPanel.add(image);
image.setBounds(20, 20, 130, 100);
leftPanel.add(letter1);
letter1.setBounds(10, 120, 130, 200);
leftPanel.add(letter2);
letter2.setBounds(10, 200, 130, 50);
splitPane.setLeftComponent(leftPanel); // 将左侧面板添加到分割面板上
splitPane.setRightComponent(rightPanel); // 将右侧面板添加到分割面板上
getContentPane().add(splitPane); // 将分割面板组件添加到容器中
setSize(new Dimension(570,400));
setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
m.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
setTitle("软件注册模块"); // 设置窗体标题
}
在软件注册窗体中,主要包括“后退”与“前进”两个按钮,为了使该按钮具有一定的功能,需要在窗体设计中为这两个按钮添加事件监听器。
“后退”按钮的事件监听器关键代码如下:
back.addActionListener(new ActionListener(){
public void actionPerformed(ActionEvent arg0) { // 重写actionPerformed()方法
setVisible(false); // 使当前窗体不可见
m.setVisible(true); // 使软件注册导航窗体可见
}
});
在上述代码中为了使软件注册导航窗体可见,需要获取MainFrame.java类的对象,这个对象在RegisterFrame.java类中作为全局变量在程序的最开始处已经被初始化。
“前进”按钮的事件监听中完成软件注册的功能,这些功能主要包括验证注册码、限制注册用户的使用时间、根据注册机器的硬件信息保证软件使用的唯一性。
4.3 验证注册码
验证注册码的代码被封装在“前进”按钮的监听事件中,完成这一功能的步骤如下:
(1)获取用户输入的验证码。
(2)将用户名通过编码程序处理为密文,比较该密文与用户输入的验证码是否相同。
在“前进”按钮事件监听器中验证注册码的关键代码如下:
forwards.addActionListener(new ActionListener(){
public void actionPerformed(ActionEvent arg0) {
SimpleDateFormat sf=new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
s=userNameT.getText().trim(); // 获取用户名
String t1Text=t1.getText().trim(); // 获取注册码
String t2Text=t2.getText().trim();
String t3Text=t3.getText().trim();
String t4Text=t4.getText().trim();
// 如果注册码不为空
if(t1Text.length()!=0&&t2Text.length()!=0&&t3Text.length()!=0&&t4Text.length()!=0){
if(s!=null){ // 如果用户名不为空
// 调用getRegister()方法将用户名转为密文
String[] a=RegisterMark.getRegister(s).split("-");
// 如果用户名和注册码不匹配
if(!a[0].equals(t1Text)||!a[1].equals(t2Text)||!a[2].equals(t3Text)||!a[3].equals(t4Text)){
// 弹出错误提示对话框
JOptionPane.showMessageDialog(RegisterFrame.this, "用户名与注册码不匹配,请确定后重新输入");
t1.setText(""); // 将注册码文本框置空
t2.setText("");
t3.setText("");
t4.setText("");
return; // 返回
}
else{ // 如果用户名和注册码匹配
String register="";
for(int i=0;i<a.length;i++){
register+=a[i]; // 将注册码返回给register变量
// 加密注册该软件的机器的MAC地址
BigInteger codeStringBigint=new encrypt().Execencrypt(RegisterMark.getMAC(),register);
// 如果程序没有将注册信息写入注册表
if(ReadWriteRegisty.ReadValue("myprograme", "register1").equals("注册表中没有该值")){
// 将加密后的MAC地址信息写入注册表
ReadWriteRegisty.WriteValue("myprograme", "register1", String.valueOf(codeStringBigint));
// 将注册时间写入注册表
ReadWriteRegisty.WriteValue("myprograme", "registertime", sf.format(new Date()));
// 将运行注册当前时间写入注册表
ReadWriteRegisty.WriteValue("myprograme", "registertimetest", sf.format(new Date()));
// 将注册表中programe3节点的值修改为sign3表示软件已注册
ReadWriteRegisty.WriteValue("myprograme", "programe3", "sign3");
}
}
……// 省略部分代码
}
……// 省略部分代码
}
}
});
在上述代码中使用到了RegisterMark.java类中的getRegister()方法,该方法用于获取用户名的密文,该方法的关键代码如下:
public static String getRegister(String s) {
String s1="",s2=""; // 初始化两个字符串
SimpleDateFormat sd=new SimpleDateFormat("yyyy"); // 设定事件的格式化对象
Date d=new Date(); // 初始化一个Date对象
s+=sd.format(d); // 将需要加密的字符串与当前年份连接
for(int i=0;i<s.length();i++){ // 循环字符串的个数
s1+=(int)s.charAt(i)*(i+1); // 对字符串中每个字符进行处理赋予s1字符串
}
for(int i=0;i<s1.length()/5;i++){ // 根据s1字符串进行处理获取字符串s2
if(i==s1.length()/5-1)
s2+=s1.substring(i*5,(i+1)*5);
else
s2+=s1.substring(i*5, (i+1)*5)+"-";
}
return s2; // 将字符串s2返回
}
获取加密的字符串后,将该字符串以“-”字符进行分割,然后分别与软件注册窗体中的4个文本框进行比较,如果每个文本框的内容与该字符串的子串相同,那么就通过注册码的验证,将注册码返回给register变量,如果每个文本框的内容与该字符串的子串不同,那么注册码验证错误,将弹出错误提示对话框。
说明:在这里生成注册码的方式是其中的一种方式,可以根据自己的想法编写更具有安全性的注册码。
4.4 限制注册用户使用时间
用户注册后,不可能永久对该软件的具有使用权利,应该在软件注册模块中为注册用户提供一个使用期限,所以要在注册代码中限制到期用户继续使用该软件。
首先需要判断用户注册使用时间是否到期,如果到期,更新注册表中programe3键值的值为sign1,判断用户注册试用时间是否到期可以使用当前时间与注册表中用户首次注册的时间进行比较,前者大于后者,说明注册到期,弹出提示对话框。
比较两个时间顺序可以使用Date类中的compareTo()方法,该方法的参数为Date类型,如果参数Date等于调用该方法的Date,则方法返回值为0,如果调用该方法的Date在参数Date之前,则返回小于0的值,如果调用该方法的Date在Date参数之后,则返回大于0的值。在调用该方法中可以能会出现NullPointerException异常。
在“继续”按钮的事件监听器中判断注册用户是否到期的关键代码如下:
try {
// 使用当前时间与注册表中首次注册时间相比较,如果小于0说明注册期限已满
if(new Date().compareTo(sf.parse(ReadWriteRegisty.ReadValue("myprograme", "registertimetest")))<0){
JOptionPane.showMessageDialog(RegisterFrame.this, "您的软件时间使用已经到期,如果希望继续使用,请注册");
// 将注册表中programe3节点修改为sign1
ReadWriteRegisty.WriteValue("myprograme", "programe3", "sign1");
return;
}
else
// 如果用户注册时间没满,将当前系统时间更新到注册表中的registertimetest节点中
ReadWriteRegisty.WriteValue("myprograme", "registertimetest", sf.format(new Date()));
} catch (ParseException e1) {
e1.printStackTrace();
}
try{
Calendar cal_start=Calendar.getInstance();
Calendar cal_end=Calendar.getInstance();
cal_start.setTime(new Date()); cal_end.setTime(sf.parse(ReadWriteRegisty.ReadValue("myprograme", "programe1")));
System.out.println(cal_start.get(Calendar.YEAR));
System.out.println(cal_end.get(Calendar.YEAR));
if(new Date().before(sf.parse(ReadWriteRegisty.ReadValue("myprograme", "registertime")))){
JOptionPane.showMessageDialog(RegisterFrame.this, "您的软件时间使用已经到期,如果希望继续使用,请注册");
ReadWriteRegisty.WriteValue("myprograme", "programe3", "sign1");
return ;
} if(cal_start.get(Calendar.YEAR)-cal_end.get(Calendar.YEAR)>=1||cal_start.get(Calendar.YEAR)-cal_end.get(Calendar.YEAR)<0){
ReadWriteRegisty.WriteValue("myprograme", "programe3", "sign2");
JOptionPane.showMessageDialog(RegisterFrame.this, "您的软件时间使用已经到期,如果希望继续使用,请注册");
ReadWriteRegisty.WriteValue("myprograme", "programe3", "sign1");
return;
}
}catch(Exception e){
e.printStackTrace();
}
为了避免用户修改系统时间以获取更长的注册使用时间,与避免用户修改系统时间以获取更长的试用时间原理基本相同,在首次运行窗体时,将系统当前时间写入注册表中的registertimetest键值,然后在注册时间没有满的情况下更新当前时间作为registertimetest键值的值,这样用户每次注册的时候,将当前时间与注册表中registertimetest键值的时间比较,如果当前时间在该时间之前,说明用户修改了系统时间。
4.5 根据注册机器的硬件信息保证软件使用唯一性
有时用户在两台硬件电脑上同时使用该软件,为了避免这种情况发生,在软件注册时,获取电脑的硬件信息、使用RSA算法进行加密,然后将密文写入配置文件中,这样在每次用户进入注册窗体时都会读取配置文件的内容,首先对该内容进行解密操作,再获取当前机器的MAC地址与该配置文件的信息进行比较,如果相同说明用户是在同一台机器上注册的。如果用户在其他机器上运行该模块,配置文件中的硬件信息已被写入,程序会弹出“一个注册码只能在唯一一台机器上使用”的错误提示对话框。
实现根据注册机器的硬件信息保证软件使用唯一性功能的步骤如下:
(1)首次注册时将该机器的MAC地址进行加密,将密文写入配置文件a.ini中。
(2)然后在每次注册的过程中都获取a.ini文件中的密文进行解密与当前MAC地址进行比较,判断软件是否在同一台机器上进行注册。
// 调用enrypt类中的Excencrypt()方法将MAC地址进行加密
BigInteger codeStringBigint=new encrypt().Execencrypt(RegisterMark.getMAC(),register);
System.out.println(ReadWriteRegisty.ReadValue("myprograme", "register1").equals("注册表中没有该值"));
if(ReadWriteRegisty.ReadValue("myprograme", "register1").equals("注册表中没有该值")){
ReadWriteRegisty.WriteValue("myprograme", "register1", String.valueOf(codeStringBigint));
ReadWriteRegisty.WriteValue("myprograme", "registertime", sf.format(new Date()));
ReadWriteRegisty.WriteValue("myprograme", "registertimetest", sf.format(new Date()));
ReadWriteRegisty.WriteValue("myprograme", "programe3", "sign3");
}else{
// 将配置信息解密与MAC地址进行比较,如果结果不同,弹出错误提示对话框
if(!new decrypt().Execdecrypt().contains(RegisterMark.getMAC())){
JOptionPane.showMessageDialog(RegisterFrame.this, "一个注册码只能在唯一一台计算机上使用");
return;
}
……// 省略部分代码
}
在上述代码中使用了encrypt类中的Excencrypt()进行加密操作,这个方法的关键代码如下:
public BigInteger Execencrypt(String str, String register) {
BigInteger codeStringBigint = BigInteger.ZERO;
try {
File f = new File("a.ini"); // 实例化a.ini文件的File对象
System.out.println(!f.exists());
if (!f.exists()) {
new genKey(); // 将公钥与私钥写入DAT类型的文件中
// 读取公钥
FileInputStream FIS = new FileInputStream("RSAPubKey.dat");
ObjectInputStream OIS = new ObjectInputStream(FIS);
RSAPublicKey RSAPK = (RSAPublicKey) OIS.readObject();
// 得到两个公钥的参数
BigInteger e = RSAPK.getPublicExponent(); BigInteger n = RSAPK.getModulus();
// 将明文转为大整数
byte[] strByte = (str.concat(register)).getBytes("UTF8");
BigInteger m = new BigInteger(strByte);
// 进行加密操作
codeStringBigint = m.modPow(e, n);
System.out.println("加密后的密文为:" + codeStringBigint);
// 保存密文
Properties pro = new Properties();
pro.put("register", String.valueOf(codeStringBigint));
try {
// 将信息保存在a.ini文件中
pro.store(new FileOutputStream("a.ini", true), null);
// 可以从a.ini中通过Properties.get方法读取配置信息
pro.load(new FileInputStream("a.ini"));
} catch (Exception ex) {
ex.printStackTrace();
}
}
} catch (Exception e) {
e.printStackTrace();
}
return codeStringBigint; // 将密文返回
}
对密文进行解密验证时使用decrypt类中的Execdecrypt()方法,在该方法中首先读取a.ini文件中的密文,然后进行解密,如果解密后的明文字符串中包含该机器的MAC地址字符串,或则说明用户在同一台机器上进行注册操作。
decrypt.java类中Execdecrypt()方法的关键代码如下:
public String Execdecrypt() {
String s="";
try{
Properties pro = new Properties(); // 获取Properties对象
try {
// 将信息包存在a.ini文件中
pro.store(new FileOutputStream("a.ini",true),null);
// 可以从a.ini中通过Properties.get()方法读取配置信息
pro.load(new FileInputStream("a.ini"));
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
// 获取在配置文件中的密文
String ctext=String.valueOf(String.valueOf(pro.get("register")));
// 将密文转为BigInteger形式
BigInteger c=new BigInteger(ctext);
// 获取私钥
FileInputStream f=new FileInputStream("RSAPrikey.dat");
ObjectInputStream b=new ObjectInputStream(f);
RSAPrivateKey prk=(RSAPrivateKey)b.readObject();
// 得到公钥的两个参数
BigInteger d=prk.getPrivateExponent();
BigInteger n=prk.getModulus();
// 解密处理
BigInteger m=c.modPow(d, n);
byte [] mt=m.toByteArray();
for(int i=0;i<mt.length;i++){
s+=(char)mt[i];
}
}catch(Exception e){
e.printStackTrace();
}
return s; // 将明文返回
}
注册机的实现
5.1 窗体概述
注册机是用于生成注册码的窗体,注册机窗体运行效果如图1所示。
图1 注册机窗体
在注册机窗体中,输入3位用户名,然后单击“生成注册码”按钮,在注册码4个文本框中将分别放置4组5位的注册码。这个注册码将用于在软件注册时使用。同时还为4个注册码文本框添加了鼠标单击事件,当用户在4组文本框中任意文本框中单击鼠标右键,窗体都会在鼠标单击位置弹出菜单。运行效果如图2所示。
图2 注册机窗体弹出菜单
5.2 窗体界面设计
注册机窗体的界面设计比较简单,首先为整个窗体添加布局管理器,这里使用绝对布局,然后将标签、文本框以及按钮放入窗体中合适的位置。
注册机窗体布局的关键代码如下:
public RegisterTradeMark() {
super();
getContentPane().setBackground(new Color(240, 255, 255));
try {
// 设定窗体的风格
UIManager.setLookAndFeel(new com.sun.java.swing.plaf.nimbus.NimbusLookAndFeel());
} catch (UnsupportedLookAndFeelException e) {
e.printStackTrace();
}
setTitle("注册机"); // 设置窗体标题
getContentPane().setLayout(null); // 设置容器的布局管理器为绝对布局
final JLabel label = new JLabel(); // 设置用户名标签
label.setBounds(10, 26, 52, 18);
label.setText("用户名:");
getContentPane().add(label); // 将用户名标签添加到容器中
userName = new JTextField();
userName.setBounds(68, 24, 147, 22);
getContentPane().add(userName); // 将用户名文本框添加到容器中
final JLabel label_1 = new JLabel();
label_1.setBounds(10, 66, 52, 18);
label_1.setText("注册码:");
getContentPane().add(label_1); // 将注册码文本框添加到容器中
final JPanel panel = new JPanel(); // 创建放置注册码文本框的面板
panel.setBackground(new Color(240, 255, 255));
panel.setLayout(null);
panel.setBounds(60, 50, 261, 68);
getContentPane().add(panel); // 将面板添加到容器中
t1 = new JTextField();
t1.setBounds(10, 21, 48, 22);
panel.add(t1); // 将文本框添加到面板中
t2 = new JTextField();
t2.setBounds(65, 21, 48, 22);
panel.add(t2);
t3 = new JTextField();
t3.setBounds(119, 21, 48, 22);
panel.add(t3);
t4 = new JTextField();
t4.setBounds(173, 21, 49, 22);
panel.add(t4);
final JPopupMenu popup=new JPopupMenu(); // 创建弹出式菜单
JMenuItem itemcut=new JMenuItem("Cut"); // 创建子菜单项
popup.add(itemcut); // 将子菜单项添加到菜单中
popup.addSeparator(); // 添加分割线
JMenuItem itemcopy=new JMenuItem("Copy");
popup.add(itemcopy);
popup.addSeparator();
JMenuItem itempaste=new JMenuItem("Paste");
popup.add(itempaste);
t1.addMouseListener(new PopAction(popup)); // 为各个文本框添加鼠标单击事件监听器
t2.addMouseListener(new PopAction(popup));
t3.addMouseListener(new PopAction(popup));
t4.addMouseListener(new PopAction(popup));
// 设置整个页面组件焦点从后向前
this.setFocusTraversalKeys(KeyboardFocusManager.BACKWARD_TRAVERSAL_KEYS, null);
……// 省略部分代码
button.setText("生成注册码"); // 创建“生成注册码”按钮
button.setBounds(93, 135, 106, 28);
getContentPane().add(button); // 将按钮添加到容器中
setSize(new Dimension(350,200));
// 设置窗体标题图标
setIconImage(CreatecdIcon.add("ico.jpg").getImage());
setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
}
5.3 生成注册码
生成注册码的功能应该在“生成注册码”按钮的事件监听器中完成,在按钮监听事件中首先获取用户名,然后根据用户名字符串经过一定的处理转为4组5位的注册码,这部分功能的关键代码如下:
button.addActionListener(new ActionListener() {
public void actionPerformed(final ActionEvent arg0) { // 重写actionPerformed()方法
String userNameT=userName.getText().trim(); // 获取用户名
// 调用RegisterMark类中的getRegister()方法将用户名进行加密
String s=RegisterMark.getRegister(userNameT);
String a[]=s.split("-"); // 将字符串以“-”字符进行分割
// 将数组中的字符添加到注册码文本框中
t1.setText(a[0]);
t2.setText(a[1]);
t3.setText(a[2]);
t4.setText(a[3]);
}
});
在上述代码中使用RegisterMark类中的getRegister()方法对用户名进行加密,这个方法的关键代码如下:
public static String getRegister(String s) {
String s1="",s2=""; // 初始化两个字符串
SimpleDateFormat sd=new SimpleDateFormat("yyyy"); // 设定事件的格式化对象
Date d=new Date(); // 初始化一个Date对象
s+=sd.format(d); // 将需要加密的字符串与当前年份连接
for(int i=0;i<s.length();i++){ // 循环字符串的个数
s1+=(int)s.charAt(i)*(i+1); // 对字符串中每个字符进行处理赋予s1字符串
}
for(int i=0;i<s1.length()/5;i++){ // 根据s1字符串进行处理获取字符串s2
if(i==s1.length()/5-1)
s2+=s1.substring(i*5,(i+1)*5);
else
s2+=s1.substring(i*5, (i+1)*5)+"-";
}
return s2; // 将字符串s2返回
}