加入了注册和登录时显示IP归属地的功能

pull/1/head
zhong-hongye12355 2 months ago
commit a013fac823

@ -0,0 +1,8 @@
# Default ignored files
/shelf/
/workspace.xml
# Editor-based HTTP Client requests
/httpRequests/
# Datasource local storage ignored files
/dataSources/
/dataSources.local.xml

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ApifoxUploaderProjectSetting">
<option name="apiAccessToken" value="APS-Du6Eoh7CmJJcLtPBY40931ru8MEovCoV" />
</component>
</project>

@ -0,0 +1,14 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="CompilerConfiguration">
<annotationProcessing>
<profile default="true" name="Default" enabled="true" />
<profile name="Maven default annotation processors profile" enabled="true">
<sourceOutputDir name="target/generated-sources/annotations" />
<sourceTestOutputDir name="target/generated-test-sources/test-annotations" />
<outputRelativeToContentRoot value="true" />
<module name="unilife-server" />
</profile>
</annotationProcessing>
</component>
</project>

@ -0,0 +1,17 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="DataSourceManagerImpl" format="xml" multifile-model="true">
<data-source source="LOCAL" name="unilife@localhost" uuid="9c6c9710-15d0-4710-8fca-930cc43549e9">
<driver-ref>mysql.8</driver-ref>
<synchronize>true</synchronize>
<jdbc-driver>com.mysql.cj.jdbc.Driver</jdbc-driver>
<jdbc-url>jdbc:mysql://localhost:3306/unilife</jdbc-url>
<jdbc-additional-properties>
<property name="com.intellij.clouds.kubernetes.db.host.port" />
<property name="com.intellij.clouds.kubernetes.db.enabled" value="false" />
<property name="com.intellij.clouds.kubernetes.db.container.port" />
</jdbc-additional-properties>
<working-dir>$ProjectFileDir$</working-dir>
</data-source>
</component>
</project>

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="Encoding">
<file url="file://$PROJECT_DIR$/unilife-server/src/main/java" charset="UTF-8" />
<file url="file://$PROJECT_DIR$/unilife-server/src/main/resources" charset="UTF-8" />
</component>
</project>

@ -0,0 +1,20 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="RemoteRepositoriesConfiguration">
<remote-repository>
<option name="id" value="central" />
<option name="name" value="Maven Central repository" />
<option name="url" value="https://repo1.maven.org/maven2" />
</remote-repository>
<remote-repository>
<option name="id" value="jboss.community" />
<option name="name" value="JBoss Community repository" />
<option name="url" value="https://repository.jboss.org/nexus/content/repositories/public/" />
</remote-repository>
<remote-repository>
<option name="id" value="central" />
<option name="name" value="Central Repository" />
<option name="url" value="https://maven.aliyun.com/repository/public" />
</remote-repository>
</component>
</project>

@ -0,0 +1,15 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ExternalStorageConfigurationManager" enabled="true" />
<component name="MavenProjectsManager">
<option name="originalFiles">
<list>
<option value="$PROJECT_DIR$/backend/pom.xml" />
<option value="$PROJECT_DIR$/unilife-server/pom.xml" />
</list>
</option>
</component>
<component name="ProjectRootManager" version="2" languageLevel="JDK_17" project-jdk-name="17" project-jdk-type="JavaSDK">
<output url="file://$PROJECT_DIR$/out" />
</component>
</project>

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/.idea/unilife.iml" filepath="$PROJECT_DIR$/.idea/unilife.iml" />
</modules>
</component>
</project>

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="JAVA_MODULE" version="4">
<component name="NewModuleRootManager" inherit-compiler-output="true">
<exclude-output />
<content url="file://$MODULE_DIR$" />
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="" vcs="Git" />
</component>
</project>

@ -0,0 +1,24 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*
node_modules
dist
dist-ssr
*.local
# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
.DS_Store
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?

@ -0,0 +1,3 @@
{
"recommendations": ["Vue.volar"]
}

