很多同学说AgileConfig的UI切实是太丑了。我想想也是的,原本这个我的项目是我本人应用的,一开始甚至连UI都没有,全靠手动在数据库里批改数据。起初加上了UI也是应用了老掉牙的bootstrap3做为根底款式。前台框架也是应用了angularjs,同样是老掉牙的货色。过年期间终于下决心翻新AgileConfig的前端UI。最初抉择的前端UI框架为AntDesign Pro + React。至于为啥选Ant-Design Pro是因为他难看,而且风行,抉择React是因为VUE跟Angular我都略知一二,罗唆趁此机会学一学React为何物,为何这么风行。
登录的认证计划为JWT,其实自己对JWT不太感冒(请看这里《咱们真的须要jwt吗?》),无奈大家都喜爱,那我也只能随大流。
其实基于ant-design pro的界面我曾经翻的差不多了,因为它反对mock数据,所以我一行后盾代码都没批改,曾经把界面快写完了。从当初开始要真正的跟后端代码进行联调了。那么咱们先从登录开始吧。先看看后端asp.net core方面会如何进行批改。

批改ASP.NET Core后端代码

  "JwtSetting": {    "SecurityKey": "xxxxxxxxxxxx", // 密钥    "Issuer": "agileconfig.admin", // 颁发者    "Audience": "agileconfig.admin", // 接收者    "ExpireSeconds": 20 // 过期工夫 s  }

在appsettings.json文件增加jwt相干配置。

  public class JwtSetting    {        static JwtSetting()        {            Instance = new JwtSetting();            Instance.Audience = Global.Config["JwtSetting:Audience"];            Instance.SecurityKey = Global.Config["JwtSetting:SecurityKey"];            Instance.Issuer = Global.Config["JwtSetting:Issuer"];            Instance.ExpireSeconds = int.Parse(Global.Config["JwtSetting:ExpireSeconds"]);        }        public string SecurityKey { get; set; }        public string Issuer { get; set; }        public string Audience { get; set; }        public int ExpireSeconds { get; set; }        public static JwtSetting Instance        {            get;        }    }

定义一个JwtSetting类,用来读取配置。

        public void ConfigureServices(IServiceCollection services)        {            services.AddMemoryCache();            services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)                      .AddJwtBearer(options =>                      {                          options.TokenValidationParameters = new TokenValidationParameters                          {                              ValidIssuer = JwtSetting.Instance.Issuer,                              ValidAudience = JwtSetting.Instance.Audience,                              IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(JwtSetting.Instance.SecurityKey)),                          };                      });            services.AddCors();            services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_3_0).AddRazorRuntimeCompilation();            services.AddFreeSqlDbContext();            services.AddBusinessServices();            services.AddAntiforgery(o => o.SuppressXFrameOptionsHeader = true);        }

批改Startup文件的ConfigureServices办法,批改认证Scheme为JwtBearerDefaults.AuthenticationScheme,在AddJwtBearer办法内配置jwt相干配置信息。因为前后端拆散我的项目所以有可能api跟ui部署在不同的域名下,所以开启Cors。

     // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.        public void Configure(IApplicationBuilder app, IWebHostEnvironment env, IServiceProvider serviceProvider)        {            if (env.IsDevelopment())            {                app.UseDeveloperExceptionPage();            }            else            {                app.UseMiddleware<ExceptionHandlerMiddleware>();            }            app.UseCors(op=> {                op.AllowAnyOrigin();                op.AllowAnyMethod();                op.AllowAnyHeader();            });            app.UseWebSockets(new WebSocketOptions()            {                KeepAliveInterval = TimeSpan.FromSeconds(60),                ReceiveBufferSize = 2 * 1024            });            app.UseMiddleware<WebsocketHandlerMiddleware>();            app.UseStaticFiles();            app.UseRouting();            app.UseAuthentication();            app.UseAuthorization();            app.UseEndpoints(endpoints =>            {                endpoints.MapDefaultControllerRoute();            });        }

