重写复现方法

This commit is contained in:
2025-08-28 18:09:20 +08:00
parent 0c26e0911e
commit 2948a25d9f
15 changed files with 887 additions and 8 deletions

View File

@@ -0,0 +1,18 @@
package com.mini.capi.biz.controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* <p>
* 前端控制器
* </p>
*
* @author gaoxq
* @since 2025-08-28
*/
@RestController
@RequestMapping("/biz/apiUser")
public class ApiUserController {
}

View File

@@ -0,0 +1,86 @@
package com.mini.capi.biz.domain;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import java.io.Serializable;
import java.time.LocalDateTime;
import lombok.Getter;
import lombok.Setter;
/**
* <p>
*
* </p>
*
* @author gaoxq
* @since 2025-08-28
*/
@Getter
@Setter
@TableName("biz_api_user")
public class ApiUser implements Serializable {
private static final long serialVersionUID = 1L;
@TableField("create_time")
private LocalDateTime createTime;
/**
* 用户编号
*/
@TableId(value = "user_id", type = IdType.AUTO)
private String userId;
/**
* 登录名称
*/
@TableField("api_user")
private String apiUser;
/**
* 登录密码
*/
@TableField("api_pswd")
private String apiPswd;
/**
* 用户名称
*/
@TableField("uname")
private String uname;
/**
* 状态
*/
@TableField("ustatus")
private String ustatus;
@TableField("update_time")
private LocalDateTime updateTime;
/**
* 租户id
*/
@TableField("f_tenant_id")
private String fTenantId;
/**
* 流程id
*/
@TableField("f_flow_id")
private String fFlowId;
/**
* 流程任务主键
*/
@TableField("f_flow_task_id")
private String fFlowTaskId;
/**
* 流程任务状态
*/
@TableField("f_flow_state")
private Integer fFlowState;
}

View File

@@ -0,0 +1,16 @@
package com.mini.capi.biz.mapper;
import com.mini.capi.biz.domain.ApiUser;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
/**
* <p>
* Mapper 接口
* </p>
*
* @author gaoxq
* @since 2025-08-28
*/
public interface ApiUserMapper extends BaseMapper<ApiUser> {
}

View File

@@ -0,0 +1,16 @@
package com.mini.capi.biz.service;
import com.mini.capi.biz.domain.ApiUser;
import com.baomidou.mybatisplus.extension.service.IService;
/**
* <p>
* 服务类
* </p>
*
* @author gaoxq
* @since 2025-08-28
*/
public interface ApiUserService extends IService<ApiUser> {
}

View File

@@ -0,0 +1,20 @@
package com.mini.capi.biz.service.impl;
import com.mini.capi.biz.domain.ApiUser;
import com.mini.capi.biz.mapper.ApiUserMapper;
import com.mini.capi.biz.service.ApiUserService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.springframework.stereotype.Service;
/**
* <p>
* 服务实现类
* </p>
*
* @author gaoxq
* @since 2025-08-28
*/
@Service
public class ApiUserServiceImpl extends ServiceImpl<ApiUserMapper, ApiUser> implements ApiUserService {
}

View File

