init backend

This commit is contained in:
zzy
2025-09-28 22:19:05 +08:00
parent 1eca0aed4a
commit 4e4988c104
18 changed files with 1794 additions and 0 deletions

92
pom.xml
View File

@ -40,6 +40,26 @@
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!-- https://github.com/xerial/sqlite-jdbc -->
<!-- https://mvnrepository.com/artifact/org.xerial/sqlite-jdbc -->
<dependency>
<groupId>org.xerial</groupId>
<artifactId>sqlite-jdbc</artifactId>
<version>3.50.3.0</version>
</dependency>
<!-- https://mvnrepository.com/artifact/io.swagger.parser.v3/swagger-parser-v3 -->
<dependency>
<groupId>io.swagger.parser.v3</groupId>
<artifactId>swagger-parser-v3</artifactId>
<version>2.1.34</version>
</dependency>
<!-- https://mvnrepository.com/artifact/io.swagger.parser.v3/swagger-parser-core -->
<dependency>
<groupId>io.swagger.parser.v3</groupId>
<artifactId>swagger-parser-core</artifactId>
<version>2.1.34</version>
</dependency>
</dependencies>
<build>
@ -48,6 +68,78 @@
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
<plugin>
<!-- https://openapi-generator.tech/docs/plugins/ -->
<groupId>org.openapitools</groupId>
<artifactId>openapi-generator-maven-plugin</artifactId>
<version>7.15.0</version>
<executions>
<execution>
<id>bookmark-api</id>
<goals>
<goal>generate</goal>
</goals>
<configuration>
<inputSpec>${project.basedir}/src/main/resources/config/bookmark/bookmark.yaml</inputSpec>
<generatorName>spring</generatorName>
<configOptions>
<sourceFolder>src/gen/java/main</sourceFolder>
<delegatePattern>false</delegatePattern>
<useTags>true</useTags>
<interfaceOnly>true</interfaceOnly>
<skipDefaultInterface>true</skipDefaultInterface>
<useJakartaEe>true</useJakartaEe>
<useBeanValidation>true</useBeanValidation>
<withJakartaValidation>true</withJakartaValidation>
<openApiNullable>false</openApiNullable>
</configOptions>
</configuration>
</execution>
<execution>
<id>user-np-api</id>
<goals>
<goal>generate</goal>
</goals>
<configuration>
<inputSpec>${project.basedir}/src/main/resources/config/user_np/user_np.yaml</inputSpec>
<generatorName>spring</generatorName>
<configOptions>
<sourceFolder>src/gen/java/main</sourceFolder>
<delegatePattern>false</delegatePattern>
<useTags>true</useTags>
<interfaceOnly>true</interfaceOnly>
<skipDefaultInterface>true</skipDefaultInterface>
<useJakartaEe>true</useJakartaEe>
<useBeanValidation>true</useBeanValidation>
<withJakartaValidation>true</withJakartaValidation>
<openApiNullable>false</openApiNullable>
</configOptions>
</configuration>
</execution>
<!-- <execution>
<id>vfs-client</id>
<goals>
<goal>generate</goal>
</goals>
<configuration>
<inputSpec>${project.basedir}/src/main/resources/config/vfs/vfs.yaml</inputSpec>
<generatorName>java</generatorName>
<configOptions>
<library>webclient</library>
<dateLibrary>java</dateLibrary>
<useBeanValidation>true</useBeanValidation>
<withJakartaValidation>true</withJakartaValidation>
<openApiNullable>false</openApiNullable>
<groupId>com.zzyxyz.api</groupId>
<artifactId>vfs-client</artifactId>
<artifactVersion>1.0.0</artifactVersion>
<modelPackage>com.zzyxyz.api.vfs.model</modelPackage>
<apiPackage>com.zzyxyz.api.vfs.api</apiPackage>
</configOptions>
</configuration>
</execution> -->
</executions>
</plugin>
</plugins>
</build>

View File

@ -2,12 +2,23 @@ package com.zzyxyz.api.bookmarks;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
@SpringBootApplication
@RestController
@ComponentScan(basePackages = {"com.zzyxyz.api.bookmarks", "org.openapitools"})
public class BookmarksApplication {
public static void main(String[] args) {
SpringApplication.run(BookmarksApplication.class, args);
}
@GetMapping("/hello")
public String hello(@RequestParam(value = "name", defaultValue = "World") String name) {
return String.format("Hello %s!", name);
}
}

View File

@ -0,0 +1,43 @@
package com.zzyxyz.api.bookmarks;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import org.springframework.web.filter.CorsFilter;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import jakarta.validation.constraints.NotNull;
@Configuration
public class CorsConfig {
@Bean
public WebMvcConfigurer corsConfigurer() {
return new WebMvcConfigurer() {
@Override
public void addCorsMappings(@NotNull CorsRegistry registry) {
registry.addMapping("/**")
.allowedOriginPatterns("*") // 使用allowedOriginPatterns而不是allowedOrigins
.allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS")
.allowedHeaders("*")
.allowCredentials(false); // 设置为false避免冲突
}
};
}
@Bean
public CorsFilter corsFilter() {
CorsConfiguration config = new CorsConfiguration();
config.addAllowedOriginPattern("*"); // 使用addAllowedOriginPattern而不是addAllowedOrigin
config.setAllowCredentials(false); // 设置为false避免冲突
config.addAllowedMethod("*");
config.addAllowedHeader("*");
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", config);
return new CorsFilter(source);
}
}

