| Version 4 (modified by , 3 days ago) ( diff ) |
|---|
Други Развојни Активности
Анализа на перформанси
Извештај за годишна финансиска активност на корисници
Анализата на перформанси се врши врз основа на моменталната состојба во базата, која има податоци кои служат за тестирање. Базата содржи: ~1000 корисници, ~2000 приходи (incomes), ~1000 finance профили.
SQL:
CREATE OR REPLACE FUNCTION test_finance_report()
RETURNS void AS $$
DECLARE
start_time timestamptz;
end_time timestamptz;
duration int;
BEGIN
start_time := clock_timestamp();
PERFORM *
FROM (
SELECT
fb.user_id,
COALESCE(stats.income_count, 0),
COALESCE(stats.annual_total_income, 0),
COALESCE(stats.active_months, 0)
FROM finance_users fb
LEFT JOIN (
-- Combined logic into one subquery for efficiency
SELECT
user_id,
COUNT(*) AS income_count,
SUM(amount) AS annual_total_income,
COUNT(DISTINCT EXTRACT(MONTH FROM date)) AS active_months
FROM incomes
WHERE EXTRACT(YEAR FROM date) = 2026
GROUP BY user_id
) stats ON stats.user_id = fb.user_id
) t;
end_time := clock_timestamp();
duration := round(1000 * (extract(epoch FROM end_time) - extract(epoch FROM start_time)));
RAISE NOTICE 'Query executed in: % ms', duration;
END;
Индекси:
idx_incomes_user_date CREATE INDEX idx_incomes_user_date ON incomes(user_id, date);
Подобрување: Index Scan наместо Seq Scan + подобро филтрирање по година
-
idx_incomes_year CREATE INDEX idx_incomes_year ON incomes ((EXTRACT(YEAR FROM date)));
Подобрување: Избегнува full table scan за годишен филтер
3.
idx_incomes_covering CREATE INDEX idx_incomes_covering ON incomes(user_id, date, amount);
Подобрување: Direct lookup
4.
CREATE UNIQUE INDEX users_pkey ON users(user_id);
Подобрување: Fast join
5.
CREATE INDEX idx_incomes_covering ON incomes(user_id, date, amount);
Подобрување: Index-only scan (без пристап до табелата)
==SQL за анализа на брзината
CREATE OR REPLACE FUNCTION test_finance_report()
RETURNS void AS $$
DECLARE
start_time timestamptz;
end_time timestamptz;
duration int;
BEGIN
start_time := clock_timestamp();
PERFORM *
FROM (
SELECT
fb.user_id,
COALESCE(stats.income_count, 0),
COALESCE(stats.annual_total_income, 0),
COALESCE(stats.active_months, 0)
FROM finance_users fb
LEFT JOIN (
-- Combined logic into one subquery for efficiency
SELECT
user_id,
COUNT(*) AS income_count,
SUM(amount) AS annual_total_income,
COUNT(DISTINCT EXTRACT(MONTH FROM date)) AS active_months
FROM incomes
WHERE EXTRACT(YEAR FROM date) = 2026
GROUP BY user_id
) stats ON stats.user_id = fb.user_id
) t;
end_time := clock_timestamp();
duration := round(1000 * (extract(epoch FROM end_time) - extract(epoch FROM start_time)));
RAISE NOTICE 'Query executed in: % ms', duration;
END;
$$ LANGUAGE plpgsql;
-- 4. PERFORMANCE RUNS
-- run 1: no indexes
SELECT test_finance_report();
-- run 2: add idx_incomes_user_date
CREATE INDEX idx_incomes_user_date ON incomes(user_id, date);
ANALYZE incomes;
SELECT test_finance_report();
-- run 3: add idx_incomes_year (Expression Index)
CREATE INDEX idx_incomes_year ON incomes ((EXTRACT(YEAR FROM date)));
ANALYZE incomes;
SELECT test_finance_report();
-- run 4: add covering index
CREATE INDEX idx_incomes_covering ON incomes(user_id, date, amount);
ANALYZE incomes;
SELECT test_finance_report();
-- Cleanup
DROP FUNCTION test_finance_report();
Сумарно: Без индекси: ~41ms Со индекси: ~16ms Забрзување: ~2.6x
Безбедност и заштита
JWT Token Authorization (Spring Security)
JWT (JSON Web Token) e stateless начин на автентикација - server НЕ чува информации за активни сесии во база, туку сите потребни податоци се во самиот token кој корисникот го чува локално/cookie. JWT содржи енкодирана json структура на информации (user_id, email, role, expiry...).
Java код во Spring Boot:
@Configuration
@Getter @Setter
@ConfigurationProperties(prefix = "auth")
public class AuthProperties {
private String secret;
private int accessTokenMaxAge;
private int refreshTokenMaxAge;
}
@Configuration
@EnableWebSecurity
public class SecurityConfig {
private final JwtAuthenticationFilter jwtAuthenticationFilter;
public SecurityConfig(JwtAuthenticationFilter jwtAuthenticationFilter) {
this.jwtAuthenticationFilter = jwtAuthenticationFilter;
}
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
.csrf(AbstractHttpConfigurer::disable)
.cors(Customizer.withDefaults())
.sessionManagement(sm -> sm.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
.httpBasic(AbstractHttpConfigurer::disable)
.formLogin(AbstractHttpConfigurer::disable)
.authorizeHttpRequests(auth -> auth
.requestMatchers("/api/auth/**").permitAll()
.anyRequest().authenticated());
http.addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);
return http.build();
}
Хеширање на пасворди (BCrypt)
Пасвордите на корисниците се чуваат во база во хеширана форма преку BCrypt, а не како plain text. Ова овозможува сигурно чување на пасвордите.
public class LegacyPasswordEncoder implements PasswordEncoder {
private final BCryptPasswordEncoder bcrypt = new BCryptPasswordEncoder();
@Override
public String encode(CharSequence rawPassword) {
return bcrypt.encode(rawPassword);
}
@Override
public boolean matches(CharSequence rawPassword, String encodedPassword) {
if (encodedPassword == null || encodedPassword.isEmpty()) {
return false;
}
if (encodedPassword.startsWith("$2a$") || encodedPassword.startsWith("$2b$")) {
return bcrypt.matches(rawPassword, encodedPassword);
}
return rawPassword.toString().equals(encodedPassword);
}
}
SQL Injection Prevention (Spring JPA/JPQL)
Целиот backend користи Spring Data JPA која автоматски генерира параметризирани пропити што спречуваат SQL injection.
Безбедно: Derived Query Methods
// UserRepository.java
public interface UserRepository extends JpaRepository<User, Long> {
Optional<User> findByEmail(String email);
Optional<User> findByUsername(String username);
}
// SELECT * FROM users WHERE email = ?
// параметарот се праќа одвоено од SQL командата
Безбедно: @Query со @Param
// TaskRepository.java
@Modifying
@Query("update Task t set t.finished = false where t.disciplineUser.userId = :userId")
int resetFinishedForUser(@Param("userId") Long userId);
// Параметарот :userId се врзува безбедно преку JDBC, не преку string concatenation
Безбедно: Collection параметри
// TrainingSessionRepository.java
@java.util.List<TrainingSession> findByTrainingUser_UserIdAndDateIn(
Long userId,
Collection<LocalDate> dates
);
=== CORS Configuration ===
CORS е безбеден механизам за заштита од requests од различни домени. Со тоа се заштитуваме од можни злонамерни requests.
Java код во Spring Boot:
{{{
@Bean
public CorsConfigurationSource corsConfigurationSource() {
CorsConfiguration config = new CorsConfiguration();
config.setAllowedOrigins(List.of(
"http://localhost:5173",
"http://127.0.0.1:5173"));
config.setAllowedMethods(List.of("GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS"));
config.setAllowedHeaders(List.of("Authorization", "Content-Type", "Accept", "Origin", "X-Requested-With"));
config.setAllowCredentials(true);
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", config);
return source;
}
}}}