@ -0,0 +1,5 @@
# Vue 3 + TypeScript + Vite
This template should help get you started developing with Vue 3 and TypeScript in Vite. The template uses Vue 3 `<script setup>` SFCs, check out the [script setup docs](https://v3.vuejs.org/api/sfc-script-setup.html#sfc-script-setup) to learn more.
Learn more about the recommended Project Setup and IDE Support in the [Vue Docs TypeScript Guide](https://vuejs.org/guide/typescript/overview.html#project-setup).

@ -0,0 +1,13 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Vite + Vue + TS</title>
</head>
<body>
<div id="app"></div>
<script type="module" src="/src/main.ts"></script>
</body>
</html>

@ -0,0 +1,26 @@
{
"name": "vue-unilife",
"private": true,
"version": "0.0.0",
"type": "module",
"scripts": {
"dev": "vite",
"build": "vue-tsc -b && vite build",
"preview": "vite preview"
},
"dependencies": {
"@vue/shared": "^3.5.13",
"axios": "^1.8.3",
"element-plus": "^2.9.7",
"vee-validate": "^4.15.0",
"vue": "^3.5.13",
"yup": "^1.6.1"
},
"devDependencies": {
"@vitejs/plugin-vue": "^5.2.1",
"@vue/tsconfig": "^0.7.0",
"typescript": "~5.7.2",
"vite": "^6.2.0",
"vue-tsc": "^2.2.4"
}
}

File diff suppressed because it is too large Load Diff

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="31.88" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 257"><defs><linearGradient id="IconifyId1813088fe1fbc01fb466" x1="-.828%" x2="57.636%" y1="7.652%" y2="78.411%"><stop offset="0%" stop-color="#41D1FF"></stop><stop offset="100%" stop-color="#BD34FE"></stop></linearGradient><linearGradient id="IconifyId1813088fe1fbc01fb467" x1="43.376%" x2="50.316%" y1="2.242%" y2="89.03%"><stop offset="0%" stop-color="#FFEA83"></stop><stop offset="8.333%" stop-color="#FFDD35"></stop><stop offset="100%" stop-color="#FFA800"></stop></linearGradient></defs><path fill="url(#IconifyId1813088fe1fbc01fb466)" d="M255.153 37.938L134.897 252.976c-2.483 4.44-8.862 4.466-11.382.048L.875 37.958c-2.746-4.814 1.371-10.646 6.827-9.67l120.385 21.517a6.537 6.537 0 0 0 2.322-.004l117.867-21.483c5.438-.991 9.574 4.796 6.877 9.62Z"></path><path fill="url(#IconifyId1813088fe1fbc01fb467)" d="M185.432.063L96.44 17.501a3.268 3.268 0 0 0-2.634 3.014l-5.474 92.456a3.268 3.268 0 0 0 3.997 3.378l24.777-5.718c2.318-.535 4.413 1.507 3.936 3.838l-7.361 36.047c-.495 2.426 1.782 4.5 4.151 3.78l15.304-4.649c2.372-.72 4.652 1.36 4.15 3.788l-11.698 56.621c-.732 3.542 3.979 5.473 5.943 2.437l1.313-2.028l72.516-144.72c1.215-2.423-.88-5.186-3.54-4.672l-25.505 4.922c-2.396.462-4.435-1.77-3.759-4.114l16.646-57.705c.677-2.35-1.37-4.583-3.769-4.113Z"></path></svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

@ -0,0 +1,12 @@
<script setup lang="ts">
import LogPage from './components/LogPage.vue';
</script>
<template>
<LogPage/>
</template>
<style scoped>
</style>

@ -0,0 +1,37 @@
import request from '../utils/request';
// 用户注册
export function register(data) {
return request({
url: '/auth/register',
method: 'post',
data
});
}
// 用户密码登录
export function login(data) {
return request({
url: '/auth/login',
method: 'post',
data
});
}
// 获取邮箱验证码
export function getEmailCode(data) {
return request({
url: '/auth/email/code',
method: 'post',
data
});
}
// 邮箱验证码登录
export function loginWithCode(data) {
return request({
url: '/auth/login/code',
method: 'post',
data
});
}

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="37.07" height="36" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 198"><path fill="#41B883" d="M204.8 0H256L128 220.8L0 0h97.92L128 51.2L157.44 0h47.36Z"></path><path fill="#41B883" d="m0 0l128 220.8L256 0h-51.2L128 132.48L50.56 0H0Z"></path><path fill="#35495E" d="M50.56 0L128 133.12L204.8 0h-47.36L128 51.2L97.92 0H50.56Z"></path></svg>

After

Width:  |  Height:  |  Size: 496 B

@ -0,0 +1,520 @@
<script setup lang="ts">
import request from '../utils/request' //
import * as yup from 'yup'
import { useField, useForm } from 'vee-validate'
import { ref ,watch} from 'vue'
//
const transRate = ref('90')
const switchLogin = ref(true);
const switchLoginMethod = ref<boolean>(true);
const switchLoginMethodEvent = () =>{
password.value = vericode.value = verifyPassword.value = "";
switchLoginMethod.value = !switchLoginMethod.value;
if(switchLoginMethod.value)//
{
password.value = verifyPassword.value = 123456;
}
else
{
vericode.value = 1;
}
}
const form_box = ref<HTMLElement | null>(null);
//
const switchLoginEvent = () =>{
password.value = vericode.value = verifyPassword.value = "";
if(switchLogin.value)//
{
if(switchLoginMethod.value)//
{
password.value = verifyPassword.value = 123456;
}
else
{
vericode.value = 1;
}
}
switchLogin.value = !switchLogin.value;
switchLogin.value ? transRate.value = '0' : transRate.value = '90';
if(form_box.value)
{
form_box.value.style.transform = `translateX(${transRate.value}%)`;
}
}
//
//
const RegisterForm = useForm({
validationSchema : yup.object({
email: yup.string().email("请输入邮箱").required("请输入正确的邮箱"),
password: yup.string().min(6,"密码至少6位").required("请输入密码"),
verifyPassword: yup.string().oneOf([yup.ref('password')],"两次密码不一致").required("请确认密码"),
vericode: yup.string().required("请输入验证码")
}),
})
//
const LoginPasswordForm = useForm({
validationSchema : yup.object({
email: yup.string().email("请输入邮箱").required("请输入正确的邮箱"),
password: yup.string().min(6,"密码至少6位").required("请输入密码"),
}),
})
const LoginEmailForm = useForm({
validationSchema : yup.object({
email: yup.string().email("请输入邮箱").required("请输入正确的邮箱"),
vericode: yup.string().required("请输入验证码")
}),
})
const {value: email} = useField('email')
const {value: password} = useField('password')
const {value: verifyPassword} = useField('verifyPassword')
const {value: vericode} = useField('vericode')//
//
const showErrors = ref(false)
const ErrorsMessage = ref('')
const checkErrors = () => {
if(RegisterForm.errors.value.email || RegisterForm.errors.value.password || RegisterForm.errors.value.verifyPassword || RegisterForm.errors.value.vericode) {
showErrors.value = true
ErrorsMessage.value = '请检查输入是否正确'
}
if(LoginPasswordForm.errors.value.email || LoginPasswordForm.errors.value.password) {
showErrors.value = true
ErrorsMessage.value = '请检查输入是否正确'
}
if(LoginEmailForm.errors.value.email || LoginEmailForm.errors.value.vericode) {
showErrors.value = true
ErrorsMessage.value = '请检查输入是否正确'
}
}
const onRegisterSubmit = () => {
RegisterForm.handleSubmit(() => {
testcode().then((res) => {
if(res.code === 200) {
register().then((res) => {
if(res.code === 200) {
console.log('注册成功')
} else {
console.log('注册失败')
}
})
} else {
console.log('注册失败')
}
})
})();
}
const chooseLogin = () => {
if(!switchLoginMethod.value) {
onLoginPasswordSubmit()
} else {
onLoginEmailSubmit()
}
}
const onLoginPasswordSubmit = () => {
LoginPasswordForm.handleSubmit(() => {
login().then((res) => {
if(res.code === 200) {
console.log('登录成功')
} else {
console.log('登录失败')
}
})
})();
}
const onLoginEmailSubmit = () => {
LoginEmailForm.handleSubmit(() => {
testcode().then((res) => {
if(res.code === 200) {
console.log('登录成功')
} else {
console.log('登录失败')
}
})
})();
}
//axios
//
async function emailcode(){
const res = await request.get('/users/code', {
data:{
email: email.value
}
})
console.log("success")
}
//
async function testcode() {
const res = await request.post('/users/login/code', {
data: {
email: email.value,
code: vericode.value
}
})
return res.data;
}
async function register(){
const res = await request.post('/users/register', {
data:{
email: email.value,
password: password.value,
username:null,
nickname:null,
studentId:null,
department:null,
major:null,
grade:null,
}
})
return res.data;
}
async function login(){
const res = await request.post('/users/login', {
data:{
email: email.value,
password: password.value
}
})
return res.data;
}
</script>
<template>
<title>登录</title>
<el-alert
v-if="showErrors"
title={{ ErrorsMessage }}
type="error"
:closable="true"
@close="showErrors = false"
center/>
<div class = "container">
<div ref = "form_box" class = "form-box">
<!-- 注册表单 -->
<div class = "register-box" :class = "{hidden: !switchLogin}">
<h1>register</h1>
<div class="email-vericode">
<input type="text" placeholder="邮箱" v-model="email">
<button class="vericode-btn" @click = "emailcode">获取验证码</button>
</div>
<input type="password" placeholder="密码" v-model="password">
<input type ="password" placeholder = "确认密码" v-model = "verifyPassword">
<input type="text" placeholder="验证码" v-model="vericode">
<button @click = "onRegisterSubmit;checkErrors">注册</button>
</div>
<!-- 登录表单 -->
<div class = "login-box" :class ="{hidden: switchLogin}">
<!--密码登录-->
<div class = password-method :class="{hidden: switchLoginMethod}">
<h1>login</h1>
<input type="text" placeholder="邮箱" v-model="email">
<input type="password" placeholder="密码" v-model="password">
<button class = "switch-btn" @click="switchLoginMethodEvent"></button>
</div>
<!--邮箱登录-->
<div class = "email-method" :class ="{hidden: !switchLoginMethod}">
<h1>login</h1>
<input class ="email-text" type="text" placeholder="邮箱" v-model="email">
<button class="email-btn" @click="emailcode"></button>
<input type="text" placeholder="验证码" v-model="vericode">
<button class = "switch-btn" @click="switchLoginMethodEvent"></button>
</div>
<button @click="chooseLogin;checkErrors"></button>
</div>
</div>
<div>
</div>
<div class="con-box right">
<h2>欢迎来到<span>UniLife学生论坛</span></h2>
<p>这是一个专属于大学生的论坛
<br>你可以在这里发表自己的观点
<br>也可以在这里找到志同道合的朋友</p>
<img src = "../../public/images/LogPage1.jpg" alt=""></img>
<p>已有账号</p>
<button id= "login" @click="switchLoginEvent"></button>
</div>
<div class="con-box left">
<h2>欢迎回到<span>UniLife学生论坛</span></h2>
<p>快来看看论坛新来的讯息和同学们的消息吧</p>
<img src = "../../public/images/LogPage2.jpg" alt=""></img>
<p>没有账户</p>
<button id= "register" @click="switchLoginEvent"></button>
</div>
</div>
</template>
<style scoped>
*{
margin: 0;
padding: 0;
}
body{
height: 100vh;;
/*弹性布局,水平垂直居中*/
display:flex;
justify-content: center;
align-items: center;
/*渐变背景*/
background: linear-gradient(200deg, #f3e7e9, #2a4b7c);
}
.container{
background-color: #fff;
width:943.8px; /* 原值786.5px再增加20% */
height:653.4px; /* 原值544.5px再增加20% */
border-radius: 5px;
/* 阴影 */
box-shadow:5px 5px 5px rgba(0,0,0,0.1);
/* 相对定位 */
position: relative;
}
.error-msg p{
align-items: center;
position:absolute;
flex-direction: column;
color:#fff;
font-size:20px;
top:12%;
left:37%;
}
.form-box{
/* 独立 */
position: absolute;
top:-7%;
left:2.5%;
background-color: #d3b7d8;
width:470px; /* 原值387.2px再增加20% */
height:750px; /* 原值665.5px再增加20% */
border-radius: 5px;
box-shadow: 2px 0 10px rgba(0,0,0,0.1);
display: flex;
justify-content: center;
align-items:center;
z-index: 2;
/* 透明度 */
/* 动画过度,先加速后减速 */
transition:transform 0.5s ease-in-out;
}
.register-box{
display:flex;
flex-direction: column;
align-items: center;
width:100%;
height:60%
}
.login-box{
display:flex;
flex-direction: column;
align-items: center;
width:100%;
height:60%
}
.email-btn{
width:20% !important;
margin-top:20px !important;
position:relative !important;
top:8% !important;
left:2% !important;
}
.email-text{
width: 50% !important;
}
.hidden{
display: none;
transition:0.5s;
}
h1{
text-align: center;
margin-bottom:25px;
/* 大写 */
text-transform:uppercase;
color: #fff;
letter-spacing:5px;
}
input{
background-color: transparent;
width:70%;
height:100px;
color:#fff;
border:none;
/* 下边框样式 */
border-bottom:1px solid rgba(255,255,255,0.4);
padding: 12px,0;
text-indent:10px;
margin: 10px 0;
font-size:19px;
letter-spacing:2px;
}
input::placeholder{
font-size: 19px;
color:#fff;
}
input:focus::placeholder{
opacity:0;
}
input:focus{
color:#a262ad;
outline:none;
border-bottom: 1px solid #a262ad80;
transition:0.5s;
}
.form-box button{
width: 70%;
margin-top:35px;
background-color:#f6f6f6;
outline:none;
border-radius: 8px;
padding:13px;
color:#a262ad;
letter-spacing:2px;
border: none;
cursor:pointer;
}
.vericode-btn{
width: 30% !important;
padding:6px;
border-radius: 5px;
letter-spacing: 4px;
outline:none;
cursor:pointer;
}
.email-vericode {
display: flex;
align-items: center;
width: 70%;
margin: 10px 0;
}
.email-vericode input {
width: 70%;
margin-right: 10px;
}
.email-vericode input::placeholder {
font-size: 19px;
color: #fff;
}
.form-box button:hover{
background-color:#a262ad;
color:#f6f6f6;
transition:background-color 0.5s ease;
}
.con-box{
width:50%;
display:flex;
flex-direction: column;
justify-content: center;
align-items: center;
position:absolute;
top:50%;
transform:translateY(-50%);
}
.con-box.left{
left:-2%;
}
.con-box.right{
right:-2%;
}
.con-box h2{
color:#8e9aaf;
font-size:25px;
font-weight:bold;
letter-spacing: 3px;
text-align:center;
margin-bottom:4px;
}
.con-box p{
font-size:15px;
letter-spacing: 2px;
color:#8e9aaf;
text-align:center;
}
.con-box span{
color: #d3b7d8;
}
.con-box img{
width:217.8px; /* 原值181.5px再增加20% */
height:217.8px; /* 原值181.5px再增加20% */
opacity:0.9;
margin:40px 0;
}
.con-box button{
margin-top:3%;
background-color:#fff;
color:#a262ad;
padding:6px;
border-radius: 5px;
letter-spacing: 1px;
outline:none;
cursor:pointer;
}
.con-box button:hover{
background-color:#d3b7d8;
color:#fff;
outline:none;
}
</style>
<style>
body{
height: 100vh;
/*弹性布局,水平垂直居中*/
display:flex;
justify-content: center;
align-items: center;
/*渐变背景*/
background: linear-gradient(200deg, #f3e7e9, #e3eeff);
}
</style>

@ -0,0 +1,9 @@
import { createApp } from 'vue'
import './style.css'
import App from './App.vue'
import ElementPlus from 'element-plus'
import 'element-plus/dist/index.css'
const app = createApp(App)
app.mount('#app')
app.use(ElementPlus)

@ -0,0 +1,5 @@
declare module '*.vue' {
import { DefineComponent } from 'vue';
const component: DefineComponent<{}, {}, any>;
export default component;
}

@ -0,0 +1,79 @@
:root {
font-family: system-ui, Avenir, Helvetica, Arial, sans-serif;
line-height: 1.5;
font-weight: 400;
color-scheme: light dark;
color: rgba(255, 255, 255, 0.87);
background-color: #242424;
font-synthesis: none;
text-rendering: optimizeLegibility;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
a {
font-weight: 500;
color: #646cff;
text-decoration: inherit;
}
a:hover {
color: #535bf2;
}
body {
margin: 0;
display: flex;
place-items: center;
min-width: 320px;
min-height: 100vh;
}
h1 {
font-size: 3.2em;
line-height: 1.1;
}
button {
border-radius: 8px;
border: 1px solid transparent;
padding: 0.6em 1.2em;
font-size: 1em;
font-weight: 500;
font-family: inherit;
background-color: #1a1a1a;
cursor: pointer;
transition: border-color 0.25s;
}
button:hover {
border-color: #646cff;
}
button:focus,
button:focus-visible {
outline: 4px auto -webkit-focus-ring-color;
}
.card {
padding: 2em;
}
#app {
max-width: 1280px;
margin: 0 auto;
padding: 2rem;
text-align: center;
}
@media (prefers-color-scheme: light) {
:root {
color: #213547;
background-color: #ffffff;
}
a:hover {
color: #747bff;
}
button {
background-color: #f9f9f9;
}
}

@ -0,0 +1,30 @@
import axios from 'axios';
const service = axios.create({
baseURL: 'http://localhost:8080',
timeout: 5000
});
service.interceptors.request.use(
config => {
console.log(1);
return config;
},
error => {
// 对请求错误做些什么
return Promise.reject(error);
}
);
service.interceptors.response.use(
response => {
console.log(2);
return response.data;
},
error => {
// 对响应错误做些什么
return Promise.reject(error);
}
);
export default service;

@ -0,0 +1 @@
/// <reference types="vite/client" />

@ -0,0 +1,14 @@
{
"extends": "@vue/tsconfig/tsconfig.dom.json",
"compilerOptions": {
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
/* Linting */
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noFallthroughCasesInSwitch": true,
"noUncheckedSideEffectImports": true
},
"include": ["src/**/*.ts", "src/**/*.tsx", "src/**/*.vue"]
}

@ -0,0 +1,7 @@
{
"files": [],
"references": [
{ "path": "./tsconfig.app.json" },
{ "path": "./tsconfig.node.json" }
]
}

@ -0,0 +1,24 @@
{
"compilerOptions": {
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo",
"target": "ES2022",
"lib": ["ES2023"],
"module": "ESNext",
"skipLibCheck": true,
/* Bundler mode */
"moduleResolution": "bundler",
"allowImportingTsExtensions": true,
"isolatedModules": true,
"moduleDetection": "force",
"noEmit": true,
/* Linting */
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noFallthroughCasesInSwitch": true,
"noUncheckedSideEffectImports": true
},
"include": ["vite.config.ts"]
}

@ -0,0 +1,8 @@
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
// https://vite.dev/config/
export default defineConfig({
plugins: [vue()],
})

@ -0,0 +1,48 @@
# 配置前端环境
---
### 检查node版本
```
npm -v
10.9.2
```
### 安装Vite
```
npm create vite@latest my-vue-app
```
此时vite会提示你选择
1⃣Select a framework 👉 选择 Vue
2⃣ Select a variant 👉 选择 TypeScript
然后安装pnpm
```
npm install -g pnpm
```
检查是否安装成功
```
pnpm -v
```
进入项目(项目我已经建好了,上传到项目里了)
```
cd Front/unilife
```
### 安装Axios
``` cmd
pnpm add axios
```
用来连接前后端
### 安装veeValidate
```cmd
pnpm add vee-validate
pnpm install yup
```
用于表单验证
### 安装ElementPlus
```cmd
pnpm add element-plus
```

@ -0,0 +1,39 @@
# UniLife —— 有你生活,优你生活
## 项目简介
UniLife 是一款面向学生的在线论坛,致力于提升校园生活体验。其口号为 “有你生活优你生活”核心功能包括论坛交流、学习资源共享、课程管理、AI 学习辅助等。项目初期以网站形式开发,后续可能扩展为移动端应用。
## 功能特性
### 1.用户系统
+ 账号注册/登录(支持智慧珞珈认证)
+ 个人资料管理
### 2.论坛功能
+ 主题发布/评论/点赞
+ 话题分类(学习、校园生活、兴趣交流等)
+ 接入校园新闻(自动抓取/人工编辑)
### 3.学习资源共享
+ 共享文档(学生自主填写各课程作业信息)
+ 资料上传/下载权限管理
### 4.课程表 & 个人行程安排
+ 课程表导入API 接入学校系统或手动录入)
+ 个人日程管理(整合课程表,支持自定义编辑)
### 5.AI 辅助学习
+ 学习计划制定(结合课程安排和个人目标)
+ 任务提醒(结合课程表,提供智能通知)
+ 部分课程智能辅助
### 6.查询功能
+ 课程信息查询
+ 资料搜索
+ 论坛帖子搜索
### 7.积分系统
+ 贡献积分(发帖、上传资源等)
+ 兑换功能(待定,可用于解锁高级功能等)
## 小组成员:
王雨菲 蔡子钦 钟宏烨 贾瀚翔 胡天琦 刘宇航

@ -0,0 +1,258 @@
## 一、API规范
### 1.1 基础信息
- **基础URL**: `http://localhost:8080`(本地测试)
- **接口格式**: RESTful API
- **数据格式**: JSON
- **字符编码**: UTF-8
- **认证方式**: JWT (JSON Web Token)
- **GET**: 获取资源
- **POST**: 创建资源
- **PUT**: 更新资源(全量更新)
- **PATCH**: 部分更新资源
- **DELETE**: 删除资源
### 1.2 响应规范
#### 响应状态码
- **200**: 成功
- **400**: 请求参数错误
- **401**: 未授权
- **403**: 禁止访问
- **404**: 资源不存在
- **500**: 服务器内部错误
#### 后端相应格式
成功响应:
```json
{
"code": 200, // 200表示成功
"message": "success",
"data": { // 实际返回数据
// ...
}
}
```
错误相应:
```json
{
"code": 400,
"message": "参数错误", // 错误信息
"data": null
}
```
## 二、 数据库设计
### 2.1用户表设计 (users)
| 字段名 | 类型 | 约束 | 说明 |
| ----------- | ------------ | ----------------------------------------------------- | ------------------------------------ |
| id | BIGINT | PRIMARY KEY, AUTO_INCREMENT | 用户ID |
| username | VARCHAR(50) | NOT NULL, UNIQUE | 用户名 |
| email | VARCHAR(100) | NOT NULL, UNIQUE | 邮箱地址(学校邮箱) |
| password | VARCHAR(255) | NOT NULL | 密码(加密存储) |
| nickname | VARCHAR(50) | NOT NULL | 昵称 |
| avatar | VARCHAR(255) | | 头像URL |
| bio | TEXT | | 个人简介 |
| gender | TINYINT | | 性别0-未知, 1-男, 2-女) |
| student_id | VARCHAR(20) | UNIQUE | 学号 |
| department | VARCHAR(100) | | 院系 |
| major | VARCHAR(100) | | 专业 |
| grade | VARCHAR(20) | | 年级 |
| points | INT | DEFAULT 0 | 积分 |
| role | TINYINT | DEFAULT 0 | 角色0-普通用户, 1-版主, 2-管理员) |
| status | TINYINT | DEFAULT 1 | 状态0-禁用, 1-启用) |
| is_verified | TINYINT | DEFAULT 0 | 是否验证0-未验证, 1-已验证) |
| login_ip | VARCHAR(50) | | 最近登录IP |
| login_time | DATETIME | | 最近登录时间 |
| created_at | DATETIME | DEFAULT CURRENT_TIMESTAMP | 创建时间 |
| updated_at | DATETIME | DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP | 更新时间 |
#### 建表语句
```sql
CREATE TABLE `users` (
`id` BIGINT PRIMARY KEY AUTO_INCREMENT COMMENT '用户ID',
`username` VARCHAR(50) NOT NULL UNIQUE COMMENT '用户名',
`email` VARCHAR(100) NOT NULL UNIQUE COMMENT '邮箱地址(学校邮箱)',
`password` VARCHAR(255) NOT NULL COMMENT '密码(加密存储)',
`nickname` VARCHAR(50) NOT NULL COMMENT '昵称',
`avatar` VARCHAR(255) DEFAULT NULL COMMENT '头像URL',
`bio` TEXT DEFAULT NULL COMMENT '个人简介',
`gender` TINYINT DEFAULT 0 COMMENT '性别0-未知, 1-男, 2-女)',
`student_id` VARCHAR(20) UNIQUE DEFAULT NULL COMMENT '学号',
`department` VARCHAR(100) DEFAULT NULL COMMENT '院系',
`major` VARCHAR(100) DEFAULT NULL COMMENT '专业',
`grade` VARCHAR(20) DEFAULT NULL COMMENT '年级',
`points` INT DEFAULT 0 COMMENT '积分',
`role` TINYINT DEFAULT 0 COMMENT '角色0-普通用户, 1-版主, 2-管理员)',
`status` TINYINT DEFAULT 1 COMMENT '状态0-禁用, 1-启用)',
`is_verified` TINYINT DEFAULT 0 COMMENT '是否验证0-未验证, 1-已验证)',
`login_ip` VARCHAR(50) DEFAULT NULL COMMENT '最近登录IP',
`login_time` DATETIME DEFAULT NULL COMMENT '最近登录时间',
`created_at` DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`updated_at` DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
INDEX `idx_email` (`email`),
INDEX `idx_username` (`username`),
INDEX `idx_student_id` (`student_id`),
INDEX `idx_role` (`role`),
INDEX `idx_status` (`status`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='用户表';
```
## 三、Api功能实现
### 3.1用户认证模块
#### 3.1.1 用户注册
- **URL**: `/users/register`
- **方法**: POST
- **描述**: 创建新用户账号
请求参数:
```json
{
"username": "student123",
"email": "student@school.edu",
"password": "Secure@Password123",
"nickname": "学生昵称",
"studentId": "20220101001",
"department": "计算机学院",
"major": "软件工程",
"grade": "2023级"
}
```
响应结果:
```json
{
"code": 200,
"message": "注册成功",
"data": {
"userId": 12345,
"username": "student123",
"nickname": "学生昵称"
}
}
```
#### 3.1.2 用户密码登录
- **URL**: `/users/login`
- **方法**: POST
- **描述**: 用户登录
请求参数:
```json
{
"username": "student123", // 用户名或邮箱
"password": "Secure@Password123"
}
```
响应结果:
```json
{
"code": 200,
"message": "登录成功",
"data": {
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"userInfo": {
"userId": 12345,
"username": "student123",
"nickname": "学生昵称",
"avatar": "https://example.com/avatar.jpg",
"role": 0,
"isVerified": true,
"status": 1
}
}
}
```
#### 3.1.3 获取邮箱验证码
- **URL**: `/users/code`
- **方法**: POST
- **描述**: 向指定邮箱发送登录验证码
请求参数
```json
{
"email": "student@school.edu"
}
```
响应结果:
```json
{
"code": 200,
"message": "验证码已发送",
"data": null
}
```
#### 3.1.4 邮箱验证码登录
- **URL**: `/users/login/code`
- **方法**: POST
- **描述**: 使用邮箱和验证码进行登录
请求参数
```json
{
"email": "student@school.edu",
"code": "123456"
}
```
成功响应:
```json
{
"code": 200,
"message": "登录成功",
"data": {
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"userInfo": {
"userId": 12345,
"username": "student123",
"nickname": "学生昵称",
"avatar": "https://example.com/avatar.jpg",
"role": 0
}
}
}
```