批改Startup的Configure办法,配置Cors为Any。

    public class JWT    {        public static string GetToken()        {            //创立用户身份标识,可按须要增加更多信息            var claims = new Claim[]            {    new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()),    new Claim("id", "admin", ClaimValueTypes.String), // 用户id    new Claim("name", "admin"), // 用户名    new Claim("admin", true.ToString() ,ClaimValueTypes.Boolean) // 是否是管理员            };            var key = Encoding.UTF8.GetBytes(JwtSetting.Instance.SecurityKey);            //创立令牌            var token = new JwtSecurityToken(              issuer: JwtSetting.Instance.Issuer,              audience: JwtSetting.Instance.Audience,              signingCredentials: new SigningCredentials(new SymmetricSecurityKey(key), SecurityAlgorithms.HmacSha256Signature),              claims: claims,              notBefore: DateTime.Now,              expires: DateTime.Now.AddSeconds(JwtSetting.Instance.ExpireSeconds)            );            string jwtToken = new JwtSecurityTokenHandler().WriteToken(token);            return jwtToken;        }    }

增加一个JWT动态类用来生成jwt的token。因为agileconfig的用户只有admin一个所以这里用户名,ID都间接写死。

 [HttpPost("admin/jwt/login")]        public async Task<IActionResult> Login4AntdPro([FromBody] LoginVM model)        {            string password = model.password;            if (string.IsNullOrEmpty(password))            {                return Json(new                {                    status = "error",                    message = "明码不能为空"                });            }            var result = await _settingService.ValidateAdminPassword(password);            if (result)            {                var jwt = JWT.GetToken();                return Json(new {                     status="ok",                    token=jwt,                    type= "Bearer",                    currentAuthority = "admin"                });            }            return Json(new            {                status = "error",                message = "明码谬误"            });        }

新增一个Action办法做为登录的入口。在这里验证完明码后生成token,并且返回到前端。
到这里.net core这边后端代码改变的差不多了。次要是增加jwt相干的货色,这些内容网上曾经写了很多了,不在赘述。
上面开始批改前端代码。

批改AntDesign Pro的代码

AntDesign Pro曾经为咱们生成好了登录页面,登录的逻辑等,然而原来的登录是假的,也不反对jwt token做为登录凭证,上面咱们要批改多个文件来欠缺这个登录。

export function setToken(token:string): void {  localStorage.setItem('token', token);}export function getToken(): string {  var tk = localStorage.getItem('token');  if (tk) {    return tk as string;  }  return '';}

在utils/authority.ts文件内新增2个办法,用来存储跟获取token。咱们的jwt token存储在localStorage里。

/** 配置request申请时的默认参数 */const request = extend({  prefix: 'http://localhost:5000',  errorHandler, // 默认错误处理  credentials: 'same-origin', // 默认申请是否带上cookie,});const authHeaderInterceptor = (url: string, options: RequestOptionsInit) => {  const authHeader = { Authorization: 'Bearer ' + getToken() };  return {    url: `${url}`,    options: { ...options, interceptors: true, headers: authHeader },  };};request.interceptors.request.use(authHeaderInterceptor);

批改utils/request.ts文件,定义一个增加Authorization头部的拦截器,并且应用这个拦截器,这有每次申请的时候主动会带上这个头部,把jwt token传送到后盾。
设置prefix为http://localhost:5000这是咱们的后端api的服务地址,真正生产的时候会替换为正式地址。
设置credentials为same-origin。

export async function accountLogin(params: LoginParamsType) {  return request('/admin/jwt/login', {    method: 'POST',    data: params,  });}

在services/login.ts文件内新增发动登录申请的办法。

 effects: {    *login({ payload }, { call, put }) {      const response = yield call(accountLogin, payload);      yield put({        type: 'changeLoginStatus',        payload: response,      });      // Login successfully      if (response.status === 'ok') {        const urlParams = new URL(window.location.href);        const params = getPageQuery();        message.success('???? ???? ????  登录胜利!');        let { redirect } = params as { redirect: string };        if (redirect) {          console.log('redirect url ' , redirect);          const redirectUrlParams = new URL(redirect);          if (redirectUrlParams.origin === urlParams.origin) {            redirect = redirect.substr(urlParams.origin.length);            if (redirect.match(/^\/.*#/)) {              redirect = redirect.substr(redirect.indexOf('#') + 1);            }          } else {            window.location.href = '/';            return;          }        }        history.replace(redirect || '/');      }    },     reducers: {    changeLoginStatus(state, { payload }) {      setAuthority(payload.currentAuthority);      setToken(payload.token)      return {        ...state,        status: payload.status,        type: payload.type,      };    },  },

批改models/login.ts文件,批改effects的login办法,在外部替换原来的fakeAccountLogin为accountLogin。同时批改reducers外部的changeLoginStatus办法,增加setToken的代码,这有批改后登录胜利后token就会被存储起来。

  effects: {    *fetch(_, { call, put }) {      const response = yield call(queryUsers);      yield put({        type: 'save',        payload: response,      });    },    *fetchCurrent(_, { call, put }) {      const response = {        name: '管理员',        userid: 'admin'      };      yield put({        type: 'saveCurrentUser',        payload: response,      });    },  },

批改models/user.ts文件,批改effects的fetchCurrent办法为间接返回response。原本fetchCurrent是会去后盾拉以后用户信息的,因为agileconfig的用户就admin一个,所以我间接写死了。

让咱们试一下登录吧:)
源码在这:https://github.com/kklldog/AgileConfig/tree/react_ui ????????????

关注我的公众号一起玩转技术