背景
到TL有整整一年了,在这一年中公司从无到有,完成了两个自动化系统整合项目,老板一直强调模块化设计这个理念,可是由于团队基础实在是薄弱,很多规范没办法执行起来,以致于这两个项目的源码惨不忍睹,代码写得很乱,可以重复利用的模块实在是太少。所以今年我主要的精力投在软件开发规范化这一块,以系统可扩展,模块化,可复用性为原则。
虽然自已技术还是个小菜,但只要有思想就应该是简单的问题,那么我从一个“用户登录模块”设计开始,把这个模块抽离出来,让所有子系统都能够复用这个登录组件,并且降低模块与主框架之间耦合。
在制造业的系统架构中,一般分为好几个工作站,每个工作站都有一个子系统,每个子系统都有需要有用户登录权限控制,所以这就意味着每个子系统都要开发这个模块,如果子系统比较多,这样开发成本就比较高,重复代码也很多,我分析了一下,这个用户登录模块有如下特点:
-
有统一的界面风格
-
模块根据用户权限不同加载对应的系统子系统
-
模块标题不一样
我们如何设计达到组件可以重复利用?
- 复用性:让所有子系统都能够重复利用这个用户登录
- 易用性:通过配置文件根据不同的子系统设置不一样的系统标题,子系统主框架调用组件简单化
- 我后台做一个WCF服务作为用户权限逻辑处理,登录组件直接调这个服务,把用户登录逻辑处理层从系统框架主分离出来成一个独立模块,子模块只需要引入这个组 件,做相应的配置,调用用户验证接口,当登录成功时,组件内部会消息通知主框架(订阅消息)进行启动加载主界面,从而达到成功登录的目的。
设计思想
设计步骤
1.设计WCF Service作后台用户登录验证处理
using System.ServiceModel; using Xiaocai.Security.IDAL; namespace Xiaocai.Commons { // NOTE: You can use the "Rename" command on the "Refactor" menu to change the interface name "IUserService" in both code and config file together. [ServiceContract] public interface ILoginService { [OperationContract] AuthUser CheckUser(string userId, string password); } }
using System; using System.Collections.Generic; using System.Linq; using System.Runtime.Serialization; using System.ServiceModel; using System.Text; using Xiaocai.Commons; using Xiaocai.Security.Core.Admin; using Xiaocai.Security.Core.Helpers; using Xiaocai.Security.IDAL; namespace Xiaocai.SystemHelper.Services { // NOTE: You can use the "Rename" command on the "Refactor" menu to change the class name "UserService" in code, svc and config file together. public class LoginService : ILoginService { private AuthController _controller; public LoginService() { _controller=new AuthController(); } public AuthUser CheckUser(string userId, string password) { AuthUser authUser=null; bool result=_controller.Login(userId, password); if (result) { authUser = _controller.AuthUser; } return authUser; } } }
我主要总结原理,很多细节就不多写了,所以服务中的验证我封装在另一组件中,此处省略。
2.发布这个WCF服务到IIS服务器中,测试如一下是否发布成功,出现如下页面表示已成功:
3.调用服务客户端
using System; using System.ServiceModel; using Xiaocai.Commons; using Xiaocai.Security.IDAL; namespace Xiaocai.SystemHelper.Proxy { public class LoginHelper { public static AuthUser OnLogin(string userId,string password) { AuthUser authUser = null; using (ChannelFactory<ILoginService> channelFactory = new ChannelFactory<ILoginService>("userService")) { ILoginService proxy = channelFactory.CreateChannel(); using (proxy as IDisposable) { authUser = proxy.CheckUser(userId, password); return authUser; } } } } }
4.新建一Winform项目作为登录组件,命名为Xiaocai.SecurityControlLibaray,把项目输出类型改为类库,以便子系统主框引入调用。
/****************************************************************************** * This document is the property of ShangHai TLian Agent Technology Co., Ltd. (TLAgent). * No exploitation or transfer of any information contained herein is permitted * in the absence of an agreement with Xiaocai * and neither the document nor any such information * may be released without the written consent of Xiaocai * * All right reserved by Xiaocai ******************************************************************************* * Owner: Agan * Version: 1.0.0.0 * Component:for login function * Function Description:* * Revision / History *------------------------------------------------------------------------------ * Flag Date Who Changes Description * -------- -------- --------------- ------------------------------------------- * 1 20130715 Agan File created *------------------------------------------------------------------------------ */ using System; using System.Configuration; using System.Windows.Forms; using Xiaocai.Commons; using Xiaocai.Security.IDAL; using Xiaocai.SystemHelper.Proxy; namespace Xiaocai.SecurityControlLibaray { public partial class LoginForm : Form { public LoginForm() { InitializeComponent(); } public virtual string Title { get; set; } private void LoginForm_Load(object sender, EventArgs e) { if (string.IsNullOrEmpty(ConfigurationManager.AppSettings["Title"])) { prgTitle.Text = string.Format("{0}", Title); } else { prgTitle.Text = string.Format("{0}", ConfigurationManager.AppSettings["Title"]); } txtUserId.Select(); } private void OnLogin(object sender, EventArgs e) { string userId = txtUserId.Text.Trim(); string password = txtPassWord.Text.Trim(); if (string.IsNullOrEmpty(userId) || string.IsNullOrEmpty(password)) { if (string.IsNullOrEmpty(userId)) { MessageHelper.ShowError("用户名不能为空!"); txtUserId.Focus(); txtUserId.SelectAll(); return; } if (string.IsNullOrEmpty(password)) { MessageHelper.ShowError(@"密码不能为空!"); txtUserId.Focus(); txtUserId.SelectAll(); return; } txtUserId.Focus(); txtUserId.SelectAll(); return; } AuthUser authUser = LoginHelper.OnLogin(userId, password); if (authUser!=null) { this.Hide(); EventService.Publish(authUser);//用户验证成功,发布消息,把登录用户传送出去 Application.ThreadException += Application_ThreadException; } else { MessageHelper.ShowError("用户名或密码不正确,请联系管理员!"); txtUserId.Focus(); txtUserId.SelectAll(); } } private static void Application_ThreadException(object sender, System.Threading.ThreadExceptionEventArgs ex) { string message = string.Format("{0}\r\n操作发生错误,您需要退出系统么?", ex.Exception.Message); MessageBox.Show(message); Application.Exit(); } private void OnCancel(object sender, EventArgs e) { Application.Exit(); } private void txtPassWord_KeyDown(object sender, KeyEventArgs e) { if (e.KeyCode == Keys.Enter) { OnLogin(sender,e); } } private void txtUserId_KeyDown(object sender, KeyEventArgs e) { if (e.KeyCode == Keys.Enter) { txtPassWord.Focus(); } } } }
6.系统子框架调用
using System; using System.Collections.Generic; using System.Linq; using System.Windows.Forms; using TLAgent.Commons; using TLAgent.Security.Core; using TLAgent.Security.IDAL; using TLAgent.SecurityControlLibaray; namespace TLAgent.Assembling.SecurityManager { static class Program { /// <summary> /// The main entry point for the application. /// </summary> [STAThread] static void Main() { Application.EnableVisualStyles(); Application.SetCompatibleTextRenderingDefault(false); AppCore.Startup(); //Application.Run(new LoginForm()); OnLonin(); } private static void OnLonin() { //订阅消息 EventAggregatorRepository.EventAggregator .GetEvent<ReceiveObjectEvent>() .Subscribe(SuccessLogin); Application.Run(new LoginForm()); } /// <summary> /// 登录成功加载主窗体 /// </summary> /// <param name="objParam">用户实体对象</param> private static void SuccessLogin(object objParam) { SecurityControlLibaray.SplashScreen.Splasher.Show(typeof(SplasherForm)); MainForm form = new MainForm(); var user = objParam as AuthUser; if (user != null) { form.AuthUser = user; } form.StartPosition = FormStartPosition.CenterScreen; form.ShowDialog(); } } }
7.配置文件中设置,如下:
<?xml version="1.0"?> <configuration> <!--WCF服务配置文件--> <system.serviceModel> <client> <endpoint address="http://localhost/SystemServices/LoginService.svc" binding="basicHttpBinding" contract="Xiaocai.Commons.ILoginService" name="userService"/> </client> </system.serviceModel> <appSettings> <add key="Title" value="用户权限管理系统"/><!--系统标题设置--> <add key="Copyright" value=" 版权所有:小菜成長記"/><!--系统版权设置--> <!-- Database with WebService --> <add key="IsRemote" value="N"/> <add key="GlobalSessionFactory" value="Xiaocai.Security.DAL.Global.SQLServerSessionFactory,Xiaocai.Security.DAL.Global"/> <!-- Database without WCFService --> <add key="Database.SqlServerConn" value="Data Source=localhost,1433;Network Library=DBMSSOCN;Initial Catalog=SecurityDB;User ID=root;Password=12345;"/> </appSettings> </configuration>
运行子系统,显示如下:
这个标题可以根据不同子系统功能在配置文件中作对应设置,到此基本完成这个登录组件的设计,这个组件可以用于所有子系统的登录界面,多次重复利用,减少开发成本。
附:因本人技术有限,也许会有更好的方法做这个模块化设计,希望高人指点,在不断学习中进步。