@ -0,0 +1,33 @@
HELP.md
target/
!.mvn/wrapper/maven-wrapper.jar
!**/src/main/**/target/
!**/src/test/**/target/
### STS ###
.apt_generated
.classpath
.factorypath
.project
.settings
.springBeans
.sts4-cache
### IntelliJ IDEA ###
.idea
*.iws
*.iml
*.ipr
### NetBeans ###
/nbproject/private/
/nbbuild/
/dist/
/nbdist/
/.nb-gradle/
build/
!**/src/main/**/build/
!**/src/test/**/build/
### VS Code ###
.vscode/

@ -0,0 +1,122 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.example</groupId>
<artifactId>unilife-server</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>backend</name>
<description>backend</description>
<properties>
<java.version>1.8</java.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<spring-boot.version>2.7.6</spring-boot.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.3.0</version>
</dependency>
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.36</version>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.github.xiaoymin</groupId>
<artifactId>knife4j-openapi2-spring-boot-starter</artifactId>
<version>4.4.0</version>
</dependency>
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.8.16</version>
</dependency>
<!-- redis-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>5.1.3</version>
</dependency>
<!-- 邮箱服务-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-mail</artifactId>
</dependency>
<!-- IP地址服务-->
<dependency>
<groupId>org.lionsoul</groupId>
<artifactId>ip2region</artifactId>
<version>2.7.0</version>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>${spring-boot.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.1</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
<encoding>UTF-8</encoding>
</configuration>
</plugin>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>${spring-boot.version}</version>
<configuration>
<mainClass>com.example.unilife.BackendApplication</mainClass>
<skip>true</skip>
</configuration>
<executions>
<execution>
<id>repackage</id>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>

@ -0,0 +1,13 @@
package com.unilife;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class UniLifeApplication {
public static void main(String[] args) {
SpringApplication.run(UniLifeApplication.class, args);
}
}

@ -0,0 +1,7 @@
package com.unilife.common.constant;
public class RedisConstant {
public static final String LOGIN_EMAIL_KEY="login:email:";
public static final String LOGIN_EMAIL_LIMIT_KEY = "login:email:limit:";
}

@ -0,0 +1,60 @@
package com.unilife.common.result;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Result<T>{
/***
*
*/
private Integer code;
/***
*
*/
private String message;
/***
*
*/
private T data;
/***
*
* @param data
* @return
* @param <T>
*/
public static <T>Result<T>success(T data){
return new Result<T>(200, "success", data);
}
/**
*
* @return
*/
public static Result<Void> success() {
return new Result<>(200, "success", null);
}
public static <T> Result<T> success(T data, String message) {
return new Result<>(200, message, data);
}
/**
*
* @param code
* @param message
* @return
*/
public static <T> Result<T> error(Integer code, String message) {
return new Result<>(code, message, null);
}
public static <T> Result<T> error(T data,String message){
return new Result<>(200,message,null);
}
}

@ -0,0 +1,54 @@
package com.unilife.controller;
import com.unilife.common.result.Result;
import com.unilife.model.dto.EmailDTO;
import com.unilife.model.dto.LogDTO;
import com.unilife.model.dto.LoginDTO;
import com.unilife.model.dto.LoginEmailDTO;
import com.unilife.service.UserService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
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 javax.servlet.http.HttpServletRequest;
@Api(tags = "用户管理")
@RestController
@RequestMapping("/users")
@Slf4j
public class UserController {
@Autowired
private UserService userService;
@ApiOperation(value = "用户注册")
@PostMapping("register")
public Result register(@RequestBody LoginDTO loginDTO, HttpServletRequest request) {
return userService.register(loginDTO,request);
}
@ApiOperation(value = "用户登录")
@PostMapping("login")
public Result login(@RequestBody LogDTO logDTO,HttpServletRequest request) { return userService.login(logDTO,request); }
@ApiOperation(value = "获取邮箱验证码")
@PostMapping("code")
public Result getCode(@RequestBody EmailDTO emailDto,HttpServletRequest request) {
String email=emailDto.getEmail();
log.debug("收到的原始邮箱: {}", email);
return userService.sendVerificationCode(email,request);
}
@ApiOperation(value = "邮箱验证码登录")
@PostMapping("login/code")
public Result loginWithEmailCode(@RequestBody LoginEmailDTO loginEmailDTO,HttpServletRequest request) {
return userService.loginWithEmail(loginEmailDTO,request);
}
}

@ -0,0 +1,14 @@
package com.unilife.mapper;
import com.unilife.model.entity.User;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
@Mapper
public interface UserMapper {
void insert(User user);
User FindByEmail(@Param("email") String email, @Param("password") String password);
User getUserByEmail(String email);
void UpdateIPLocation(@Param("email") String email,@Param("loginIp") String loginIp);
User FindByOnlyEmail(@Param("email") String email);
}

@ -0,0 +1,12 @@
package com.unilife.model.dto;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@NoArgsConstructor
@AllArgsConstructor
public class EmailDTO {
String email;
}

@ -0,0 +1,14 @@
package com.unilife.model.dto;
//这个才是登录的DTO
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@NoArgsConstructor
@AllArgsConstructor
public class LogDTO {
private String email;
private String password;
}

@ -0,0 +1,19 @@
package com.unilife.model.dto;
//这个是注册的DTO
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@AllArgsConstructor
@NoArgsConstructor
public class LoginDTO {
private String username;
private String email;
private String password;
private String nickname;
private String studentId;
private String department;
private String major;
private String grade;
}

@ -0,0 +1,13 @@
package com.unilife.model.dto;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@AllArgsConstructor
@NoArgsConstructor
public class LoginEmailDTO {
private String email;
private String code;
}

@ -0,0 +1,56 @@
package com.unilife.model.entity;
import lombok.*;
import java.io.Serializable;
import java.time.LocalDateTime;
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class User implements Serializable {
private static final long serialVersionUID = 1L;
private Long id;
private String username;
private String email;
private String password;
private String nickname;
private String avatar;
private String bio;
private Byte gender;
private String studentId;
private String department;
private String major;
private String grade;
private Integer points = 0;
private Byte role = 0;
private Byte status = 1;
private Byte isVerified = 0;
private String loginIp;
private LocalDateTime loginTime;
private LocalDateTime createdAt;
private LocalDateTime updatedAt;
}

@ -0,0 +1,30 @@
package com.unilife.model.vo;
import com.unilife.model.entity.User;
import lombok.Data;
@Data
public class LogVO {
private Integer id;
private String username;
private String nickname;
private String avatar;
private Byte role;
private Byte isVerified;
private Byte status;
private String loginIp;
public LogVO(Integer id,String username,String nickname,String avatar,Byte role,Byte isVerified,Byte status,String loginIp)
{
this.id = id;
this.username = username;
this.nickname = nickname;
this.avatar = avatar;
this.role = role;
this.isVerified = isVerified;
this.status = status;
this.loginIp = loginIp;
}
}

@ -0,0 +1,19 @@
package com.unilife.model.vo;
import lombok.Data;
@Data
public class LoginVO {
private Integer id;
private String username;
private String nickname;
private String loginIp;
public LoginVO(Integer id, String username, String nickname,String loginIp)
{
this.id = id;
this.username = username;
this.nickname = nickname;
this.loginIp = loginIp;
}
}

@ -0,0 +1,10 @@
package com.unilife.service;
import com.unilife.model.dto.LoginDTO;
import javax.servlet.http.HttpServletRequest;
public interface IPLocationService {
public String getIPLocation(String ip);
public String getClientIP(HttpServletRequest request);
}

@ -0,0 +1,22 @@
package com.unilife.service;
import com.unilife.common.result.Result;
import com.unilife.model.dto.LogDTO;
import com.unilife.model.dto.LoginDTO;
import com.unilife.model.dto.LoginEmailDTO;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
public interface UserService {
Result register(LoginDTO loginDTO, HttpServletRequest request);
Result login(LogDTO logDTO, HttpServletRequest request);
Result sendVerificationCode(String email,HttpServletRequest request);
Result loginWithEmail(LoginEmailDTO loginEmailDTO, HttpServletRequest request);
}

@ -0,0 +1,77 @@
package com.unilife.service.impl;
import com.unilife.service.IPLocationService;
import org.lionsoul.ip2region.xdb.Searcher;
import org.springframework.stereotype.Service;
import org.springframework.util.ReflectionUtils;
import org.springframework.util.ResourceUtils;
import javax.servlet.http.HttpServletRequest;
import java.io.File;
import java.io.IOException;
import java.io.FileNotFoundException;
@Service
public class IPLocationServicelmpl implements IPLocationService {
private Searcher searcher;
@Override
public String getIPLocation(String ip) {
if("127.0.0.1".equals(ip) || ip.startsWith("192.168")){
return "局域网 IP";
}
if(searcher == null){
try{
File file = ResourceUtils.getFile("classpath:ipdb/ip2region.xdb");
String dbPath = file.getPath();
//System.out.println(dbPath);
searcher = Searcher.newWithFileOnly(dbPath);
}
catch(FileNotFoundException e){
e.printStackTrace();
return "IP地址库文件未找到";
}
catch (IOException e){
e.printStackTrace();
return "IP地址库初始化失败";
}
}
String region = null;
String errormessage = null;
try{
region = searcher.search(ip);
}
catch (Exception e){
errormessage = e.getMessage();
if(errormessage != null && errormessage.length() > 256){
errormessage = errormessage.substring(0, 256);
}
e.printStackTrace();
return "IP归属地查找失败" + errormessage;
}
return (region == null) ? "未知归属地" : region;
}
@Override
public String getClientIP(HttpServletRequest request) {
if("127.0.0.1".equals(request.getRemoteAddr())){
return "14.21.80.0";
}//实际开发的时候删掉!
String ip = request.getHeader("X-forwarded-for");
if (ip == null || ip.isEmpty() || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("Proxy-Client-IP");
}
if (ip == null || ip.isEmpty() || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("WL-Proxy-Client-IP");
}
if (ip == null || ip.isEmpty() || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("HTTP_CLIENT_IP");
}
if (ip == null || ip.isEmpty() || "unknown".equalsIgnoreCase(ip)) {
ip = request.getRemoteAddr();
}
return (ip != null) ? ip.split(",")[0] : null;
}
}

@ -0,0 +1,244 @@
package com.unilife.service.impl;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.util.RandomUtil;
import cn.hutool.core.util.StrUtil;
import com.unilife.common.constant.RedisConstant;
import com.unilife.common.result.Result;
import com.unilife.mapper.UserMapper;
import com.unilife.model.dto.LogDTO;
import com.unilife.model.dto.LoginDTO;
import com.unilife.model.dto.LoginEmailDTO;
import com.unilife.model.entity.User;
import com.unilife.model.vo.LogVO;
import com.unilife.model.vo.LoginVO;
import com.unilife.service.IPLocationService;
import com.unilife.service.UserService;
import com.unilife.utils.RegexUtils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.mail.javamail.JavaMailSender;
import org.springframework.mail.javamail.MimeMessageHelper;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Service;
import javax.mail.MessagingException;
import javax.mail.internet.MimeMessage;
import javax.servlet.http.HttpServletRequest;
import java.time.Duration;
import java.time.LocalDateTime;
import java.util.HashMap;
import java.util.Map;
import static com.unilife.common.constant.RedisConstant.LOGIN_EMAIL_KEY;
@Slf4j
@Component
@Service
public class UserServiceImpl implements UserService {
@Autowired
private IPLocationService ipLocationService;
@Autowired
private UserMapper userMapper;
@Autowired
private JavaMailSender mailSender;
@Autowired
private StringRedisTemplate stringRedisTemplate;
@Value("${spring.mail.username}")
private String from;
final int CODE_EXPIRE_MINUTES = 10;
final int LIMIT_SECONDS=60;
@Override
public Result register(LoginDTO loginDTO, HttpServletRequest request) {
if(loginDTO.getEmail().isEmpty() || loginDTO.getPassword().isEmpty()) {
return Result.error(400,"邮箱或密码不能为空");
}
if(loginDTO.getPassword().length() < 6) {
return Result.error(400,"密码长度过短!");
}
User getuser = userMapper.FindByOnlyEmail(loginDTO.getEmail());
if(getuser != null) {
return Result.error(400,"用户已存在!");
}
User user = new User();
BeanUtil.copyProperties(loginDTO,user);
String IPAddress = ipLocationService.getClientIP(request);
String Location = ipLocationService.getIPLocation(IPAddress);
user.setLoginIp(Location);
userMapper.insert(user);
LoginVO loginVO = new LoginVO(Math.toIntExact(user.getId()),user.getUsername()
,user.getNickname(),user.getLoginIp());
return Result.success(loginVO);
}
@Override
public Result login(LogDTO logDTO,HttpServletRequest request) {
User user = new User();
BeanUtil.copyProperties(logDTO,user);//将登录的前端传来的消息拷贝给这个user
User getuser = userMapper.FindByEmail(user.getEmail(),user.getPassword());
if(getuser == null)
{
return Result.error(logDTO,"用户不存在,登录失败!");
}
if(!user.getPassword().equals(getuser.getPassword()))
{
return Result.error(logDTO,"密码错误,登录失败!");
}
String LastLogIpLocation = getuser.getLoginIp();
String IPAddress = ipLocationService.getClientIP(request);
String Location = ipLocationService.getIPLocation(IPAddress);
getuser.setLoginIp(Location);
userMapper.UpdateIPLocation(getuser.getEmail(), getuser.getLoginIp());
LogVO logVO = new LogVO(Math.toIntExact(getuser.getId()), getuser.getUsername(), getuser.getNickname(),
getuser.getAvatar(), getuser.getRole(), getuser.getIsVerified(), getuser.getStatus(),getuser.getLoginIp());
return Result.success(logVO,"上次登录IP归属地为" + LastLogIpLocation);
}
@Override
public Result sendVerificationCode(String email,HttpServletRequest request) {
//1.校验邮箱是否合法
boolean emailInvalid = RegexUtils.isEmailInvalid(email);
if(emailInvalid){
return Result.error(400,"邮箱格式不正确");
}
//2.防止频繁发送验证码
String countKey = RedisConstant.LOGIN_EMAIL_LIMIT_KEY + email;
Boolean setSuccess = stringRedisTemplate.opsForValue().setIfAbsent(
countKey,
"1",
Duration.ofSeconds(LIMIT_SECONDS)
);
if (Boolean.FALSE.equals(setSuccess)) {
return Result.error(null, "请求过于频繁,请稍后再试");
}
//3.生成随机验证码
String code = RandomUtil.randomNumbers(6);
log.debug("成功生成验证码,邮箱{},验证码{}", email, code);
//4.发送验证码到邮箱
try {
//构建邮件
MimeMessage message=mailSender.createMimeMessage();
MimeMessageHelper helper = new MimeMessageHelper(message, true);
helper.setFrom(from);
helper.setTo(email);
helper.setSubject("UniLife - 登录验证码");
String IPAddress = ipLocationService.getClientIP(request);
String Location = ipLocationService.getIPLocation(IPAddress);
String content = "<div style=\"font-family: Arial, sans-serif; max-width: 600px; margin: 0 auto; padding: 20px; border: 1px solid #eee; border-radius: 5px;\">" +
"<h2 style=\"color: #333;\">您好!</h2>" +
"<p>感谢您使用UniLife平台,本次登录地为" + Location + "您的验证码是:</p>" +
"<div style=\"background-color: #f5f5f5; padding: 10px; text-align: center; font-size: 24px; font-weight: bold; letter-spacing: 5px; margin: 20px 0;\">" +
code +
"</div>" +
"<p>此验证码将在10分钟内有效。</p>" +
"<p>如果您没有请求此验证码,请忽略此邮件。</p>" +
"<p style=\"margin-top: 30px; font-size: 12px; color: #888;\">" +
"这是一封自动生成的邮件,请勿直接回复。" +
"</p></div>";
helper.setText(content, true);
//4.发送邮件
mailSender.send(message);
}catch (MessagingException e){
log.error("邮件发送失败");
return Result.error(400,"邮件发送失败");
}
//5.存储随机产生的验证码,设置有效期为十分钟
stringRedisTemplate.opsForValue().set(LOGIN_EMAIL_KEY + email, code, Duration.ofMinutes(CODE_EXPIRE_MINUTES));
return Result.success(200,"验证码已发送");
}
@Override
public Result loginWithEmail(LoginEmailDTO loginEmailDTO,HttpServletRequest request) {
String email=loginEmailDTO.getEmail();
if(RegexUtils.isEmailInvalid(email)){
return Result.error(null,"请输入正确的邮箱");
}
String cacheCode = stringRedisTemplate.opsForValue().get(RedisConstant.LOGIN_EMAIL_KEY + email);
if (cacheCode == null) {
return Result.error(null, "验证码已过期或未发送,请重新获取");
}
// 3. 校验验证码是否正确
String code = loginEmailDTO.getCode();
if (!cacheCode.equals(code)) {
return Result.error(null, "验证码错误");
}
// 4. 验证通过,删除验证码
stringRedisTemplate.delete(RedisConstant.LOGIN_EMAIL_KEY + email);
// 5. 查询用户是否存在
User user=userMapper.getUserByEmail(email);
if(user == null){
user = createUserWithEmail(email,request);
}
//6.生成登录凭证
//TODO
// 8. 返回用户信息和登录凭证
Map<String, Object> userInfo = new HashMap<>();
//HashMap userInfo.put("token", token);
userInfo.put("user", user);
return Result.success(userInfo);
}
/**
* 使
*/
private User createUserWithEmail(String email,HttpServletRequest request) {
User user = new User();
user.setEmail(email);
user.setNickname("用户" + RandomUtil.randomString(6)); // 生成随机昵称
String username = email.split("@")[0]+"_"+ RandomUtil.randomString(4); // 使用@前面的部分作为用户名
user.setUsername(username);
String password = RandomUtil.randomString(6);
String IPAddress = ipLocationService.getClientIP(request);
String Location = ipLocationService.getIPLocation(IPAddress);
user.setPassword(password);
user.setRole((byte)0); // 普通用户角色
user.setStatus((byte)1); // 正常状态
user.setIsVerified((byte)0); // 未验证
user.setPoints(0); // 初始积分
user.setGender((byte)0);
user.setLoginIp(Location);//注册地IP
// 保存用户
try {
userMapper.insert(user);
}catch (Exception e){
log.error("用户创建失败");
}
return user;
}
}

@ -0,0 +1,21 @@
package com.unilife.utils;
public class RegexPatterns {
/**
*
*/
public static final String PHONE_REGEX = "^1([38][0-9]|4[579]|5[0-3,5-9]|6[6]|7[0135678]|9[89])\\d{8}$";
/**
*
*/
public static final String EMAIL_REGEX = "^[a-zA-Z0-9_-]+@[a-zA-Z0-9_-]+(\\.[a-zA-Z0-9_-]+)+$";
/**
* 4~32线
*/
public static final String PASSWORD_REGEX = "^\\w{4,32}$";
/**
* , 6
*/
public static final String VERIFY_CODE_REGEX = "^[a-zA-Z\\d]{6}$";
}

@ -0,0 +1,39 @@
package com.unilife.utils;
import cn.hutool.core.util.StrUtil;
public class RegexUtils {
/**
*
* @param phone
* @return true:false
*/
public static boolean isPhoneInvalid(String phone){
return mismatch(phone, RegexPatterns.PHONE_REGEX);
}
/**
*
* @param email
* @return true:false
*/
public static boolean isEmailInvalid(String email){
return mismatch(email, RegexPatterns.EMAIL_REGEX);
}
/**
*
* @param code
* @return true:false
*/
public static boolean isCodeInvalid(String code){
return mismatch(code, RegexPatterns.VERIFY_CODE_REGEX);
}
// 校验是否不符合正则格式
private static boolean mismatch(String str, String regex){
if (StrUtil.isBlank(str)) {
return true;
}
return !str.matches(regex);
}
}

@ -0,0 +1,55 @@
server:
port: 8087
forward-headers-strategy: framework
spring:
datasource:
url: jdbc:mysql://localhost:3306/UniLife?useSSL=false&serverTimezone=UTC&characterEncoding=UTF-8
username: root
password: zhong20050428
driver-class-name: com.mysql.cj.jdbc.Driver
mail:
host: smtp.163.com
port: 465
username: c2991692032@163.com
password: VPq5u3NcAAqtG9GT
properties:
mail:
smtp:
auth: true
ssl:
enable: true
socketFactory:
port: 465
class: javax.net.ssl.SSLSocketFactory
redis:
host: 127.0.0.1
port: 6379
#电脑上要下载redis客户端
#每次使用发送邮件功能前需要在redis目录下启动cmd并执行redis-server.exe redis.windows.conf打开服务
#若出现一个盒子状的东西和port参数服务启动成功此时不要关闭命令行窗口否则会关闭redis服务
#只有redis服务启动成功邮件才能正常发送
knife4j:
enable: true
openapi:
title: Knife4j官方文档
description: "测试"
email: xiaoymin@foxmail.com
concat: 八一菜刀
url: https://docs.xiaominfo.com
version: v4.0
license: Apache 2.0
license-url: https://stackoverflow.com/
terms-of-service-url: https://stackoverflow.com/
group:
test1:
group-name: 分组名称
api-rule: package
api-rule-resources:
- com.unilife
mybatis:
mapper-locations: classpath:mappers/*.xml
configuration:
map-underscore-to-camel-case: true
logging:
level:
com.unilife: debug

@ -0,0 +1,78 @@
<?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.unilife.mapper.UserMapper">
<resultMap id="userResultMap" type="com.unilife.model.entity.User">
<id column="id" property="id"/>
<result column="email" property="email"/>
<result column="password" property="password"/>
<result column="username" property="username"/>
<result column="nickname" property="nickname"/>
<result column="avatar" property="avatar"/>
<result column="role" property="role"/>
<result column="is_verified" property="isVerified"/>
<result column="status" property="status"/>
<result column="login_ip" property="loginIp"/>
</resultMap>
<insert id="insert" parameterType="com.unilife.model.entity.User" useGeneratedKeys="true" keyProperty="id">
INSERT INTO users (
username, email, password,nickname,avatar,bio, gender,student_id,department, major,grade,points,role,status,is_verified,login_ip,login_time
) VALUES (
#{username},
#{email},
#{password},
#{nickname},
#{avatar},
#{bio},
#{gender},
#{studentId},
#{department},
#{major},
#{grade},
#{points},
#{role},
#{status},
#{isVerified},
#{loginIp},
#{loginTime}
)
</insert>
<select id="FindByEmail" resultMap="userResultMap">
SELECT id, email, password, username, nickname, avatar, role, is_verified, status,login_ip
FROM users
WHERE email = #{email} AND password = #{password}
</select>
<select id="FindByOnlyEmail" resultMap="userResultMap">
SELECT id, email, password, username, nickname, avatar, role, is_verified, status,login_ip
FROM users
WHERE email = #{email}
</select>
<update id="UpdateIPLocation" parameterType="com.unilife.model.entity.User">
UPDATE users
SET login_ip = #{loginIp}
WHERE email = #{email}
</update>
<select id="getUserByEmail" resultType="com.unilife.model.entity.User">
select username,
email,
password,
nickname,
avatar,
bio,
gender,
student_id,
department,
major,
grade,
points,
role,
status,
is_verified,
login_ip,
login_time
from users where email=#{email};
</select>
</mapper>

@ -0,0 +1,6 @@
<html>
<body>
<h1>hello word!!!</h1>
<p>this is a html page</p>
</body>
</html>

@ -0,0 +1,13 @@
package com.unilife;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
@SpringBootTest
class BackendApplicationTests {
@Test
void contextLoads() {
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 268 KiB

@ -0,0 +1,107 @@
# UniLife学生论坛开发任务书
## 1. 背景介绍
UniLife 是一款面向学生的在线论坛,旨在提升学生的校园生活体验。项目口号为“**有你生活,优你生活**”强调社区互动、学习资源共享以及智能辅助功能。初期计划作为网站开发后续可能扩展为移动端应用。UniLife 结合了论坛、学习工具和 AI 辅助等功能,帮助学生高效管理学习与生活。
## 2. 欲解决问题
1. **信息获取难**:缺乏统一的信息平台,学生获取课程信息、校园新闻较为分散。
2. **学习资源不足**:没有系统化的学习资料汇总,缺乏便捷的共享和查询方式。
3. **时间管理混乱**:课程表、作业和个人行程难以整合,影响学习效率。
4. **学习计划缺乏智能性**:希望引入 AI帮助学生制定个性化学习计划和日程管理。
5. **学生互动需求**:需要一个开放的论坛环境,支持讨论、交流学习经验。
6. **激励机制缺失**:希望通过积分系统鼓励用户积极贡献和使用平台。
## 3. 推荐方案
### 技术架构
- **前端**Vue.js
- **后端**Spring BootJava
- **数据库**MySQL
- **缓存**Redis
- **AI 支持**GPT-4 API 及推荐算法
- **部署**Docker + Kubernetes后期扩展
## 4.应用场景
- **学生论坛**:作为校园内部社交与学习交流平台
- **学习辅助**:提供智能学习计划和学习资源
- **个人时间管理**:高效管理课程表与个人日程
- **数据共享**:学生自主上传和共享学习资料
## 5. 软件系统的功能描述
UniLife主要功能如下
### (1) 用户系统
- **账号注册/登录**(支持学校邮箱认证)
- 用户资料管理
### (2) 论坛功能
- 主题发布/评论/点赞
- **话题分类**(学习、校园生活、兴趣交流等)
- 接入校园新闻(自动抓取/人工编辑)
### (3) 学习资源共享
- **共享文档**:学生自主填写各课程作业情况
- 资料上传/下载权限管理
### (4) 课程表 & 个人行程安排
- **课程表导入**(支持 API 接入学校系统或手动录入)
- **个人日程管理**(整合课程表,支持自定义编辑)
### (5) AI 辅助学习
- **学习计划制定**(根据课程安排和个人目标)
- **任务提醒**(结合课程表,提供智能通知)
- 部分课程智能辅助(例如编程自动批改、数学解题等)
### (6) 查询功能
- **课程信息查询**
- **资料搜索**
- **论坛帖子搜索**
### (7) 积分系统
- **贡献积分**(发帖、上传资源等)
- **兑换功能**(可用于解锁高级功能,如帖子置顶等等)
### (8) 补充模块
- **通知系统**(用于提醒作业截止日期、课程变动等)
- **权限管理**(不同用户角色拥有不同权限,如普通用户、管理员)
- **移动端优化**(后期可拓展为 PWA 或 App
## 6. 环境要求
### 开发环境
- **操作系统**Windows
- **编程语言**JavaSpring Boot、JavaScriptVue.js
- **数据库**MySQL
- **代码管理**Git + GitHub
### 运行环境
- **服务器**:阿里云 / 腾讯云 / 本地服务器
- **Web 服务器**Nginx + Tomcat
- **存储**:对象存储(阿里云 OSS / AWS S3
- **负载均衡**Nginx / Kubernetes
### 测试环境
- **单元测试**JUnit、Mockito
- **前端测试**Jest、Cypress
- **集成测试**Postman / Swagger
## 7. 可行性及潜在风险
### 可行性分析
- **技术可行性**:技术选型成熟,已有成熟的开发框架支持。
- **资源可行性**:可依赖学校提供的 API 进行数据对接。
- **市场需求**:学生对信息交流、学习资源共享有强需求。
### 潜在风险及对策
| **风险** | **可能影响** | **应对措施** |
| --- | --- | --- |
| 学生活跃度不高 | 影响社区运营 | 设计激励机制(积分系统、竞赛等) |
| 资源管理混乱 | 影响学习资料质量 | 设置审核机制,管理员定期维护 |
| 课程表数据难以对接 | 用户录入成本高 | 提供手动导入和 API 双重方案 |
| AI 辅助效果不理想 | 影响用户体验 | 逐步迭代 AI 模块,收集用户反馈 |
## 8. 承担人员
- **项目经理**:王雨菲
- **产品经理**:钟宏烨
- **开发经理**:蔡子钦
- **小组成员**:胡天琦,贾瀚翔,刘宇航

@ -0,0 +1,117 @@
# UniLife学生论坛开发任务书
## 1. 背景介绍
UniLife 是一款专为大学生设计的综合性在线论坛平台,旨在通过整合学习、生活与社交功能,全面提升学生的校园生活体验。项目口号为“有你生活,优你生活”,强调以用户为中心,通过社区互动、学习资源共享以及智能辅助功能,为学生打造一个高效、便捷、智能的学习与生活管理工具。
UniLife 的核心目标是为学生提供一个一站式服务平台,解决他们在校园生活中遇到的信息分散、资源不足、时间管理混乱等问题。通过结合论坛、学习工具和 AI 辅助功能UniLife 不仅帮助学生更好地管理学习任务,还为他们提供了一个开放的交流空间,促进知识共享与经验交流。
## 2. 欲解决问题
1. **信息获取难**:缺乏统一的信息平台,学生获取课程信息、校园新闻较为分散。
2. **学习资源不足**:没有系统化的学习资料汇总,缺乏便捷的共享和查询方式。
3. **时间管理混乱**:课程表、作业和个人行程难以整合,影响学习效率。
4. **学习计划缺乏智能性**:希望引入 AI帮助学生制定个性化学习计划和日程管理。
5. **学生互动需求**:需要一个开放的论坛环境,支持讨论、交流学习经验。
6. **激励机制缺失**:希望通过积分系统鼓励用户积极贡献和使用平台。
7. **作业管理混乱**:作业资料分散,难以找到高质量资源,缺乏便捷的共享和查询方式。
8. **个性化推荐不足**:缺乏根据用户兴趣和学习习惯的个性化推荐功能。
## 3. 推荐方案
### 技术架构
- **前端**Vue.js
- **后端**Spring BootJava
- **数据库**MySQL
- **缓存**Redis
- **AI 支持**GPT-4 API 及推荐算法
- **部署**Docker + Kubernetes后期扩展
## 4.应用场景
- **学生论坛**:作为校园内部社交与学习交流平台
- **学习辅助**:提供智能学习计划和学习资源
- **个人时间管理**:高效管理课程表与个人日程
- **数据共享**:学生自主上传和共享学习资料
- **作业共享与查询**:支持上传、共享并查询各门课的作业,形成高质量作业资源库
## 5. 软件系统的功能描述
UniLife主要功能如下
### (1) 用户系统
- **账号注册/登录**(支持学校邮箱认证)
- 用户资料管理
### (2) 论坛功能
- 主题发布/评论/点赞
- **话题分类**(学习、校园生活、兴趣交流等)
- 接入校园新闻(自动抓取/人工编辑)
- **私信功能**:支持用户之间私密交流
### (3) 学习资源共享
- **共享文档**:学生自主填写各课程作业情况
- 资料上传/下载权限管理
- **分类与搜索**:支持按课程、年级、作业类型分类,提供关键词搜索功能
### (4) 课程表 & 个人行程安排
- **课程表导入**(支持 API 接入学校系统或手动录入)
- **个人日程管理**(整合课程表,支持自定义编辑)
- **实时提醒**:提供课表变动、作业截止日期、考试安排等实时通知
### (5) AI 辅助学习
- **学习计划制定**(根据课程安排和个人目标)
- **任务提醒**(结合课程表,提供智能通知)
- **个性化推荐**:根据用户学习习惯和兴趣,推荐相关学习资料、课程和活动
### (6) 查询功能
- **课程信息查询**
- **资料搜索**
- **论坛帖子搜索**
- **作业查询**:支持按课程、日期、关键词等分类搜索
### (7) 积分系统
- **贡献积分**(发帖、上传资源等)
- **兑换功能**(可用于解锁高级功能,如帖子置顶等)
### (8) 补充模块
- **通知系统**(用于提醒作业截止日期、课程变动等)
- **权限管理**(不同用户角色拥有不同权限,如普通用户、管理员)
- **移动端优化**(后期可拓展为 PWA 或 App
## 6. 环境要求
### 开发环境
- **操作系统**Windows
- **编程语言**JavaSpring Boot、JavaScriptVue.js
- **数据库**MySQL
- **代码管理**Git + GitHub
### 运行环境
- **服务器**:阿里云 / 腾讯云 / 本地服务器
- **Web 服务器**Nginx + Tomcat
- **存储**:对象存储(阿里云 OSS / AWS S3
- **负载均衡**Nginx / Kubernetes
### 测试环境
- **单元测试**JUnit、Mockito
- **前端测试**Jest、Cypress
- **集成测试**Postman / Swagger
## 7. 可行性及潜在风险
### 可行性分析
- **技术可行性**:技术选型成熟,已有成熟的开发框架支持。
- **资源可行性**:可依赖学校提供的 API 进行数据对接。
- **市场需求**:学生对信息交流、学习资源共享有强需求。
### 潜在风险及对策
| **风险** | **可能影响** | **应对措施** |
| --- | --- | --- |
| 学生活跃度不高 | 影响社区运营 | 设计激励机制(积分系统、竞赛等) |
| 资源管理混乱 | 影响学习资料质量 | 设置审核机制,管理员定期维护 |
| 课程表数据难以对接 | 用户录入成本高 | 提供手动导入和 API 双重方案 |
| AI 辅助效果不理想 | 影响用户体验 | 逐步迭代 AI 模块,收集用户反馈 |
| 版权与学术诚信问题 | 涉及抄袭或学术不端 | 明确平台规则,提供“参考用途”提示,推广学术诚信教育 |
| 数据安全与隐私问题 | 用户担心数据泄露 | 符合隐私政策,提供匿名功能,确保数据安全 |
## 8. 承担人员
- **项目经理**:王雨菲
- **产品经理**:钟宏烨
- **开发经理**:蔡子钦
- **小组成员**:胡天琦,贾瀚翔,刘宇航

@ -0,0 +1,360 @@
**用户:**我们正在开发一个多功能校园论坛综合体有课表查询、作业共享查询、学习资料共享、ai辅助制定学习计划和基础的校园论坛功能等你能帮助我们获取和精化需求吗
**LLM**当然可以!开发一个多功能校园论坛综合体是一个非常有趣且有意义的项目。以下是一些关于如何获取和精化需求的建议:
### 1. **用户调研**
- **目标用户**:明确你的目标用户群体,比如本科生、研究生、教师等。
- **调研方法**:可以通过问卷调查、访谈、焦点小组等方式收集用户的需求和痛点。
- **关键问题**:询问用户他们目前在使用哪些工具,这些工具的优缺点是什么,他们希望新系统能提供哪些功能。
### 2. **功能细化**
- **课表查询**:确保用户可以方便地查看和更新课表,支持导入导出功能,可能还需要提醒功能。
- **作业共享查询**:用户可以上传、下载和查询作业,支持按课程、日期等分类搜索。
- **学习资料共享**:建立一个资料库,用户可以上传和下载学习资料,支持评论和评分功能。
- **AI辅助制定学习计划**根据用户的课表和作业情况AI可以帮助制定学习计划提供学习建议。
- **校园论坛**:支持发帖、回帖、点赞、私信等基本功能,可以按话题分类,支持搜索功能。
### 3. **技术需求**
- **平台选择**决定是开发Web应用、移动应用还是两者兼顾。
- **数据安全**:确保用户数据的安全性,特别是涉及到个人信息和学习资料的部分。
- **性能优化**:确保系统在高并发情况下的稳定性和响应速度。
### 4. **用户体验**
- **界面设计**:简洁直观的界面设计,确保用户能够快速上手。
- **交互设计**:良好的交互设计,减少用户操作步骤,提升用户体验。
### 5. **测试与反馈**
- **原型测试**:在开发初期制作原型,进行用户测试,收集反馈并进行迭代。
- **持续改进**:上线后持续收集用户反馈,不断优化和改进系统。
### 6. **法律与合规**
- **隐私政策**:确保系统符合相关的隐私政策和数据保护法规。
- **版权问题**:确保用户上传的资料不侵犯版权,制定相应的使用条款。
希望这些建议能帮助你更好地获取和精化需求,祝你的项目顺利成功!如果有更多具体问题,欢迎随时提问。
**用户:**我们的目标用户群体是大学生,你觉得大学生对于一个可以涵盖大部分学习生活的网站有什么需求和痛点
**LLM**大学生对于一个涵盖大部分学习生活的网站通常会有以下需求和痛点,这些可以帮助你更好地设计和优化你的多功能校园论坛综合体:
### 需求
1. **一站式服务**
- **需求**:希望一个平台能够整合所有学习相关的功能,如课表查询、作业管理、学习资料共享、考试安排等。
- **痛点**:目前可能需要使用多个不同的应用或网站来完成这些任务,导致操作繁琐和时间浪费。
2. **便捷的课表管理**
- **需求**:能够方便地查看和更新课表,支持导入导出功能,提醒上课时间和地点。
- **痛点**:现有的课表管理工具可能不够灵活,无法及时更新或提醒。
3. **高效的作业管理**
- **需求**:能够上传、下载和查询作业,支持按课程、日期等分类搜索,提醒作业截止日期。
- **痛点**:作业管理混乱,容易忘记截止日期,或者找不到相关的作业资料。
4. **丰富的学习资料共享**
- **需求**:建立一个资料库,用户可以上传和下载学习资料,支持评论和评分功能。
- **痛点**:学习资料分散,难以找到高质量的资源,或者资源更新不及时。
5. **智能的学习计划制定**
- **需求**根据用户的课表和作业情况AI可以帮助制定学习计划提供学习建议。
- **痛点**:缺乏个性化的学习计划,难以合理安排学习时间。
6. **活跃的校园论坛**
- **需求**:支持发帖、回帖、点赞、私信等基本功能,可以按话题分类,支持搜索功能。
- **痛点**:现有的校园论坛可能不够活跃,或者功能单一,无法满足多样化的交流需求。
7. **个性化推荐**
- **需求**:根据用户的学习习惯和兴趣,推荐相关的学习资料、课程和活动。
- **痛点**:缺乏个性化的推荐,难以发现对自己有用的资源。
### 痛点
1. **信息过载**
- **痛点**:面对大量的信息和功能,用户可能会感到 overwhelmed不知道如何高效地使用平台。
- **解决方案**:设计简洁直观的界面,提供引导和教程,帮助用户快速上手。
2. **数据安全与隐私**
- **痛点**:担心个人信息和学习资料的安全性,害怕数据泄露。
- **解决方案**:确保系统符合相关的隐私政策和数据保护法规,提供透明的数据使用说明。
3. **跨平台兼容性**
- **痛点**:希望能够在不同的设备(如手机、平板、电脑)上无缝使用平台。
- **解决方案**:开发响应式设计,支持多平台访问,确保用户体验一致。
4. **实时更新与通知**
- **痛点**:希望及时获取课表变动、作业提醒、考试安排等重要信息。
- **解决方案**提供实时更新和通知功能支持多种通知方式如邮件、短信、APP推送
5. **社区互动与支持**
- **痛点**:希望在遇到问题时能够快速获得帮助,或者与其他同学交流经验。
- **解决方案**建立活跃的社区论坛提供在线客服或FAQ鼓励用户互动和互助。
通过深入了解这些需求和痛点,你可以更好地设计和优化你的多功能校园论坛综合体,提供更符合大学生实际需求的服务。如果有更多具体问题,欢迎随时提问!
**用户:**目前市面上有没有什么类似的程序?这些程序的亮点分别是什么?根据上文的内容,我们的程序可以把什么作为产品亮点?
**LLM**目前市面上确实有一些类似的程序,它们各自有不同的亮点。以下是一些常见的类似程序及其亮点:
### 1. **Blackboard**
- **亮点**
- **综合性强**:整合了课程管理、作业提交、成绩查询、讨论区等多种功能。
- **跨平台支持**支持Web和移动端访问方便用户随时随地使用。
- **丰富的教育资源**:提供大量的在线课程和学习资源。
### 2. **Canvas**
- **亮点**
- **用户友好的界面**:设计简洁直观,易于上手。
- **强大的协作功能**:支持小组讨论、项目协作等功能,促进师生互动。
- **个性化学习路径**:根据学生的学习进度和表现,提供个性化的学习建议。
### 3. **Moodle**
- **亮点**
- **开源和可定制性**:开源平台,允许用户根据需求进行定制和扩展。
- **丰富的插件支持**:支持多种插件,可以扩展功能,如在线考试、证书颁发等。
- **社区支持**:拥有活跃的开发者社区,提供技术支持和资源共享。
### 4. **Google Classroom**
- **亮点**
- **无缝集成Google工具**与Google Drive、Docs、Sheets等工具无缝集成方便文件管理和协作。
- **简洁易用**:界面简洁,操作简单,适合快速上手。
- **实时反馈**:教师可以实时查看学生作业进度,并提供即时反馈。
### 5. **Edmodo**
- **亮点**
- **社交网络风格**:界面类似于社交网络,易于学生和教师互动。
- **家长参与**:支持家长账号,家长可以查看学生的学习进度和表现。
- **丰富的教育资源**:提供大量的教育应用和资源,支持个性化学习。
### 你的程序可以突出的产品亮点
根据上文的内容,你的程序可以考虑以下产品亮点:
1. **一站式学习生活平台**
- **亮点**整合课表查询、作业管理、学习资料共享、AI辅助学习计划制定、校园论坛等多种功能满足大学生全方位的学习和生活需求。
2. **智能AI辅助学习计划**
- **亮点**利用AI技术根据用户的课表和作业情况智能制定个性化的学习计划提供学习建议帮助学生高效管理时间。
3. **丰富的学习资料共享社区**
- **亮点**:建立一个活跃的学习资料共享社区,用户可以上传、下载和评论学习资料,支持按课程、日期等分类搜索,提供高质量的学习资源。
4. **实时更新与通知系统**
- **亮点**:提供实时更新和通知功能,及时提醒用户课表变动、作业截止日期、考试安排等重要信息,确保用户不会错过任何重要事项。
5. **个性化推荐系统**
- **亮点**:根据用户的学习习惯和兴趣,推荐相关的学习资料、课程和活动,帮助用户发现对自己有用的资源。
6. **跨平台兼容性**
- **亮点**支持Web和移动端访问确保用户可以在不同的设备上无缝使用平台提供一致的用户体验。
7. **数据安全与隐私保护**
- **亮点**:确保系统符合相关的隐私政策和数据保护法规,提供透明的数据使用说明,保护用户的个人信息和学习资料安全。
通过突出这些亮点,你的程序可以在竞争激烈的市场中脱颖而出,吸引更多大学生用户。如果有更多具体问题,欢迎随时提问!
**用户:**我们想把各科作业分享、共享和查询作为产品亮点,你觉得可行吗?需要注意什么
**LLM**将各科作业的分享、共享和查询作为产品亮点是**非常可行**的,因为这是大学生日常学习中的高频需求,能够有效解决作业管理混乱、资料分散等问题。然而,要实现这一亮点并确保其成功,需要注意以下几个方面:
---
### **可行性分析**
1. **用户需求强烈**
- 大学生经常需要查找作业要求、参考他人的作业思路,或者分享自己的作业成果。一个集中的作业共享平台可以极大地提高学习效率。
2. **差异化竞争**
- 虽然市面上有一些学习管理平台如Blackboard、Canvas但专门针对作业共享和查询的功能并不多见。如果你的平台能够在这方面做得更深入、更便捷将具有很强的竞争力。
3. **社区互动价值**
- 作业共享功能可以促进学生之间的互动和交流,形成一个学习社区,增强用户粘性。
---
### **需要注意的关键点**
1. **版权与学术诚信**
- **问题**:作业共享可能涉及抄袭或学术不端行为。
- **解决方案**
- 明确平台的使用规则,禁止直接复制他人作业。
- 提供“参考用途”提示,鼓励用户仅将共享作业作为学习参考。
- 与学校合作,推广学术诚信教育。
2. **数据质量与分类**
- **问题**:如果作业内容杂乱无章,用户难以找到需要的资源。
- **解决方案**
- 设计清晰的分类系统,按课程、年级、作业类型等分类。
- 引入评分或评论功能,让用户对作业质量进行评价,帮助筛选高质量内容。
- 提供搜索功能,支持关键词、标签等搜索方式。
3. **用户贡献激励**
- **问题**:用户可能缺乏动力上传作业。
- **解决方案**
- 设计激励机制,如积分系统、排行榜、奖励等,鼓励用户分享优质作业。
- 提供便捷的上传工具,降低用户操作成本。
4. **隐私与数据安全**
- **问题**:作业中可能包含个人信息或敏感内容。
- **解决方案**
- 允许用户匿名上传作业。
- 提供隐私设置选项,让用户决定作业的公开范围(如仅限班级、全校或公开)。
- 确保平台符合数据保护法规,保护用户隐私。
5. **技术支持与用户体验**
- **问题**:如果平台功能复杂或操作不便,用户可能不愿意使用。
- **解决方案**
- 设计简洁直观的界面,降低用户学习成本。
- 提供多格式支持如PDF、Word、图片等方便用户上传和查看作业。
- 确保平台稳定性和响应速度,避免卡顿或崩溃。
6. **与学校课程体系的结合**
- **问题**:如果作业内容与课程脱节,用户可能觉得平台不实用。
- **解决方案**
- 与学校或教师合作,获取官方作业资源或推荐内容。
- 支持用户按课程、教师或学期筛选作业,提高相关性。
---
### **如何突出这一亮点**
1. **功能设计**
- **作业上传与下载**:支持多种格式,提供清晰的分类和标签。
- **作业查询**:支持关键词搜索、按课程筛选、按时间排序等功能。
- **作业评论与评分**:允许用户对作业进行评价,形成高质量内容的推荐机制。
- **作业提醒**:提醒用户作业截止日期,并提供相关参考作业。
2. **社区互动**
- 设计讨论区,允许用户针对特定作业提问或交流。
- 引入“作业互助”功能,让用户可以请求帮助或提供帮助。
3. **个性化推荐**
- 根据用户的学习记录和兴趣,推荐相关的作业资源。
- 提供“热门作业”或“精选作业”板块,展示高质量内容。
4. **数据可视化**
- 提供用户作业上传和下载的数据统计,帮助用户了解自己的学习情况。
- 展示作业的热度和评价,帮助用户快速找到优质资源。
---
### **总结**
将各科作业的分享、共享和查询作为产品亮点是可行的,但需要在**版权保护、数据质量、用户激励、隐私安全**等方面做好设计和规划。如果能够解决这些问题,并提供便捷、高效的功能,这一亮点将极大地吸引大学生用户,并成为平台的核心竞争力。希望这些建议对你有帮助,如果有更多问题,欢迎随时交流!
**用户:**根据上面的对话内容生成一个总结文档
**LLM**
### **多功能校园论坛综合体项目总结文档**
---
#### **项目背景**
我们正在开发一个多功能校园论坛综合体旨在为大学生提供一个涵盖大部分学习生活需求的一站式平台。核心功能包括课表查询、作业共享与查询、学习资料共享、AI辅助学习计划制定以及校园论坛等。目标用户群体为大学生希望通过整合这些功能解决他们在学习和生活中的痛点提升学习效率和体验。
---
#### **用户需求与痛点**
1. **需求**
- 一站式服务:整合课表、作业、学习资料等功能。
- 便捷的课表管理:支持导入导出、提醒功能。
- 高效的作业管理:上传、下载、查询作业,提醒截止日期。
- 丰富的学习资料共享:建立资料库,支持评论和评分。
- 智能的学习计划制定AI辅助个性化学习计划。
- 活跃的校园论坛:支持发帖、回帖、私信等功能。
- 个性化推荐:根据用户兴趣推荐学习资源和活动。
2. **痛点**
- 信息过载:功能繁多,用户可能感到 overwhelmed。
- 数据安全与隐私:担心个人信息泄露。
- 跨平台兼容性:希望在不同设备上无缝使用。
- 实时更新与通知:及时获取课表变动、作业提醒等信息。
- 社区互动与支持:希望快速获得帮助或交流经验。
---
#### **竞争分析**
1. **类似程序**
- **Blackboard**:综合性强,跨平台支持,丰富的教育资源。
- **Canvas**:用户友好,强大的协作功能,个性化学习路径。
- **Moodle**:开源可定制,丰富的插件支持,活跃的社区。
- **Google Classroom**无缝集成Google工具简洁易用实时反馈。
- **Edmodo**:社交网络风格,家长参与,丰富的教育资源。
2. **差异化机会**
- 专注于作业共享与查询,解决大学生作业管理混乱的问题。
- 结合AI技术提供智能学习计划制定和个性化推荐。
- 建立活跃的学习资料共享社区,促进用户互动。
---
#### **产品亮点**
1. **一站式学习生活平台**
- 整合课表、作业、学习资料、AI学习计划、校园论坛等功能。
2. **智能AI辅助学习计划**
- 根据用户课表和作业情况,智能制定个性化学习计划。
3. **丰富的学习资料共享社区**
- 支持上传、下载、评论和评分,提供高质量学习资源。
4. **实时更新与通知系统**
- 及时提醒课表变动、作业截止日期、考试安排等重要信息。
5. **个性化推荐系统**
- 根据用户学习习惯和兴趣,推荐相关学习资料和活动。
6. **跨平台兼容性**
- 支持Web和移动端访问提供一致的用户体验。
7. **数据安全与隐私保护**
- 符合隐私政策和数据保护法规,保护用户个人信息。
---
#### **作业共享与查询功能设计**
1. **功能设计**
- 作业上传与下载:支持多种格式,提供清晰的分类和标签。
- 作业查询:支持关键词搜索、按课程筛选、按时间排序。
- 作业评论与评分:允许用户对作业进行评价,形成高质量内容推荐机制。
- 作业提醒:提醒用户作业截止日期,并提供相关参考作业。
2. **社区互动**
- 设计讨论区,允许用户针对特定作业提问或交流。
- 引入“作业互助”功能,让用户可以请求帮助或提供帮助。
3. **个性化推荐**
- 根据用户学习记录和兴趣,推荐相关作业资源。
- 提供“热门作业”或“精选作业”板块,展示高质量内容。
4. **数据可视化**
- 提供用户作业上传和下载的数据统计,帮助用户了解学习情况。
- 展示作业热度和评价,帮助用户快速找到优质资源。
---
#### **注意事项**
1. **版权与学术诚信**
- 明确平台使用规则,禁止直接复制他人作业。
- 提供“参考用途”提示,鼓励用户仅将共享作业作为学习参考。
2. **数据质量与分类**
- 设计清晰的分类系统,按课程、年级、作业类型等分类。
- 引入评分或评论功能,筛选高质量内容。
3. **用户贡献激励**
- 设计积分系统、排行榜、奖励等激励机制,鼓励用户分享优质作业。
4. **隐私与数据安全**
- 允许用户匿名上传作业,提供隐私设置选项。
- 确保平台符合数据保护法规,保护用户隐私。
5. **技术支持与用户体验**
- 设计简洁直观的界面,降低用户学习成本。
- 确保平台稳定性和响应速度,避免卡顿或崩溃。
6. **与学校课程体系的结合**
- 与学校或教师合作,获取官方作业资源或推荐内容。
- 支持用户按课程、教师或学期筛选作业,提高相关性。
---
#### **总结**
通过整合课表管理、作业共享、学习资料共享、AI学习计划制定和校园论坛等功能我们的多功能校园论坛综合体能够有效解决大学生在学习生活中的痛点。特别是将作业共享与查询作为产品亮点结合AI技术和社区互动可以形成独特的竞争优势。在开发过程中需重点关注版权保护、数据质量、用户激励、隐私安全等问题确保平台的高效性和用户友好性。希望这些总结和建议对项目推进有所帮助期待项目的成功上线
Loading…
Cancel
Save