实现登录注册接口

Change-Id: I3d57cca89cac8945d562f6a39127b3454c1cd9ac
diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties
index 3ca17a4..db34ebe 100644
--- a/src/main/resources/application.properties
+++ b/src/main/resources/application.properties
@@ -1 +1,19 @@
-spring.application.name=backend
+# ??MySQL??
+spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
+spring.datasource.url=jdbc:mysql://localhost:3306/groupone_db\
+?useSSL=false\
+&serverTimezone=Asia/Shanghai\
+&characterEncoding=utf8\
+&allowPublicKeyRetrieval=true
+
+spring.datasource.username=root
+spring.datasource.password=Rfw@2935
+
+# ???????????
+spring.datasource.hikari.maximum-pool-size=10
+spring.datasource.hikari.minimum-idle=5
+spring.datasource.hikari.idle-timeout=600000
+spring.datasource.hikari.connection-timeout=30000
+
+# ???? MyBatis ?????
+mybatis.configuration.map-underscore-to-camel-case=true
\ No newline at end of file
diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml
new file mode 100644
index 0000000..6cf60f3
--- /dev/null
+++ b/src/main/resources/application.yml
@@ -0,0 +1,60 @@
+spring:
+  application:
+    name: PtSite
+  mail:
+    host: smtp.qq.com
+    port: 465
+    username: 583398241@qq.com
+    password: feojrgbupvmxbbgi
+    protocol: smtps
+    properties:
+      mail.smtp.auth: true
+      mail.smtp.ssl.enable: true
+      mail.smtp.ssl.trust: smtp.qq.com
+      mail.smtp.socketFactory.class: javax.net.ssl.SSLSocketFactory
+
+  datasource:
+    driver-class-name: com.mysql.cj.jdbc.Driver
+    url: jdbc:mysql://localhost:3306/groupone_db?useSSL=false&serverTimezone=Asia/Shanghai&characterEncoding=utf8
+    username: root
+    password: "Rfw@2935"
+    hikari:
+      maximum-pool-size: 10
+      minimum-idle: 5
+      idle-timeout: 600000
+      connection-timeout: 30000
+
+  elasticsearch:
+    rest:
+      uris: http://192.168.74.129:9200
+
+  servlet:
+    multipart:
+      enabled: true
+      max-file-size: 1024MB
+      max-request-size: 2048MB
+
+  # Redis配置(当前已注释)
+  # data:
+  #   redis:
+  #     host: 192.168.74.129
+  #     port: 6379
+  #     database: 0
+  #     lettuce:
+  #       pool:
+  #         max-active: 8
+  #         max-idle: 8
+  #         min-idle: 0
+
+mybatis:
+  configuration:
+    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
+    map-underscore-to-camel-case: true
+
+server:
+  port: 8080
+
+ark:
+  api:
+    key: 5c648105-9e49-4a78-a1b2-ee8fcc7c02ad
+
diff --git a/src/main/resources/schema.sql b/src/main/resources/schema.sql
new file mode 100644
index 0000000..8bb0c85
--- /dev/null
+++ b/src/main/resources/schema.sql
@@ -0,0 +1,45 @@
+CREATE TABLE `user` (
+                        `user_id` INT PRIMARY KEY AUTO_INCREMENT,
+                        `username` VARCHAR(255) NOT NULL UNIQUE,
+                        `email` VARCHAR(255) NOT NULL UNIQUE,
+                        `password` VARCHAR(255) NOT NULL,
+                        `address` VARCHAR(255),
+                        `role` VARCHAR(50) NOT NULL DEFAULT 'user',
+                        `profile_pic` VARCHAR(255),
+                        `registration_date` DATETIME NOT NULL,  -- 推荐用时间类型(而非字符串)
+                        `identification_number` VARCHAR(18),    -- 身份证号建议用 VARCHAR(18)
+                        `avatar` VARCHAR(255),
+                        `isfollowed` BOOLEAN NOT NULL DEFAULT FALSE,
+                        INDEX `idx_user_id` (`user_id`)          -- 可选:主键通常自带索引
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
+
+CREATE TABLE invite_code (
+                             code VARCHAR(20) PRIMARY KEY,
+                             max_uses INT NOT NULL,
+                             remaining_uses INT NOT NULL,
+                             expiry_time DATETIME NOT NULL,
+                             created_by INT NOT NULL,
+                             created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
+                             FOREIGN KEY (created_by) REFERENCES user(user_id)
+);
+
+CREATE TABLE user_follow (
+                             follower_id INT NOT NULL,
+                             followed_id INT NOT NULL,
+                             created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
+                             PRIMARY KEY (follower_id, followed_id),
+                             FOREIGN KEY (follower_id) REFERENCES user(user_id),
+                             FOREIGN KEY (followed_id) REFERENCES user(user_id)
+);
+
+INSERT INTO `user` (
+    username, email, password, registration_date, identification_number, role
+) VALUES (
+             'admin', 'admin@example.com', 'admin123', NOW(), 87654321, 'admin'
+         );
+
+INSERT INTO invite_code (
+    code, max_uses, remaining_uses, expiry_time, created_by
+) VALUES
+      ('WELCOME2025', 5, 5, '2026-01-01 00:00:00', 1),
+      ('INVITEONLY', 3, 3, '2025-12-31 23:59:59', 1);
\ No newline at end of file
diff --git a/src/main/resources/static/home.html b/src/main/resources/static/home.html
new file mode 100644
index 0000000..635856a
--- /dev/null
+++ b/src/main/resources/static/home.html
@@ -0,0 +1,24 @@
+<!-- src/main/resources/static/home.html -->
+<!DOCTYPE html>
+<html lang="zh-CN">
+<head><meta charset="UTF-8"><title>首页</title></head>
+<body>
+<h1>欢迎访问受保护页面</h1>
+<div id="userInfo"></div>
+<script>
+    (async ()=>{
+        const token = localStorage.getItem('token');
+        if (!token) return window.location.href='login.html';
+        const res = await fetch('/api/me', {
+            headers: { 'token': token }
+        });
+        const json = await res.json();
+        if (json.code!==0) {
+            return window.location.href='login.html';
+        }
+        document.getElementById('userInfo')
+            .innerText = '当前用户:' + json.data.username;
+    })();
+</script>
+</body>
+</html>
diff --git a/src/main/resources/static/login.html b/src/main/resources/static/login.html
new file mode 100644
index 0000000..993941d
--- /dev/null
+++ b/src/main/resources/static/login.html
@@ -0,0 +1,38 @@
+<!-- src/main/resources/static/login.html -->
+<!DOCTYPE html>
+<html lang="zh-CN">
+<head>
+    <meta charset="UTF-8">
+    <title>登录</title>
+</head>
+<body>
+<h2>用户登录</h2>
+<form id="loginForm">
+    <label>身份证号:</label>
+    <input type="number" name="identificationNumber" required>
+    <label>密码:</label>
+    <input type="password" name="password" required>
+    <button type="submit">登录</button>
+</form>
+<script>
+    document.getElementById('loginForm')
+        .addEventListener('submit', async e => {
+            e.preventDefault();
+            const form = new URLSearchParams(new FormData(e.target));
+            const res = await fetch('/login', {
+                method: 'POST',
+                body: form
+            });
+            const json = await res.json();
+            if (res.ok && json.code === 0) {
+                // 保存 token
+                localStorage.setItem('token', json.data);
+                // 跳转到受保护的页面
+                window.location.href = 'home.html';
+            } else {
+                alert(json.msg);
+            }
+        });
+</script>
+</body>
+</html>
diff --git a/src/main/resources/static/register.html b/src/main/resources/static/register.html
new file mode 100644
index 0000000..1e43e90
--- /dev/null
+++ b/src/main/resources/static/register.html
@@ -0,0 +1,54 @@
+<!-- src/main/resources/static/register.html -->
+<!DOCTYPE html>
+<html lang="zh-CN">
+<head>
+    <meta charset="UTF-8">
+    <title>注册</title>
+</head>
+<body>
+<h2>用户注册</h2>
+<form id="regForm">
+    <label>用户名:</label>
+    <input type="text" name="username" required>
+    <label>邮箱:</label>
+    <input type="email" name="email" required>
+    <button type="button" id="sendCode">发送验证码</button>
+    <label>邮箱验证码:</label>
+    <input type="text" name="verificationCode" required>
+    <label>密码:</label>
+    <input type="password" name="password" required>
+    <label>身份证号(8 位数字):</label>
+    <input type="number" name="identificationNumber" required>
+    <label>邀请码:</label>
+    <input type="text" name="inviteCode" required>
+    <button type="submit">注册</button>
+</form>
+<script>
+    document.getElementById('sendCode')
+        .addEventListener('click', async () => {
+            const form = new URLSearchParams();
+            form.set('email', document.querySelector('[name=email]').value);
+            form.set('inviteCode', document.querySelector('[name=inviteCode]').value);
+            const res = await fetch('/sendVerification', {
+                method: 'POST',
+                body: form
+            });
+            alert((await res.json()).msg);
+        });
+    document.getElementById('regForm')
+        .addEventListener('submit', async e => {
+            e.preventDefault();
+            const form = new URLSearchParams(new FormData(e.target));
+            const res = await fetch('/register', {
+                method: 'POST',
+                body: form
+            });
+            const json = await res.json();
+            alert(json.msg);
+            if (res.ok && json.code===0) {
+                window.location.href='login.html';
+            }
+        });
+</script>
+</body>
+</html>