共计 6586 个字符,预计需要花费 17 分钟才能阅读完成。
很多同学说 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 ????????????
关注我的公众号一起玩转技术