@@ -1,10 +1,11 @@
package com.mini.capi.config;
import com.mini.capi.utils.vToken;
import com.mini.capi.biz.domain.ApiUser;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;
import jakarta.servlet.http.HttpSession;
@Component
public class AuthInterceptor implements HandlerInterceptor {
@@ -13,9 +14,9 @@ public class AuthInterceptor implements HandlerInterceptor {
public boolean preHandle(HttpServletRequest request,
HttpServletResponse response,
Object handler) throws Exception {
String token = request.getHeader("Authorization");
if (token == null || !vToken.isValidToken(token)) {
HttpSession session = request.getSession();
ApiUser apiUser = (ApiUser) session.getAttribute("Authorization");
if (apiUser == null) {
response.sendRedirect(request.getContextPath() + "/login");
return false;
}

View File

@@ -0,0 +1,52 @@
package com.mini.capi.model.auth;
import lombok.Data;
import java.io.Serializable;
@Data
public class Result implements Serializable {
// 状态码200表示成功其他表示错误
private int code;
// 响应信息
private String msg;
// 响应数据(可选)
private Object data;
// 私有构造方法,防止直接创建实例
private Result() {}
// 成功响应
public static Result success(String msg) {
Result result = new Result();
result.code = 200;
result.msg = msg;
return result;
}
// 带数据的成功响应
public static Result success(String msg, Object data) {
Result result = new Result();
result.code = 200;
result.msg = msg;
result.data = data;
return result;
}
// 错误响应
public static Result error(String msg) {
Result result = new Result();
result.code = 500; // 500表示服务器错误也可以根据实际情况使用其他错误码
result.msg = msg;
return result;
}
// 带自定义错误码的错误响应
public static Result error(int code, String msg) {
Result result = new Result();
result.code = code;
result.msg = msg;
return result;
}
}

View File

@@ -29,7 +29,7 @@ public class demo {
.pathInfo(Collections.singletonMap(OutputFile.xml, System.getProperty("user.dir") + "/src/main/resources/mapper"));
})
.strategyConfig(builder -> {
builder.addInclude("biz_sync_task_log")
builder.addInclude("biz_api_user")
.addTablePrefix("biz_")
.entityBuilder()
.enableLombok()

View File

@@ -0,0 +1,66 @@
package com.mini.capi.sys.controller;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.mini.capi.biz.domain.ApiUser;
import com.mini.capi.biz.service.ApiUserService;
import com.mini.capi.model.auth.Result;
import jakarta.annotation.Resource;
import jakarta.servlet.http.HttpSession;
import lombok.Data;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.io.Serializable;
import java.util.Objects;
@RestController
@RequestMapping("/Sys/login")
public class LoginController {
@Resource
private ApiUserService userService;
@Data
public static class LoginRequest implements Serializable {
private String username;
private String password;
}
/**
* 密码校验(生产环境需替换为加密比对)
*/
private boolean verifyPassword(String rawPassword, String encodedPassword) {
return Objects.equals(rawPassword, encodedPassword);
}
/**
* 用户登录
*/
@PostMapping("/userLogin")
public Result login(@RequestBody LoginRequest user, HttpSession session) {
try {
QueryWrapper<ApiUser> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("api_user", user.getUsername())
.eq("ustatus", 1);
ApiUser apiUser = userService.getOne(queryWrapper);
if (apiUser == null) {
return Result.error("账户不存在");
}
if (!verifyPassword(user.getPassword(), apiUser.getApiPswd())) {
// 可记录登录失败日志,用于后续风控
return Result.error("账户或密码错误");
}
session.setAttribute("Authorization", apiUser);
return Result.success("登录成功");
} catch (Exception e) {
return Result.error("登录失败,请稍后重试");
}
}
}

View File

@@ -10,4 +10,13 @@ public class LoginPageController {
public String loginPage() {
return "forward:/index.html";
}
/**
* 首页
*/
@GetMapping("/welcome")
public String welcomePage() {
return "forward:/views/demo.html";
}
}

View File

@@ -0,0 +1,25 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.mini.capi.biz.mapper.ApiUserMapper">
<!-- 通用查询映射结果 -->
<resultMap id="BaseResultMap" type="com.mini.capi.biz.domain.ApiUser">
<id column="user_id" property="userId" />
<result column="create_time" property="createTime" />
<result column="api_user" property="apiUser" />
<result column="api_pswd" property="apiPswd" />
<result column="uname" property="uname" />
<result column="ustatus" property="ustatus" />
<result column="update_time" property="updateTime" />
<result column="f_tenant_id" property="fTenantId" />
<result column="f_flow_id" property="fFlowId" />
<result column="f_flow_task_id" property="fFlowTaskId" />
<result column="f_flow_state" property="fFlowState" />
</resultMap>
<!-- 通用查询结果列 -->
<sql id="Base_Column_List">
create_time, user_id, api_user, api_pswd, uname, ustatus, update_time, f_tenant_id, f_flow_id, f_flow_task_id, f_flow_state
</sql>
</mapper>

View File

@@ -0,0 +1,324 @@
/* 全局样式重置增加box-sizing强制继承避免尺寸计算偏差 */
* {
margin: 0;
padding: 0;
box-sizing: border-box;
font-family: "Microsoft YaHei", sans-serif;
}
/* 样式变量:统一管理,减少硬编码 */
:root {
--primary-color: #3498db;
--primary-dark: #2980b9;
--primary-light: #e3f2fd;
--bg-gradient-1: #2c3e50;
--bg-gradient-2: #3498db;
--white: #fff;
--gray-light: #f8f9fa;
--gray-border: #ddd;
--gray-hover: #f1f1f1;
--text-gray: #555;
--text-light-gray: #888;
--danger-color: #e74c3c;
--shadow-normal: 0 10px 30px rgba(0, 0, 0, 0.15);
--shadow-modal: 0 10px 25px rgba(0, 0, 0, 0.2);
--shadow-input: 0 0 0 3px rgba(52, 152, 219, 0.1);
--transition-base: all 0.25s ease;
--login-box-width: 420px; /* 固定登录框宽度,避免变形 */
--input-height: 48px; /* 固定输入框高度,统一视觉 */
}
/* 页面主体:固定布局,确保登录框不被挤压 */
body {
display: flex;
align-items: center;
justify-content: center;
min-height: 100vh;
background: linear-gradient(135deg, var(--bg-gradient-1), var(--bg-gradient-2));
padding: 0 80px;
overflow: hidden; /* 禁止页面滚动 */
}
/* -------------------------- 左侧品牌区域:固定尺寸和位置 -------------------------- */
.brand-side {
width: 55%;
max-width: 700px;
color: var(--white);
padding: 40px;
text-align: center;
margin-right: 60px;
}
.brand-title-group {
margin-top: 10px;
}
.brand-side h1 {
font-size: 3.2rem;
margin-bottom: 15px;
text-shadow: 0 3px 12px rgba(0, 0, 0, 0.25);
letter-spacing: 1px;
}
.brand-divider {
width: 80px;
height: 3px;
background: linear-gradient(90deg, transparent, var(--white), transparent);
margin: 0 auto 20px;
opacity: 0.8;
}
.brand-side p {
font-size: 1.15rem;
opacity: 0.95;
line-height: 1.7;
text-shadow: 0 1px 5px rgba(0, 0, 0, 0.15);
}
.brand-icon {
display: flex;
align-items: center;
justify-content: center;
gap: 18px;
margin-bottom: 25px;
}
.brand-icon i {
font-size: 2.8rem;
opacity: 0.95;
transition: var(--transition-base);
}
.brand-icon i:hover {
transform: translateY(-6px) scale(1.05);
opacity: 1;
}
/* -------------------------- 右侧登录容器:固定尺寸,彻底解决变形 -------------------------- */
.login-container {
width: var(--login-box-width); /* 固定宽度,不随内容拉伸 */
background: var(--white);
border-radius: 12px;
box-shadow: var(--shadow-normal);
overflow: hidden;
transition: var(--transition-base);
position: relative;
z-index: 10;
}
.login-container:hover {
transform: translateY(-5px);
box-shadow: 0 12px 35px rgba(0, 0, 0, 0.18);
}
.login-header {
text-align: center;
padding: 25px;
background: var(--gray-light);
border-bottom: 1px solid var(--gray-border);
}
.login-header h2 {
font-size: 1.7rem;
color: #333;
font-weight: 600;
letter-spacing: 0.5px;
}
/* 表单区域:固定内边距,避免内容溢出 */
.login-form {
padding: 35px 30px;
width: 100%;
}
/* 表单组:固定间距,不随内容变化 */
.form-group {
margin-bottom: 28px;
width: 100%;
}
.required-mark {
color: var(--danger-color);
margin-left: 4px;
font-size: 1rem;
}
.form-group label {
display: block;
margin-bottom: 9px;
color: var(--text-gray);
font-size: 1rem;
font-weight: 500;
}
/* -------------------------- 输入框组:固定高度+定位,彻底解决变形 -------------------------- */
.input-group {
position: relative;
display: flex;
align-items: center;
width: 100%;
height: var(--input-height); /* 固定容器高度,与输入框一致 */
}
/* 输入框:固定高度+内边距,不拉伸 */
.form-group input {
width: 100%;
height: 100%; /* 继承容器高度,避免自行拉伸 */
padding: 0 18px 0 45px; /* 左侧留足图标空间,右侧留足按钮空间 */
border: 1px solid var(--gray-border);
border-radius: 8px;
font-size: 1rem;
outline: none;
transition: var(--transition-base);
background-color: var(--white);
resize: none;
appearance: none;
}
/* 输入框聚焦:仅改变边框和阴影,不改变尺寸 */
.form-group input:focus {
border-color: var(--primary-color);
box-shadow: var(--shadow-input);
background-color: var(--primary-light);
}
.form-group input:not(:placeholder-shown) {
border-color: #b3d9f2;
}
/* 输入框图标:固定左侧定位,不随输入框变化 */
.input-icon {
position: absolute;
left: 16px; /* 固定左侧距离,不偏移 */
color: var(--text-light-gray);
font-size: 1.1rem;
z-index: 1;
width: 20px; /* 固定图标宽度,避免抖动 */
text-align: center;
}
.form-group input:not(:placeholder-shown) + .input-icon {
color: var(--primary-color);
}
/* 密码切换按钮:固定最右侧,不偏移 */
.toggle-pwd {
position: absolute;
right: 12px; /* 固定右侧距离 */
top: 50%;
transform: translateY(-50%); /* 垂直居中 */
background: transparent;
border: none;
color: var(--text-light-gray);
cursor: pointer;
font-size: 1rem;
width: 24px;
height: 24px;
border-radius: 50%;
transition: var(--transition-base);
z-index: 2;
display: flex;
align-items: center;
justify-content: center;
}
.toggle-pwd:hover {
background-color: var(--gray-hover);
color: var(--text-gray);
}
/* 登录按钮:固定尺寸,不拉伸 */
.btn-login {
width: 100%;
height: 52px; /* 固定按钮高度 */
background: var(--primary-color);
color: var(--white);
border: none;
border-radius: 8px;
font-size: 1.05rem;
font-weight: 500;
cursor: pointer;
transition: var(--transition-base);
letter-spacing: 0.5px;
box-shadow: 0 3px 8px rgba(52, 152, 219, 0.2);
white-space: nowrap;
}
.btn-login:hover {
background: var(--primary-dark);
transform: translateY(-2px);
box-shadow: 0 5px 12px rgba(41, 128, 185, 0.25);
}
.btn-login:active {
transform: translateY(0);
box-shadow: 0 2px 5px rgba(41, 128, 185, 0.2);
}
/* -------------------------- 弹窗:固定层级,避免被遮挡 -------------------------- */
.modal {
display: none;
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.5);
justify-content: center;
align-items: center;
z-index: 9999; /* 最高层级,不被任何元素遮挡 */
opacity: 0;
transition: opacity 0.3s ease;
}
.modal.show {
opacity: 1;
}
.modal-content {
background: var(--white);
padding: 25px;
border-radius: 10px;
width: 320px;
text-align: center;
box-shadow: var(--shadow-modal);
transform: translateY(-20px);
transition: transform 0.3s ease;
}
.modal.show .modal-content {
transform: translateY(0);
}
.modal-content .icon {
font-size: 2.5rem;
color: var(--danger-color);
margin-bottom: 15px;
}
.modal-content p {
margin-bottom: 20px;
color: var(--text-gray);
font-size: 1rem;
line-height: 1.5;
}
.modal-content button {
padding: 10px 25px;
border: none;
border-radius: 6px;
background: var(--primary-color);
color: var(--white);
cursor: pointer;
transition: var(--transition-base);
font-size: 0.95rem;
}
.modal-content button:hover {
background: var(--primary-dark);
transform: translateY(-2px);
}
.modal-content button:active {
transform: translateY(0);
}

View File

@@ -0,0 +1,175 @@
/**
* 登录逻辑处理:阻止表单默认提交,校验用户名/密码,调用登录接口
* @param {Event} e - 表单提交事件
* @returns {boolean} - 返回false阻止默认提交
*/
function handleLogin(e) {
// 1. 阻止表单默认提交行为(避免页面刷新)
e.preventDefault();
// 2. 获取并清理用户名、密码(去除前后空格)
const username = document.getElementById('username').value.trim();
const password = document.getElementById('password').value.trim();
// 3. 前端简单校验(空值判断)
if (!username) {
showModal('请输入用户名');
return false;
}
if (!password) {
showModal('请输入密码');
return false;
}
// 4. 调用后端登录接口POST请求
fetch('Sys/login/userLogin', {
method: 'POST',
headers: {
'Content-Type': 'application/json' // 声明请求体为JSON格式
},
body: JSON.stringify({username, password}) // 转换为JSON字符串
})
.then(response => {
// 4.1 校验HTTP响应状态非200-299范围视为错误
if (!response.ok) {
throw new Error(`请求失败,状态码:${response.status}`);
}
// 4.2 解析响应体假设后端返回JSON格式
return response.json();
})
.then(data => {
// 5. 处理后端返回结果(假设接口返回 { code: 200, msg: '成功' } 结构)
if (data.code === 200) {
// 登录成功跳转到demo页面可根据实际需求修改跳转路径
window.location.href = 'welcome';
} else {
// 业务错误:显示后端返回的错误信息,无信息时显示默认提示
showModal(data.msg || '登录失败,请检查账号密码是否正确');
}
})
.catch(error => {
// 6. 捕获请求异常网络错误、接口500等
showModal(`登录异常:${error.message}`);
});
return false;
}
/**
* 显示错误弹窗
* @param {string} msg - 弹窗提示内容
*/
function showModal(msg) {
const errorMsgElem = document.getElementById('errorMsg');
const modalElem = document.getElementById('errorModal');
// 设置弹窗文本内容
errorMsgElem.innerText = msg;
// 显示弹窗通过flex布局实现居中
modalElem.style.display = 'flex';
// 延迟添加show类触发过渡动画避免动画不生效
setTimeout(() => {
modalElem.classList.add('show');
}, 10);
}
/**
* 关闭错误弹窗
*/
function closeModal() {
const modalElem = document.getElementById('errorModal');
// 移除show类触发淡出动画
modalElem.classList.remove('show');
// 动画结束后隐藏弹窗300ms与CSS过渡时间保持一致
setTimeout(() => {
modalElem.style.display = 'none';
}, 300);
}
/**
* 清空指定输入框内容
* @param {string} inputId - 输入框的ID属性值
*/
function clearInput(inputId) {
const inputElem = document.getElementById(inputId);
if (inputElem) {
// 清空输入框内容
inputElem.value = '';
// 触发input事件更新清空按钮的显示状态
inputElem.dispatchEvent(new Event('input'));
// 聚焦到当前输入框(提升用户体验)
inputElem.focus();
}
}
/**
* 切换密码输入框的可见性(明文/密文切换)
*/
function togglePasswordVisibility() {
const passwordInput = document.getElementById('password');
const toggleBtn = passwordInput.parentElement.querySelector('.toggle-pwd i');
if (passwordInput.type === 'password') {
// 切换为明文显示
passwordInput.type = 'text';
// 更换图标为"眼睛"(表示当前是明文)
toggleBtn.classList.remove('bi-eye-slash');
toggleBtn.classList.add('bi-eye');
} else {
// 切换为密文显示
passwordInput.type = 'password';
// 更换图标为"眼睛斜杠"(表示当前是密文)
toggleBtn.classList.remove('bi-eye');
toggleBtn.classList.add('bi-eye-slash');
}
}
/**
* 输入框内容变化时的动态反馈:控制清空按钮的显示/隐藏
* @param {HTMLInputElement} inputElem - 输入框元素
*/
function handleInputChange(inputElem) {
const clearBtn = inputElem.parentElement.querySelector('.clear-btn');
if (clearBtn) {
// 输入框有内容时显示清空按钮,无内容时隐藏
clearBtn.hidden = !inputElem.value.trim();
}
}
// 页面加载完成后:绑定全局交互事件
document.addEventListener('DOMContentLoaded', () => {
// 1. 获取用户名和密码输入框元素
const usernameInput = document.getElementById('username');
const passwordInput = document.getElementById('password');
// 2. 绑定输入框内容变化事件(控制清空按钮显示)
if (usernameInput) {
usernameInput.addEventListener('input', () => handleInputChange(usernameInput));
}
if (passwordInput) {
passwordInput.addEventListener('input', () => handleInputChange(passwordInput));
}
// 3. 点击弹窗外部关闭弹窗
const modalElem = document.getElementById('errorModal');
modalElem.addEventListener('click', (e) => {
// 仅当点击弹窗背景(而非内容区)时关闭
if (e.target === modalElem) {
closeModal();
}
});
// 4. 按ESC键关闭弹窗
document.addEventListener('keydown', (e) => {
// 仅当弹窗显示时生效
if (e.key === 'Escape' && modalElem.style.display === 'flex') {
closeModal();
}
});
// 5. 页面初始化时,默认聚焦到用户名输入框(提升用户体验)
if (usernameInput) {
usernameInput.focus();
}
});

View File

@@ -1,10 +1,71 @@
<!DOCTYPE html>
<html lang="en">
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>Title</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.2/font/bootstrap-icons.min.css">
<link rel="stylesheet" href="./assets/css/login-style.css">
<title>cApi登录</title>
</head>
<body>
<!-- 左侧品牌区域 -->
<div class="brand-side">
<div class="brand-icon">
<i class="bi bi-shield-lock" style="color: #ecf0f1;"></i>
<i class="bi bi-key" style="color: #2ecc71;"></i>
<i class="bi bi-server" style="color: #1abc9c;"></i>
</div>
<div class="brand-title-group">
<h1>cApi管理系统</h1>
<div class="brand-divider"></div>
<p>安全、高效的一站式管理解决方案,为您的业务保驾护航</p>
</div>
</div>
<!-- 右侧登录容器:固定宽度,避免变形 -->
<div class="login-container">
<div class="login-header">
<h2>系统登录</h2>
</div>
<form class="login-form" onsubmit="return handleLogin(event)">
<!-- 用户名输入组:结构扁平化,避免嵌套冲突 -->
<div class="form-group">
<label for="username">
账户
<span class="required-mark">*</span>
</label>
<!-- 输入框容器:仅包裹输入框+功能按钮,固定定位规则 -->
<div class="input-group">
<input type="text" id="username" placeholder="请输入用户名" required>
<i class="bi bi-person input-icon"></i>
</div>
</div>
<!-- 密码输入组:同用户名结构,保持一致性 -->
<div class="form-group">
<label for="password">
密码
<span class="required-mark">*</span>
</label>
<div class="input-group">
<input type="password" id="password" placeholder="请输入密码" required>
<i class="bi bi-lock input-icon"></i>
</div>
</div>
<button type="submit" class="btn-login">登录</button>
</form>
</div>
<!-- 错误提示弹窗:固定层级,避免被遮挡 -->
<div class="modal" id="errorModal">
<div class="modal-content">
<i class="bi bi-exclamation-circle icon"></i>
<p id="errorMsg"></p>
<button onclick="closeModal()">确定</button>
</div>
</div>
<script src="./assets/js/login-script.js"></script>
</body>
</html>
</html>

View File

@@ -0,0 +1,10 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
</body>
</html>