View File

@ -0,0 +1,92 @@
package com.zzyxyz.api.bookmarks.bookmark;
import java.time.OffsetDateTime;
public class Bookmark {
private Long id;
private Long externalId;
private String name;
private String link;
private String detail;
private String description;
private OffsetDateTime createdAt;
private OffsetDateTime updatedAt;
// Constructors
public Bookmark() {}
public Bookmark(Long externalId, String name, String link, String detail, String description) {
this.externalId = externalId;
this.name = name;
this.link = link;
this.detail = detail;
this.description = description;
this.createdAt = OffsetDateTime.now();
this.updatedAt = OffsetDateTime.now();
}
// Getters and Setters
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public Long getExternalId() {
return externalId;
}
public void setExternalId(Long externalId) {
this.externalId = externalId;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getLink() {
return link;
}
public void setLink(String link) {
this.link = link;
}
public String getDetail() {
return detail;
}
public void setDetail(String detail) {
this.detail = detail;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
public OffsetDateTime getCreatedAt() {
return createdAt;
}
public void setCreatedAt(OffsetDateTime createdAt) {
this.createdAt = createdAt;
}
public OffsetDateTime getUpdatedAt() {
return updatedAt;
}
public void setUpdatedAt(OffsetDateTime updatedAt) {
this.updatedAt = updatedAt;
}
}

View File

@ -0,0 +1,124 @@
package com.zzyxyz.api.bookmarks.bookmark;
import java.sql.SQLException;
import java.time.OffsetDateTime;
import org.openapitools.api.DataApi;
import org.openapitools.model.BookmarkRequest;
import org.openapitools.model.BookmarkResponse;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.CrossOrigin;
@Controller
@CrossOrigin(origins = "*")
public class BookmarkController implements DataApi {
@Autowired
private BookmarkDAO bookmarkDAO;
// 初始化数据库表
public BookmarkController() {
try {
bookmarkDAO = new BookmarkDAO();
bookmarkDAO.createTableIfNotExists();
} catch (SQLException e) {
e.printStackTrace();
}
}
@Override
public ResponseEntity<BookmarkResponse> createBookmark(Long id, BookmarkRequest bookmarkRequest) {
try {
// 检查是否已存在相同externalId的书签
Bookmark existingBookmark = bookmarkDAO.findByExternalId(id);
if (existingBookmark != null) {
return ResponseEntity.status(409).body(null);
}
Bookmark bookmark = new Bookmark(
id,
bookmarkRequest.getName(),
bookmarkRequest.getLink(),
bookmarkRequest.getDetail(),
bookmarkRequest.getDescription()
);
Bookmark savedBookmark = bookmarkDAO.insert(bookmark);
BookmarkResponse response = convertToResponse(savedBookmark);
return ResponseEntity.status(201).body(response);
} catch (SQLException e) {
e.printStackTrace();
return ResponseEntity.status(500).build();
}
}
@Override
public ResponseEntity<Void> deleteBookmark(Long id) {
try {
Bookmark bookmark = bookmarkDAO.findById(id);
if (bookmark == null) {
return ResponseEntity.notFound().build();
}
bookmarkDAO.delete(id);
return ResponseEntity.noContent().build();
} catch (SQLException e) {
e.printStackTrace();
return ResponseEntity.status(500).build();
}
}
@Override
public ResponseEntity<BookmarkResponse> getBookmark(Long id) {
try {
Bookmark bookmark = bookmarkDAO.findById(id);
if (bookmark == null) {
return ResponseEntity.notFound().build();
}
BookmarkResponse response = convertToResponse(bookmark);
return ResponseEntity.ok(response);
} catch (SQLException e) {
e.printStackTrace();
return ResponseEntity.status(500).build();
}
}
@Override
public ResponseEntity<BookmarkResponse> updateBookmark(Long id, BookmarkRequest bookmarkRequest) {
try {
Bookmark existingBookmark = bookmarkDAO.findById(id);
if (existingBookmark == null) {
return ResponseEntity.notFound().build();
}
existingBookmark.setName(bookmarkRequest.getName());
existingBookmark.setLink(bookmarkRequest.getLink());
existingBookmark.setDetail(bookmarkRequest.getDetail());
existingBookmark.setDescription(bookmarkRequest.getDescription());
existingBookmark.setUpdatedAt(OffsetDateTime.now());
boolean updated = bookmarkDAO.update(existingBookmark);
if (updated) {
BookmarkResponse response = convertToResponse(existingBookmark);
return ResponseEntity.ok(response);
} else {
return ResponseEntity.status(500).build();
}
} catch (SQLException e) {
e.printStackTrace();
return ResponseEntity.status(500).build();
}
}
private BookmarkResponse convertToResponse(Bookmark bookmark) {
return new BookmarkResponse()
.id(bookmark.getId())
.name(bookmark.getName())
.link(bookmark.getLink())
.detail(bookmark.getDetail())
.description(bookmark.getDescription())
.createdAt(bookmark.getCreatedAt())
.updatedAt(bookmark.getUpdatedAt());
}
}

View File

@ -0,0 +1,170 @@
package com.zzyxyz.api.bookmarks.bookmark;
import org.springframework.stereotype.Component;
import java.sql.*;
import java.time.OffsetDateTime;
import java.util.ArrayList;
import java.util.List;
@Component
public class BookmarkDAO {
private static final String DB_URL = "jdbc:sqlite:bookmarks.db";
static {
try {
Class.forName("org.sqlite.JDBC");
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
public void createTableIfNotExists() throws SQLException {
String sql = """
CREATE TABLE IF NOT EXISTS bookmarks (
id INTEGER PRIMARY KEY AUTOINCREMENT,
external_id INTEGER UNIQUE NOT NULL,
name TEXT NOT NULL,
link TEXT,
detail TEXT,
description TEXT,
created_at TEXT NOT NULL,
updated_at TEXT NOT NULL
)
""";
try (Connection connection = DriverManager.getConnection(DB_URL);
Statement statement = connection.createStatement()) {
statement.executeUpdate(sql);
}
}
public Bookmark insert(Bookmark bookmark) throws SQLException {
String sql = """
INSERT INTO bookmarks (
external_id, name, link, detail, description, created_at, updated_at
) VALUES (?, ?, ?, ?, ?, ?, ?)
""";
try (Connection connection = DriverManager.getConnection(DB_URL);
PreparedStatement statement = connection.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS)) {
statement.setLong(1, bookmark.getExternalId());
statement.setString(2, bookmark.getName());
statement.setString(3, bookmark.getLink());
statement.setString(4, bookmark.getDetail());
statement.setString(5, bookmark.getDescription());
statement.setString(6, bookmark.getCreatedAt().toString());
statement.setString(7, bookmark.getUpdatedAt().toString());
int affectedRows = statement.executeUpdate();
if (affectedRows == 0) {
throw new SQLException("Creating bookmark failed, no rows affected.");
}
try (ResultSet generatedKeys = statement.getGeneratedKeys()) {
if (generatedKeys.next()) {
bookmark.setId(generatedKeys.getLong(1));
} else {
throw new SQLException("Creating bookmark failed, no ID obtained.");
}
}
}
return bookmark;
}
public Bookmark findById(Long id) throws SQLException {
String sql = "SELECT * FROM bookmarks WHERE id = ?";
try (Connection connection = DriverManager.getConnection(DB_URL);
PreparedStatement statement = connection.prepareStatement(sql)) {
statement.setLong(1, id);
ResultSet rs = statement.executeQuery();
if (rs.next()) {
return mapResultSetToBookmark(rs);
}
}
return null;
}
public Bookmark findByExternalId(Long externalId) throws SQLException {
String sql = "SELECT * FROM bookmarks WHERE external_id = ?";
try (Connection connection = DriverManager.getConnection(DB_URL);
PreparedStatement statement = connection.prepareStatement(sql)) {
statement.setLong(1, externalId);
ResultSet rs = statement.executeQuery();
if (rs.next()) {
return mapResultSetToBookmark(rs);
}
}
return null;
}
public List<Bookmark> findAll() throws SQLException {
String sql = "SELECT * FROM bookmarks";
List<Bookmark> bookmarks = new ArrayList<>();
try (Connection connection = DriverManager.getConnection(DB_URL);
Statement statement = connection.createStatement();
ResultSet rs = statement.executeQuery(sql)) {
while (rs.next()) {
bookmarks.add(mapResultSetToBookmark(rs));
}
}
return bookmarks;
}
public boolean update(Bookmark bookmark) throws SQLException {
String sql = """
UPDATE bookmarks SET
name = ?, link = ?, detail = ?, description = ?, updated_at = ?
WHERE id = ?
""";
try (Connection connection = DriverManager.getConnection(DB_URL);
PreparedStatement statement = connection.prepareStatement(sql)) {
statement.setString(1, bookmark.getName());
statement.setString(2, bookmark.getLink());
statement.setString(3, bookmark.getDetail());
statement.setString(4, bookmark.getDescription());
statement.setString(5, OffsetDateTime.now().toString());
statement.setLong(6, bookmark.getId());
int affectedRows = statement.executeUpdate();
return affectedRows > 0;
}
}
public boolean delete(Long id) throws SQLException {
String sql = "DELETE FROM bookmarks WHERE id = ?";
try (Connection connection = DriverManager.getConnection(DB_URL);
PreparedStatement statement = connection.prepareStatement(sql)) {
statement.setLong(1, id);
int affectedRows = statement.executeUpdate();
return affectedRows > 0;
}
}
private Bookmark mapResultSetToBookmark(ResultSet rs) throws SQLException {
Bookmark bookmark = new Bookmark();
bookmark.setId(rs.getLong("id"));
bookmark.setExternalId(rs.getLong("external_id"));
bookmark.setName(rs.getString("name"));
bookmark.setLink(rs.getString("link"));
bookmark.setDetail(rs.getString("detail"));
bookmark.setDescription(rs.getString("description"));
bookmark.setCreatedAt(OffsetDateTime.parse(rs.getString("created_at")));
bookmark.setUpdatedAt(OffsetDateTime.parse(rs.getString("updated_at")));
return bookmark;
}
}

View File

@ -0,0 +1,96 @@
package com.zzyxyz.api.bookmarks.user_np;
import java.time.OffsetDateTime;
public class UserNP {
private Long id;
private String username;
private String password;
private String email;
private String token;
private OffsetDateTime createdAt;
private OffsetDateTime updatedAt;
// Constructors
public UserNP() {
this.createdAt = OffsetDateTime.now();
this.updatedAt = OffsetDateTime.now();
}
public UserNP(String username, String password, String email) {
this.username = username;
this.password = password;
this.email = email;
this.createdAt = OffsetDateTime.now();
this.updatedAt = OffsetDateTime.now();
}
// Getters and Setters
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public String getToken() {
return token;
}
public void setToken(String token) {
this.token = token;
}
public OffsetDateTime getCreatedAt() {
return createdAt;
}
public void setCreatedAt(OffsetDateTime createdAt) {
this.createdAt = createdAt;
}
public OffsetDateTime getUpdatedAt() {
return updatedAt;
}
public void setUpdatedAt(OffsetDateTime updatedAt) {
this.updatedAt = updatedAt;
}
// Hash password method (placeholder)
public void hashPassword(String rawPassword) {
// In a real implementation, you would use a proper password hashing library like BCrypt
this.password = rawPassword; // This is just a placeholder
}
// Check password method (placeholder)
public boolean checkPassword(String rawPassword) {
// In a real implementation, you would use a proper password hashing library like BCrypt
return this.password.equals(rawPassword); // This is just a placeholder
}
}

View File

@ -0,0 +1,142 @@
package com.zzyxyz.api.bookmarks.user_np;
import org.openapitools.api.AuthApi;
import org.openapitools.api.DefaultApi;
import org.openapitools.model.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.CrossOrigin;
import java.sql.SQLException;
import java.time.OffsetDateTime;
import java.util.UUID;
@Controller
@CrossOrigin(origins = "*")
public class UserNPController implements DefaultApi, AuthApi {
@Autowired
private UserNPDAO userDAO;
// 初始化数据库表
public UserNPController() {
try {
userDAO = new UserNPDAO();
userDAO.createTableIfNotExists();
} catch (SQLException e) {
e.printStackTrace();
}
}
@Override
public ResponseEntity<Void> deleteUser(String username) {
try {
UserNP user = userDAO.findByUsername(username);
if (user == null) {
// 如果用户不存在按照API规范返回200状态
return ResponseEntity.ok().build();
}
userDAO.delete(user.getId());
return ResponseEntity.ok().build();
} catch (SQLException e) {
e.printStackTrace();
return ResponseEntity.status(500).build();
}
}
@Override
public ResponseEntity<Void> updatePassword(String username, ChangePasswordRequest changePasswordRequest) {
try {
UserNP user = userDAO.findByUsername(username);
if (user == null) {
return ResponseEntity.status(401).build(); // 认证失败
}
// 验证旧密码
if (!user.checkPassword(changePasswordRequest.getOldPassword())) {
return ResponseEntity.status(401).build(); // 认证失败
}
// 更新密码
user.hashPassword(changePasswordRequest.getNewPassword());
user.setUpdatedAt(OffsetDateTime.now());
userDAO.update(user);
return ResponseEntity.ok().build();
} catch (SQLException e) {
e.printStackTrace();
return ResponseEntity.status(500).build();
}
}
@Override
public ResponseEntity<LoginResponse> userLogin(String username, LoginRequest loginRequest) {
try {
UserNP user = userDAO.findByUsername(username);
if (user == null) {
return ResponseEntity.status(401).build(); // 认证失败
}
// 验证密码
if (!user.checkPassword(loginRequest.getPassword())) {
return ResponseEntity.status(401).build(); // 认证失败
}
// 生成token
String token = UUID.randomUUID().toString();
user.setToken(token);
user.setUpdatedAt(OffsetDateTime.now());
userDAO.update(user);
LoginResponse response = new LoginResponse();
response.setToken(token);
response.setUserId(user.getId());
return ResponseEntity.ok(response);
} catch (SQLException e) {
e.printStackTrace();
return ResponseEntity.status(500).build();
}
}
@Override
public ResponseEntity<Void> userRegister(String username, RegisterRequest registerRequest) {
try {
// 检查用户名是否已存在
UserNP existingUser = userDAO.findByUsername(username);
if (existingUser != null) {
return ResponseEntity.status(409).build(); // 用户名已存在
}
// 创建新用户
UserNP user = new UserNP(username, "", registerRequest.getEmail());
user.hashPassword(registerRequest.getPassword());
// 保存到数据库
UserNP savedUser = userDAO.insert(user);
if (savedUser.getId() != null) {
return ResponseEntity.status(201).build();
} else {
return ResponseEntity.status(500).build();
}
} catch (SQLException e) {
e.printStackTrace();
return ResponseEntity.status(500).build();
}
}
@Override
public ResponseEntity<Object> getUserInfo(String username) {
// 未实现
return ResponseEntity.status(500).build();
}
@Override
public ResponseEntity<Void> saveUserInfo(String username, Object body) {
// 未实现
return ResponseEntity.status(500).build();
}
}

View File

@ -0,0 +1,166 @@
package com.zzyxyz.api.bookmarks.user_np;
import org.springframework.stereotype.Component;
import java.sql.*;
import java.time.OffsetDateTime;
import java.util.ArrayList;
import java.util.List;
@Component
public class UserNPDAO {
private static final String DB_URL = "jdbc:sqlite:users.db";
static {
try {
Class.forName("org.sqlite.JDBC");
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
public void createTableIfNotExists() throws SQLException {
String sql = """
CREATE TABLE IF NOT EXISTS users (
id INTEGER PRIMARY KEY AUTOINCREMENT,
username TEXT NOT NULL UNIQUE,
password TEXT NOT NULL,
email TEXT,
token TEXT,
created_at TEXT NOT NULL,
updated_at TEXT NOT NULL
)
""";
try (Connection connection = DriverManager.getConnection(DB_URL);
Statement statement = connection.createStatement()) {
statement.executeUpdate(sql);
}
}
public UserNP insert(UserNP user) throws SQLException {
String sql = """
INSERT INTO users (
username, password, email, token, created_at, updated_at
) VALUES (?, ?, ?, ?, ?, ?)
""";
try (Connection connection = DriverManager.getConnection(DB_URL);
PreparedStatement statement = connection.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS)) {
statement.setString(1, user.getUsername());
statement.setString(2, user.getPassword());
statement.setString(3, user.getEmail());
statement.setString(4, user.getToken());
statement.setString(5, user.getCreatedAt().toString());
statement.setString(6, user.getUpdatedAt().toString());
int affectedRows = statement.executeUpdate();
if (affectedRows == 0) {
throw new SQLException("Creating user failed, no rows affected.");
}
try (ResultSet generatedKeys = statement.getGeneratedKeys()) {
if (generatedKeys.next()) {
user.setId(generatedKeys.getLong(1));
} else {
throw new SQLException("Creating user failed, no ID obtained.");
}
}
}
return user;
}
public UserNP findById(Long id) throws SQLException {
String sql = "SELECT * FROM users WHERE id = ?";
try (Connection connection = DriverManager.getConnection(DB_URL);
PreparedStatement statement = connection.prepareStatement(sql)) {
statement.setLong(1, id);
ResultSet rs = statement.executeQuery();
if (rs.next()) {
return mapResultSetToUser(rs);
}
}
return null;
}
public UserNP findByUsername(String username) throws SQLException {
String sql = "SELECT * FROM users WHERE username = ?";
try (Connection connection = DriverManager.getConnection(DB_URL);
PreparedStatement statement = connection.prepareStatement(sql)) {
statement.setString(1, username);
ResultSet rs = statement.executeQuery();
if (rs.next()) {
return mapResultSetToUser(rs);
}
}
return null;
}
public List<UserNP> findAll() throws SQLException {
String sql = "SELECT * FROM users";
List<UserNP> users = new ArrayList<>();
try (Connection connection = DriverManager.getConnection(DB_URL);
Statement statement = connection.createStatement();
ResultSet rs = statement.executeQuery(sql)) {
while (rs.next()) {
users.add(mapResultSetToUser(rs));
}
}
return users;
}
public boolean update(UserNP user) throws SQLException {
String sql = """
UPDATE users SET
username = ?, password = ?, email = ?, token = ?, updated_at = ?
WHERE id = ?
""";
try (Connection connection = DriverManager.getConnection(DB_URL);
PreparedStatement statement = connection.prepareStatement(sql)) {
statement.setString(1, user.getUsername());
statement.setString(2, user.getPassword());
statement.setString(3, user.getEmail());
statement.setString(4, user.getToken());
statement.setString(5, OffsetDateTime.now().toString());
statement.setLong(6, user.getId());
int affectedRows = statement.executeUpdate();
return affectedRows > 0;
}
}
public boolean delete(Long id) throws SQLException {
String sql = "DELETE FROM users WHERE id = ?";
try (Connection connection = DriverManager.getConnection(DB_URL);
PreparedStatement statement = connection.prepareStatement(sql)) {
statement.setLong(1, id);
int affectedRows = statement.executeUpdate();
return affectedRows > 0;
}
}
private UserNP mapResultSetToUser(ResultSet rs) throws SQLException {
UserNP user = new UserNP();
user.setId(rs.getLong("id"));
user.setUsername(rs.getString("username"));
user.setPassword(rs.getString("password"));
user.setEmail(rs.getString("email"));
user.setToken(rs.getString("token"));
user.setCreatedAt(OffsetDateTime.parse(rs.getString("created_at")));
user.setUpdatedAt(OffsetDateTime.parse(rs.getString("updated_at")));
return user;
}
}

View File

@ -1 +1,3 @@
spring.application.name=bookmarks
server.port=8081
server.servlet.context-path=/api

View File

@ -0,0 +1,238 @@
openapi: '3.0.3'
info:
title: zzyxyz_bookmark_api
description: bookmark API服务
version: '1.0'
servers:
- url: http://localhost:8081/api
description: 开发环境
- url: https://api.zzyxyz.com/api
description: 生产环境
tags:
- name: data
description: 书签相关操作
security:
- ApiKeyAuth: []
paths:
/bookmarks/v1/data/{id}:
parameters:
- name: id
in: path
example: 1
required: true
schema:
type: integer
format: int64
post:
summary: 创建书签
description: 在文件夹下创建一个书签
operationId: createBookmark
tags: [data]
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/BookmarkRequest'
responses:
'201':
description: 创建成功的书签
content:
application/json:
schema:
$ref: '#/components/schemas/BookmarkResponse'
'400':
description: 请求参数错误
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
'401':
$ref: '#/components/responses/Unauthorized'
'403':
$ref: '#/components/responses/Forbidden'
'500':
$ref: '#/components/responses/ServerInternalError'
get:
summary: 获取书签详情
description: 通过id获取书签内容
operationId: getBookmark
tags: [data]
responses:
'200':
description: 书签详情
content:
application/json:
schema:
$ref: '#/components/schemas/BookmarkResponse'
'404':
description: 书签不存在
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
'401':
$ref: '#/components/responses/Unauthorized'
'403':
$ref: '#/components/responses/Forbidden'
'500':
$ref: '#/components/responses/ServerInternalError'
put:
summary: 更新书签
description: 更新指定id的书签
operationId: updateBookmark
tags: [data]
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/BookmarkRequest'
responses:
'200':
description: 更新后的书签
content:
application/json:
schema:
$ref: '#/components/schemas/BookmarkResponse'
'400':
description: 请求参数错误
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
'404':
description: 书签不存在
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
'401':
$ref: '#/components/responses/Unauthorized'
'403':
$ref: '#/components/responses/Forbidden'
'500':
$ref: '#/components/responses/ServerInternalError'
delete:
summary: 删除书签
description: 删除指定id的书签
operationId: deleteBookmark
tags: [data]
responses:
'204':
description: 删除成功
'404':
description: 书签不存在
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
'401':
$ref: '#/components/responses/Unauthorized'
'403':
$ref: '#/components/responses/Forbidden'
'500':
$ref: '#/components/responses/ServerInternalError'
components:
securitySchemes:
ApiKeyAuth:
type: apiKey
in: header
name: X-BookMark-Token
responses:
ServerInternalError:
description: 服务器内部错误
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
Unauthorized:
description: 未授权
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
Forbidden:
description: 无权限
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
schemas:
BookmarkRequest:
type: object
properties:
name:
type: string
minLength: 1
maxLength: 255
description: 书签名称
example: 测试名称
link:
type: string
description: 书签链接
example: /swagger/index.html
detail:
type: string
description: 书签详情链接
description:
type: string
description: 书签描述
required:
- name
BookmarkResponse:
type: object
description: 书签相应结构体
properties:
id:
type: integer
format: int64
description: 书签ID
name:
type: string
description: 书签名称
link:
type: string
description: 书签链接
detail:
type: string
description: 书签详情链接
description:
type: string
description: 书签描述
created_at:
type: string
format: date-time
description: 创建时间
updated_at:
type: string
format: date-time
description: 更新时间
required:
- id
- name
- created_at
- updated_at
Error:
type: object
description: 错误信息
properties:
errtype:
type: string
example: "ParameterError"
description: 错误类型
message:
example: "传递的第一个参数错误"
type: string
description: 错误信息
required:
- errtype
- message

View File

@ -0,0 +1,6 @@
# yaml-language-server: ...
package: client
generate:
client: true
models: true
output: ./gen/bookmarks_client/client.go

View File

@ -0,0 +1,7 @@
# yaml-language-server: ...
package: server
generate:
gin-server: true
models: true
strict-server: true
output: ./gen/bookmarks_server/server.go

View File

@ -0,0 +1,7 @@
# yaml-language-server: ...
package: server
generate:
gin-server: true
models: true
strict-server: true
output: ./gen/user_np_server/server.go

View File

@ -0,0 +1,210 @@
openapi: '3.0.3'
info:
title: zzyxyz_user_np_api
description: 用户节点权限相关操作(user_name and password)
version: '1.0'
servers:
- url: http://localhost:8081/api
description: 开发环境
- url: https://api.zzyxyz.com/api
description: 生产环境
security:
- ApiKeyAuth: []
paths:
/auth/user/{username}/login:
parameters:
- name: username
in: path
example: user_name
required: true
schema:
type: string
post:
summary: 用户登录
description: 使用用户名和密码进行登录
operationId: UserLogin
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/LoginRequest'
responses:
'200':
description: 登录成功
content:
application/json:
schema:
$ref: '#/components/schemas/LoginResponse'
'400':
description: 请求参数错误
'401':
description: 认证失败
/auth/user/{username}:
parameters:
- name: username
in: path
example: user_name
required: true
schema:
type: string
post:
summary: 用户注册
description: 创建新用户账户
operationId: UserRegister
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/RegisterRequest'
responses:
'201':
description: 注册成功
'400':
description: 请求参数错误
'409':
description: 用户名已存在
patch:
summary: 修改密码
description: 修改已登录用户的密码
operationId: updatePassword
security:
- ApiKeyAuth: []
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/ChangePasswordRequest'
responses:
'200':
description: 密码修改成功
'400':
description: 请求参数错误
'401':
description: 认证失败
delete:
summary: 删除用户
description: 删除用户
operationId: deleteUser
security:
- ApiKeyAuth: []
responses:
'200':
description: 用户注销成功
'401':
description: 认证失败
/auth/user/{username}/info:
parameters:
- name: username
in: path
example: user_name
required: true
schema:
type: string
get:
summary: 获取用户信息
description: 获取用户信息 json object
operationId: getUserInfo
tags: [auth]
responses:
'200':
description: 用户信息
content:
application/json:
schema:
type: object
'500':
$ref: '#/components/responses/ServerInternalError'
put:
summary: 保存用户信息
description: 保存用户信息 json object
operationId: saveUserInfo
tags: [auth]
requestBody:
required: true
content:
application/json:
schema:
type: object
responses:
'200':
description: 保存成功
'500':
$ref: '#/components/responses/ServerInternalError'
components:
responses:
ServerInternalError:
description: 服务器内部错误
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
schemas:
LoginRequest:
type: object
required:
- password
properties:
password:
type: string
LoginResponse:
type: object
properties:
token:
type: string
user_id:
type: integer
format: int64
RegisterRequest:
type: object
required:
- password
properties:
password:
type: string
email:
type: string
ChangePasswordRequest:
type: object
required:
- old_password
- new_password
properties:
old_password:
type: string
new_password:
type: string
Error:
type: object
description: 错误信息
properties:
errtype:
type: string
example: "ParameterError"
description: 错误类型
message:
example: "传递的第一个参数错误"
type: string
description: 错误信息
required:
- errtype
- message
securitySchemes:
ApiKeyAuth:
type: apiKey
in: header
name: Authorization

View File

@ -0,0 +1,6 @@
# yaml-language-server: ...
package: client
generate:
client: true
models: true
output: ./gen/vfs_client/client.go

View File

@ -0,0 +1,7 @@
# yaml-language-server: ...
package: server
generate:
gin-server: true
models: true
strict-server: true
output: ./gen/vfs_server/server.go

View File

@ -0,0 +1,375 @@
openapi: '3.0.3'
info:
title: zzyxyz_vfs_api
description: 虚拟文件系统API服务
version: '1.0'
servers:
- url: http://localhost:8080/api
description: 开发环境
- url: https://api.zzyxyz.com/api
description: 生产环境
tags:
- name: vfs
description: 虚拟文件系统相关操作
- name: user
description: 用户相关操作
security:
- ApiKeyAuth: []
paths:
/vfs/v1/users/{username}:
parameters:
- name: username
in: path
example: user
required: true
schema:
type: string
post:
summary: 创建用户
description: 创建一个用户
operationId: createUser
tags: [user]
responses:
'201':
description: 创建成功
headers:
X-VFS-Token:
schema:
type: string
description: 认证令牌
'400':
$ref: '#/components/responses/ParameterError'
'401':
$ref: '#/components/responses/UnauthorizedError'
'403':
$ref: '#/components/responses/ForbiddenError'
'500':
$ref: '#/components/responses/ServerInternalError'
delete:
summary: 删除用户
description: 删除一个用户
operationId: deleteUser
tags: [user]
responses:
'204':
description: 删除成功
'404':
description: 用户不存在
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
'400':
$ref: '#/components/responses/ParameterError'
'401':
$ref: '#/components/responses/UnauthorizedError'
'403':
$ref: '#/components/responses/ForbiddenError'
'500':
$ref: '#/components/responses/ServerInternalError'
/vfs/v1/files:
parameters:
- name: path
in: query
required: true
description: |
文件系统路径,例如:
- "/documents/readme.txt" (文件路径)
- "/services/sql.bk.api" (服务文件)
- "/folder/" (目录路径,以/结尾)
schema:
type: string
example: "/documents/readme.txt"
get:
summary: 读取文件或列出目录
description: 获取指定路径的文件内容或目录列表
operationId: getVFSNode
tags: [vfs]
parameters:
- name: op
in: query
required: false
description: 操作类型, list表示列出目录内容, 不指定则读取文件内容
schema:
type: string
enum: [list]
responses:
'200':
description: 文件内容或目录列表
content:
application/json:
schema:
description: 目录条目列表
type: array
items:
$ref: '#/components/schemas/VFSDirectoryEntry'
text/plain:
schema:
type: string
'400':
$ref: '#/components/responses/ParameterError'
'401':
$ref: '#/components/responses/UnauthorizedError'
'403':
$ref: '#/components/responses/ForbiddenError'
'404':
$ref: '#/components/responses/PathNotFoundError'
'500':
$ref: '#/components/responses/ServerInternalError'
post:
summary: 创建文件或目录
description: 创建文件或目录
operationId: createVFSNode
tags: [vfs]
requestBody:
required: false
content:
text/plain:
schema:
type: string
description: 文件内容, 或者服务内容
responses:
'201':
description: 创建成功
content:
text/plain:
schema:
type: string
application/json:
schema:
$ref: '#/components/schemas/VFSNodeResponse'
'400':
$ref: '#/components/responses/ParameterError'
'401':
$ref: '#/components/responses/UnauthorizedError'
'403':
$ref: '#/components/responses/ForbiddenError'
'404':
$ref: '#/components/responses/PathNotFoundError'
'500':
$ref: '#/components/responses/ServerInternalError'
patch:
summary: 修改文件或修改目录
description: 修改/移动/重命名/复制 已存在的 文件/目录
operationId: updateVFSNode
tags: [vfs]
parameters:
- name: op
in: query
description: |
更新操作模式:
- move: 移动文件或目录到新位置
- rename: 重命名文件或目录
- change: 修改文件内容或目录属性
- copy: 复制文件或目录到新位置
required: true
schema:
type: string
enum: [move, rename, change, copy]
example: "rename"
requestBody:
required: true
content:
application/json:
schema:
type: string
description: 目的文件夹路径 / 文件名路径
example: "/home/"
text/plain:
schema:
type: string
responses:
'200':
description: 操作成功
content:
application/json:
schema:
$ref: '#/components/schemas/VFSNodeResponse'
'400':
$ref: '#/components/responses/ParameterError'
'401':
$ref: '#/components/responses/UnauthorizedError'
'403':
$ref: '#/components/responses/ForbiddenError'
'404':
$ref: '#/components/responses/PathNotFoundError'
'500':
$ref: '#/components/responses/ServerInternalError'
delete:
summary: 删除文件或目录
description: 删除指定路径的文件或目录
operationId: deleteVFSNode
tags: [vfs]
parameters:
- name: op
in: query
description: |
删除操作模式:
- recursive: 递归删除目录及其所有内容
- force: 强制删除,忽略只读等保护属性
不指定时执行普通删除操作
required: false
schema:
type: string
enum: [recursive, force]
example: "recursive"
responses:
'204':
description: 删除成功
'400':
$ref: '#/components/responses/ParameterError'
'401':
$ref: '#/components/responses/UnauthorizedError'
'403':
$ref: '#/components/responses/ForbiddenError'
'404':
$ref: '#/components/responses/PathNotFoundError'
'500':
$ref: '#/components/responses/ServerInternalError'
components:
securitySchemes:
ApiKeyAuth:
type: apiKey
in: header
name: X-VFS-Token
responses:
ServerInternalError:
description: 服务器内部错误
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
example:
errtype: "InternalServerError"
message: "服务器内部错误"
UnauthorizedError:
description: 未授权
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
example:
errtype: "Unauthorized"
message: "访问被拒绝,缺少有效的认证令牌"
ForbiddenError:
description: 禁止
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
example:
errtype: "Forbidden"
message: "访问被拒绝,您没有权限访问此资源"
PathNotFoundError:
description: 路径不存在
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
example:
errtype: "PathNotFound"
message: "指定的路径不存在"
ParameterError:
description: 请求参数错误
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
example:
errtype: "ParameterError"
message: "请求参数无效或缺失"
schemas:
VFSNodeType:
type: string
description: 节点类型
enum: [file, directory, service]
VFSNodeResponse:
type: object
properties:
name:
type: string
description: 节点名称
type:
$ref: '#/components/schemas/VFSNodeType'
created_at:
type: string
format: date-time
description: 创建时间
updated_at:
type: string
format: date-time
description: 更新时间
required:
- name
- type
- created_at
- updated_at
VFSDirectoryEntry:
type: object
properties:
name:
type: string
description: 条目名称
type:
$ref: '#/components/schemas/VFSNodeType'
permissions:
type: string
description: 权限信息,如 "rwo" (读 写 拥有)
required:
- name
- type
Error:
type: object
description: 错误信息
properties:
errtype:
type: string
description: |
错误类型,可能的值包括:
- "InternalServerError": 服务器内部错误
- "Unauthorized": 客户端需要提供正确格式权限
- "Forbidden": 无权限访问
- "PathNotFound": 路径不存在
- "ParameterError": 请求参数错误
- "ConflictError": 资源冲突
- "DatabaseError": 数据库操作错误
- "NotFoundError": 资源未找到
- "AccessDenied": 访问被拒绝
- "ServiceProxyError": 代理服务错误
example: "InternalServerError"
enum:
- "InternalServerError"
- "Unauthorized"
- "Forbidden"
- "PathNotFound"
- "ParameterError"
- "ConflictError"
- "DatabaseError"
- "NotFoundError"
- "AccessDenied"
- "ServiceProxyError"
message:
type: string
description: 详细的错误信息
example: "传递的第一个参数错误"
required:
- errtype
- message