Commit 79e3bfa9 authored by yuwenwen's avatar yuwenwen
parents 8e6b76a6 3734696e
......@@ -2,7 +2,6 @@ package com.tangguo.web.controller.system;
import cn.hutool.core.util.StrUtil;
import com.alibaba.fastjson2.JSON;
import com.tangguo.common.annotation.Log;
import com.tangguo.common.constant.Constants;
import com.tangguo.common.core.domain.AjaxResult;
import com.tangguo.common.core.domain.entity.SysMenu;
......@@ -11,10 +10,8 @@ import com.tangguo.common.core.domain.entity.SysUser;
import com.tangguo.common.core.domain.model.LoginBody;
import com.tangguo.common.core.domain.model.LoginUser;
import com.tangguo.common.core.domain.model.WxcpCodeLogin;
import com.tangguo.common.enums.BusinessType;
import com.tangguo.common.exception.ServiceException;
import com.tangguo.common.utils.SecurityUtils;
import com.tangguo.common.utils.StringUtils;
import com.tangguo.framework.config.ServerConfig;
import com.tangguo.framework.web.service.SysLoginService;
import com.tangguo.framework.web.service.SysPermissionService;
......@@ -59,7 +56,10 @@ public class SysLoginController {
private TokenService tokenService;
@Autowired
private WxCpService wxCpService;
private WxCpService mobileWxCpService;
@Autowired
private WxCpService pcWxCpService;
@Autowired
private ServerConfig serverConfig;
......@@ -67,8 +67,12 @@ public class SysLoginController {
@Autowired
private ISysUserService userService;
@Value("${wx.cp.redirect-url}")
private String redirectUrl;
@Value("${wx.cp.pc-redirect-url}")
private String pcRedirectUrl;
@Value("${wx.cp.mobile-redirect-url}")
private String mobileRedirectUrl;
......@@ -88,6 +92,47 @@ public class SysLoginController {
return ajax;
}
/**
* 企微用户Code登录
*
* @param bo 登录参数
* @return 登录结果
*/
@PostMapping("/pc/code/login")
public AjaxResult pcCodeLogin(@RequestBody WxcpCodeLogin bo) {
LoginUser loginUser = SecurityUtils.getLoginUserNotEx();
log.info("=> 登录请求参数:{}", bo);
log.info("=> 当前登录用户:{}", loginUser);
// 企微登录认证链接
String authCode = bo.getCode();
WxCpOAuth2Service oauth2Service = this.pcWxCpService.getOauth2Service();
if (Objects.isNull(loginUser) && StrUtil.isBlank(authCode)) {
String oauth2Url = oauth2Service.buildAuthorizationUrl(this.pcRedirectUrl, null);
log.info("=> 认证失败,返回Oauth2登录链接:{}", oauth2Url);
return AjaxResult.error(401, "身份认证失败", oauth2Url);
}
// 查询企微用户信息
String token = null;
if (Objects.isNull(loginUser) && StrUtil.isNotBlank(authCode)) {
try {
WxCpOauth2UserInfo userInfo = oauth2Service.getUserInfo(authCode);
log.info("=> 查询企微用户信息,返回结果:{},{}", authCode, JSON.toJSONString(userInfo));
UserDetails userDetails = this.userDetailsServiceImpl.loadUserByUsername2(userInfo.getUserId());
token = this.tokenService.createToken((LoginUser) userDetails);
} catch (Exception e) {
log.error("=> 查询用户信息失败:", e);
throw new ServiceException("登录失败,查询用户信息失败。");
}
}
log.info("=> 登录结果:{}", token);
return AjaxResult.success("登录成功", token);
}
/**
* 获取用户信息
*
......@@ -129,16 +174,16 @@ public class SysLoginController {
* @return 登录结果
*/
@PostMapping("/bbs/mobile/user/code/login")
public AjaxResult codeLogin(@RequestBody WxcpCodeLogin bo) {
public AjaxResult mobileCodeLogin(@RequestBody WxcpCodeLogin bo) {
LoginUser loginUser = SecurityUtils.getLoginUserNotEx();
log.info("=> 登录请求参数:{}", bo);
log.info("=> 当前登录用户:{}", loginUser);
// 企微登录认证链接
String authCode = bo.getCode();
WxCpOAuth2Service oauth2Service = this.wxCpService.getOauth2Service();
WxCpOAuth2Service oauth2Service = this.mobileWxCpService.getOauth2Service();
if (Objects.isNull(loginUser) && StrUtil.isBlank(authCode)) {
String oauth2Url = oauth2Service.buildAuthorizationUrl(this.redirectUrl, null);
String oauth2Url = oauth2Service.buildAuthorizationUrl(this.mobileRedirectUrl, null);
log.info("=> 认证失败,返回Oauth2登录链接:{}", oauth2Url);
return AjaxResult.error(401, "身份认证失败", oauth2Url);
}
......
......@@ -92,11 +92,17 @@ spring:
listener:
max-concurrency: 3
# 企业微信配置
wx:
cp:
redirect-url: https://test.tangguo.ren/bbsh5/pages/login/login
pc-redirect-url: https://test.tangguo.ren/bbs/pc/wechatlogin
mobile-redirect-url: https://test.tangguo.ren/bbsh5/pages/login/login
corp-id: ww63ca87d5f8647514
app-config:
app-configs:
- name: '移动端应用配置'
agent-id: 1000072
secret: O2KXf2b9oGG2GBrpzDgf4EFdhGwl2KaS9BWtJQT1I64
- name: '管理端应用配置'
agent-id: 1000072
secret: O2KXf2b9oGG2GBrpzDgf4EFdhGwl2KaS9BWtJQT1I64
......@@ -95,8 +95,13 @@ spring:
# 企业微信配置
wx:
cp:
redirect-url: https://wecom.jift.edu.cn/bbsh5/pages/login/login
pc-redirect-url: https://wecom.jift.edu.cn/bbs/pc/wechatlogin
mobile-redirect-url: https://wecom.jift.edu.cn/bbs/h5/pages/login/login
corp-id: wxd2a84aa7529d3801
app-config:
app-configs:
- name: '移动端应用配置'
agent-id: 1000218
secret: UubIP6xbLBzw3DwcIyOARYf1e4cm5GNJKNFZTlVfgyo
- name: '管理端应用配置'
agent-id: 1000219
secret: OEMM3DAd-2FK-9Ggiu3xsD7Sg4SlPuV7os1hAWrTIwk
......@@ -95,7 +95,13 @@ spring:
# 企业微信配置
wx:
cp:
pc-redirect-url: https://test.tangguo.ren/bbs/pc/wechatlogin
mobile-redirect-url: https://test.tangguo.ren/bbsh5/pages/login/login
corp-id: ww63ca87d5f8647514
app-config:
app-configs:
- name: '移动端应用配置'
agent-id: 1000072
secret: O2KXf2b9oGG2GBrpzDgf4EFdhGwl2KaS9BWtJQT1I64
- name: '管理端应用配置'
agent-id: 1000072
secret: O2KXf2b9oGG2GBrpzDgf4EFdhGwl2KaS9BWtJQT1I64
......@@ -119,14 +119,3 @@ xss:
excludes: /system/notice
# 匹配链接
urlPatterns: /system/*,/monitor/*,/tool/*
mobile:
auth:
res-token-name: token
req-token-name: Authorization
issuer: Mobile-Auth
algorithm-id: HS512
sign-key: SignKey2025@.
effective-time: 7d
path-patterns: /bbs/mobile/**
......@@ -23,14 +23,14 @@ public class ApplicationTest {
private JmsTemplate jmsTemplate;
@Resource
private WxCpService wxCpService;
private WxCpService mobileWxCpService;
@Test
public void test() {
WxCpOAuth2Service oauth2Service = this.wxCpService.getOauth2Service();
System.out.println(oauth2Service.buildAuthorizationUrl("https://test.tangguo.ren/bbs/h5/pages/login/login/", ""));
WxCpOAuth2Service oauth2Service = this.mobileWxCpService.getOauth2Service();
System.out.println(oauth2Service.buildAuthorizationUrl("https://qywx.tangguo.ren/bbs/h5/pages/login/login/", ""));
}
......
......@@ -57,3 +57,15 @@ export function getCodeImg() {
timeout: 20000
})
}
// 企业
export function loginByWechatWork(data) {
return request({
url: '/pc/code/login',
headers: {
isToken: false
},
method: 'post',
data: data
})
}
......@@ -8,7 +8,7 @@ import { isRelogin } from '@/utils/request'
NProgress.configure({ showSpinner: false })
const whiteList = ['/login', '/register']
const whiteList = ['/login', '/register','/wechatlogin']
router.beforeEach((to, from, next) => {
NProgress.start()
......
......@@ -46,6 +46,11 @@ export const constantRoutes = [
component: () => import('@/views/login'),
hidden: true
},
{
path: '/wechatlogin',
component: () => import('@/views/wechatlogin'),
hidden: true
},
{
path: '/register',
component: () => import('@/views/register'),
......@@ -177,7 +182,7 @@ Router.prototype.replace = function push(location) {
}
export default new Router({
base:'/bbspc',
base:'/bbs/pc',
mode: 'history', // 去掉url中的#
scrollBehavior: () => ({ y: 0 }),
routes: constantRoutes
......
import { login, logout, getInfo } from '@/api/login'
import { getToken, setToken, removeToken } from '@/utils/auth'
import { login, logout, getInfo, loginByWechatWork } from "@/api/login";
import { getToken, setToken, removeToken } from "@/utils/auth";
const user = {
state: {
token: getToken(),
name: '',
avatar: '',
name: "",
avatar: "",
roles: [],
permissions: []
permissions: [],
},
mutations: {
SET_TOKEN: (state, token) => {
state.token = token
state.token = token;
},
SET_NAME: (state, name) => {
state.name = name
state.name = name;
},
SET_AVATAR: (state, avatar) => {
state.avatar = avatar
state.avatar = avatar;
},
SET_ROLES: (state, roles) => {
state.roles = roles
state.roles = roles;
},
SET_PERMISSIONS: (state, permissions) => {
state.permissions = permissions
}
state.permissions = permissions;
},
},
actions: {
getWechatLogin({ commit }, code) {
return new Promise((resolve, reject) => {
loginByWechatWork({ code })
.then((res) => {
setToken(res.data);
commit("SET_TOKEN", res.data);
resolve(res);
})
.catch((error) => {
reject(error);
});
});
},
// 登录
Login({ commit }, userInfo) {
const username = userInfo.username.trim()
const password = userInfo.password
const code = userInfo.code
const uuid = userInfo.uuid
const username = userInfo.username.trim();
const password = userInfo.password;
const code = userInfo.code;
const uuid = userInfo.uuid;
return new Promise((resolve, reject) => {
login(username, password, code, uuid).then(res => {
setToken(res.token)
commit('SET_TOKEN', res.token)
resolve()
}).catch(error => {
reject(error)
})
login(username, password, code, uuid)
.then((res) => {
setToken(res.token);
commit("SET_TOKEN", res.token);
resolve();
})
.catch((error) => {
reject(error);
});
});
},
// 获取用户信息
GetInfo({ commit, state }) {
return new Promise((resolve, reject) => {
getInfo().then(res => {
const user = res.user
const avatar = (user.avatar == "" || user.avatar == null) ? require("@/assets/images/profile.jpg") : process.env.VUE_APP_BASE_API + user.avatar;
if (res.roles && res.roles.length > 0) { // 验证返回的roles是否是一个非空数组
commit('SET_ROLES', res.roles)
commit('SET_PERMISSIONS', res.permissions)
getInfo()
.then((res) => {
const user = res.user;
const avatar =
user.avatar == "" || user.avatar == null
? require("@/assets/images/profile.jpg")
: process.env.VUE_APP_BASE_API + user.avatar;
if (res.roles && res.roles.length > 0) {
// 验证返回的roles是否是一个非空数组
commit("SET_ROLES", res.roles);
commit("SET_PERMISSIONS", res.permissions);
} else {
commit('SET_ROLES', ['ROLE_DEFAULT'])
commit("SET_ROLES", ["ROLE_DEFAULT"]);
}
commit('SET_NAME', user.userName)
commit('SET_AVATAR', avatar)
resolve(res)
}).catch(error => {
reject(error)
})
commit("SET_NAME", user.userName);
commit("SET_AVATAR", avatar);
resolve(res);
})
.catch((error) => {
reject(error);
});
});
},
// 退出系统
LogOut({ commit, state }) {
return new Promise((resolve, reject) => {
logout(state.token).then(() => {
commit('SET_TOKEN', '')
commit('SET_ROLES', [])
commit('SET_PERMISSIONS', [])
removeToken()
resolve()
}).catch(error => {
reject(error)
})
logout(state.token)
.then(() => {
commit("SET_TOKEN", "");
commit("SET_ROLES", []);
commit("SET_PERMISSIONS", []);
removeToken();
resolve();
})
.catch((error) => {
reject(error);
});
});
},
// 前端 登出
FedLogOut({ commit }) {
return new Promise(resolve => {
commit('SET_TOKEN', '')
removeToken()
resolve()
})
}
}
}
return new Promise((resolve) => {
commit("SET_TOKEN", "");
removeToken();
resolve();
});
},
},
};
export default user
export default user;
import axios from 'axios'
import { Notification, MessageBox, Message, Loading } from 'element-ui'
import store from '@/store'
import { getToken } from '@/utils/auth'
import errorCode from '@/utils/errorCode'
import axios from "axios";
import { Notification, MessageBox, Message, Loading } from "element-ui";
import store from "@/store";
import { getToken } from "@/utils/auth";
import errorCode from "@/utils/errorCode";
import { tansParams, blobValidate } from "@/utils/ruoyi";
import cache from '@/plugins/cache'
import { saveAs } from 'file-saver'
import cache from "@/plugins/cache";
import { saveAs } from "file-saver";
let downloadLoadingInstance;
// 是否显示重新登录
export let isRelogin = { show: false };
axios.defaults.headers['Content-Type'] = 'application/json;charset=utf-8'
axios.defaults.headers["Content-Type"] = "application/json;charset=utf-8";
// 创建axios实例
const service = axios.create({
// axios中请求配置有baseURL选项,表示请求URL公共部分
baseURL: process.env.VUE_APP_BASE_API,
// 超时
timeout: 30000
})
timeout: 30000,
});
// 判断是否为企业微信环境
const isWeComEnv = () => {
// 企业微信环境的userAgent特征
const userAgent = window.navigator.userAgent.toLowerCase();
return (
userAgent.includes("wxwork") ||
(userAgent.includes("micromessenger") && userAgent.includes("wxwork"))
);
};
// request拦截器
service.interceptors.request.use(config => {
service.interceptors.request.use(
(config) => {
// 是否需要设置 token
const isToken = (config.headers || {}).isToken === false
const isToken = (config.headers || {}).isToken === false;
// 是否需要防止数据重复提交
const isRepeatSubmit = (config.headers || {}).repeatSubmit === false
const isRepeatSubmit = (config.headers || {}).repeatSubmit === false;
if (getToken() && !isToken) {
config.headers['Authorization'] = 'Bearer ' + getToken() // 让每个请求携带自定义token 请根据实际情况自行修改
config.headers["Authorization"] = "Bearer " + getToken(); // 让每个请求携带自定义token 请根据实际情况自行修改
}
// get请求映射params参数
if (config.method === 'get' && config.params) {
let url = config.url + '?' + tansParams(config.params);
if (config.method === "get" && config.params) {
let url = config.url + "?" + tansParams(config.params);
url = url.slice(0, -1);
config.params = {};
config.url = url;
}
if (!isRepeatSubmit && (config.method === 'post' || config.method === 'put')) {
if (
!isRepeatSubmit &&
(config.method === "post" || config.method === "put")
) {
const requestObj = {
url: config.url,
data: typeof config.data === 'object' ? JSON.stringify(config.data) : config.data,
time: new Date().getTime()
}
const sessionObj = cache.session.getJSON('sessionObj')
if (sessionObj === undefined || sessionObj === null || sessionObj === '') {
cache.session.setJSON('sessionObj', requestObj)
data:
typeof config.data === "object"
? JSON.stringify(config.data)
: config.data,
time: new Date().getTime(),
};
const sessionObj = cache.session.getJSON("sessionObj");
if (
sessionObj === undefined ||
sessionObj === null ||
sessionObj === ""
) {
cache.session.setJSON("sessionObj", requestObj);
} else {
const s_url = sessionObj.url; // 请求地址
const s_data = sessionObj.data; // 请求数据
const s_time = sessionObj.time; // 请求时间
const interval = 1000; // 间隔时间(ms),小于此时间视为重复提交
if (s_data === requestObj.data && requestObj.time - s_time < interval && s_url === requestObj.url) {
const message = '数据正在处理,请勿重复提交';
console.warn(`[${s_url}]: ` + message)
return Promise.reject(new Error(message))
if (
s_data === requestObj.data &&
requestObj.time - s_time < interval &&
s_url === requestObj.url
) {
const message = "数据正在处理,请勿重复提交";
console.warn(`[${s_url}]: ` + message);
return Promise.reject(new Error(message));
} else {
cache.session.setJSON('sessionObj', requestObj)
cache.session.setJSON("sessionObj", requestObj);
}
}
}
return config;
},
(error) => {
console.log(error);
Promise.reject(error);
}
return config
}, error => {
console.log(error)
Promise.reject(error)
})
);
// 响应拦截器
service.interceptors.response.use(res => {
service.interceptors.response.use(
(res) => {
// 未设置状态码则默认成功状态
const code = res.data.code || 200;
// 获取错误信息
const msg = errorCode[code] || res.data.msg || errorCode['default']
const msg = errorCode[code] || res.data.msg || errorCode["default"];
// 二进制数据则直接返回
if (res.request.responseType === 'blob' || res.request.responseType === 'arraybuffer') {
return res.data
if (
res.request.responseType === "blob" ||
res.request.responseType === "arraybuffer"
) {
return res.data;
}
if (code === 401) {
if (!isRelogin.show) {
if (isWeComEnv()) {
console.log(res.data.data);
if (res.data.data && res.data.data.indexOf("http") === 0) {
window.location = res.data.data;
}
} else if (!isRelogin.show) {
isRelogin.show = true;
MessageBox.confirm('登录状态已过期,您可以继续留在该页面,或者重新登录', '系统提示', { confirmButtonText: '重新登录', cancelButtonText: '取消', type: 'warning' }).then(() => {
MessageBox.confirm(
"登录状态已过期,您可以继续留在该页面,或者重新登录",
"系统提示",
{
confirmButtonText: "重新登录",
cancelButtonText: "取消",
type: "warning",
}
)
.then(() => {
isRelogin.show = false;
store.dispatch('LogOut').then(() => {
location.href = '/index';
store.dispatch("LogOut").then(() => {
location.href = "/index";
});
})
}).catch(() => {
.catch(() => {
isRelogin.show = false;
});
}
return Promise.reject('无效的会话,或者会话已过期,请重新登录。')
return Promise.reject("无效的会话,或者会话已过期,请重新登录。");
} else if (code === 500) {
Message({ message: msg, type: 'error' })
return Promise.reject(new Error(msg))
Message({ message: msg, type: "error" });
return Promise.reject(new Error(msg));
} else if (code === 601) {
Message({ message: msg, type: 'warning' })
return Promise.reject('error')
Message({ message: msg, type: "warning" });
return Promise.reject("error");
} else if (code !== 200) {
Notification.error({ title: msg })
return Promise.reject('error')
Notification.error({ title: msg });
return Promise.reject("error");
} else {
return res.data
return res.data;
}
},
error => {
console.log('err' + error)
(error) => {
console.log("err" + error);
let { message } = error;
if (message == "Network Error") {
message = "后端接口连接异常";
......@@ -111,36 +156,48 @@ service.interceptors.response.use(res => {
} else if (message.includes("Request failed with status code")) {
message = "系统接口" + message.substr(message.length - 3) + "异常";
}
Message({ message: message, type: 'error', duration: 5 * 1000 })
return Promise.reject(error)
Message({ message: message, type: "error", duration: 5 * 1000 });
return Promise.reject(error);
}
)
);
// 通用下载方法
export function download(url, params, filename, config) {
downloadLoadingInstance = Loading.service({ text: "正在下载数据,请稍候", spinner: "el-icon-loading", background: "rgba(0, 0, 0, 0.7)", })
return service.post(url, params, {
transformRequest: [(params) => { return tansParams(params) }],
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
responseType: 'blob',
...config
}).then(async (data) => {
downloadLoadingInstance = Loading.service({
text: "正在下载数据,请稍候",
spinner: "el-icon-loading",
background: "rgba(0, 0, 0, 0.7)",
});
return service
.post(url, params, {
transformRequest: [
(params) => {
return tansParams(params);
},
],
headers: { "Content-Type": "application/x-www-form-urlencoded" },
responseType: "blob",
...config,
})
.then(async (data) => {
const isBlob = blobValidate(data);
if (isBlob) {
const blob = new Blob([data])
saveAs(blob, filename)
const blob = new Blob([data]);
saveAs(blob, filename);
} else {
const resText = await data.text();
const rspObj = JSON.parse(resText);
const errMsg = errorCode[rspObj.code] || rspObj.msg || errorCode['default']
const errMsg =
errorCode[rspObj.code] || rspObj.msg || errorCode["default"];
Message.error(errMsg);
}
downloadLoadingInstance.close();
}).catch((r) => {
console.error(r)
Message.error('下载文件出现错误,请联系管理员!')
downloadLoadingInstance.close();
})
.catch((r) => {
console.error(r);
Message.error("下载文件出现错误,请联系管理员!");
downloadLoadingInstance.close();
});
}
export default service
export default service;
<template>
<div>
</div>
</template>
<script>
import { mapActions } from 'vuex'
export default {
mounted() {
if (this.isWeComEnv()) {
// 企业微信环境处理
const code = this.$route.query.code
this.getWechatLogin(code).then(() => {
this.$router.replace('/')
})
} else {
// 非企业微信环境,跳转到普通登录页
this.$router.replace('/login')
}
},
methods: {
// 判断是否为企业微信环境
isWeComEnv() {
// return true
const userAgent = window.navigator.userAgent.toLowerCase()
return userAgent.includes('wxwork') || (userAgent.includes('micromessenger') && userAgent.includes('wxwork'))
},
// 映射 Vuex 中的 getWechatLogin 方法
...mapActions(['getWechatLogin'])
}
}
</script>
......@@ -18,7 +18,7 @@ module.exports = {
// 部署生产环境和开发环境下的URL。
// 默认情况下,Vue CLI 会假设你的应用是被部署在一个域名的根路径上
// 例如 https://www.ruoyi.vip/。如果应用被部署在一个子路径上,你就需要用这个选项指定这个子路径。例如,如果你的应用被部署在 https://www.ruoyi.vip/admin/,则设置 baseUrl 为 /admin/。
publicPath: process.env.NODE_ENV === "production" ? "/bbspc" : "/",
publicPath: process.env.NODE_ENV === "production" ? "/bbs/pc" : "/",
// 在npm run build 或 yarn build 时 ,生成文件的目录名称(要和baseUrl的生产环境路径一致)(默认dist)
outputDir: 'dist',
// 用于放置生成的静态资源 (js、css、img、fonts) 的;(项目打包之后,静态资源会放在这个文件夹下)
......
{
"name" : "社区",
"appid" : "__UNI__E3457E1",
"appid" : "__UNI__69A40B9",
"description" : "",
"versionName" : "1.0.0",
"versionCode" : "100",
......@@ -72,7 +72,7 @@
"h5" : {
"router" : {
"mode" : "history",
"base" : "/bbsh5/"
"base" : "/bbs/h5/"
}
}
}
......@@ -110,7 +110,7 @@ public class SecurityConfig extends WebSecurityConfigurerAdapter {
// 过滤请求
.authorizeRequests()
// 对于登录login 注册register 验证码captchaImage 允许匿名访问
.antMatchers("/login", "/bbs/mobile/user/code/login", "/register", "/captchaImage").permitAll()
.antMatchers("/login", "/bbs/mobile/user/code/login", "/pc/code/login", "/register", "/captchaImage").permitAll()
// 静态资源,可匿名访问
.antMatchers(HttpMethod.GET, "/", "/*.html", "/**/*.html", "/**/*.css", "/**/*.js", "/profile/**").permitAll()
.antMatchers("/swagger-ui.html", "/swagger-resources/**", "/webjars/**", "/*/api-docs", "/druid/**").permitAll()
......
......@@ -33,10 +33,10 @@ public class AuthenticationEntryPointImpl implements AuthenticationEntryPoint, S
private static final long serialVersionUID = -8970718410437077606L;
@Autowired
private WxCpService wxCpService;
private WxCpService mobileWxCpService;
@Value("${wx.cp.redirect-url}")
private String redirectUrl;
@Value("${wx.cp.mobile-redirect-url}")
private String mobileRedirectUrl;
@Override
public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException e)
......@@ -47,8 +47,8 @@ public class AuthenticationEntryPointImpl implements AuthenticationEntryPoint, S
log.info("=> 身份认证失败,Token:{}", request.getHeader("Authorization"));
if (uri.startsWith("/bbs/mobile")) {
WxCpOAuth2Service oauth2Service = this.wxCpService.getOauth2Service();
String oauth2Url = oauth2Service.buildAuthorizationUrl(this.redirectUrl, null);
WxCpOAuth2Service oauth2Service = this.mobileWxCpService.getOauth2Service();
String oauth2Url = oauth2Service.buildAuthorizationUrl(this.mobileRedirectUrl, null);
ServletUtils.renderString(response, JSON.toJSONString(AjaxResult.error(401, "身份认证失败", oauth2Url)));
} else {
String msg = StringUtils.format("请求访问:{},认证失败,无法访问系统资源", uri);
......
......@@ -65,7 +65,7 @@ public class SysLoginService {
private UserDetailsServiceImpl userDetailsServiceImpl;
@Autowired
private WxCpService wxCpService;
private WxCpService mobileWxCpService;
/**
......
......@@ -9,6 +9,7 @@ import org.springframework.boot.context.properties.EnableConfigurationProperties
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.List;
import java.util.Objects;
......@@ -29,9 +30,29 @@ public class WxCpConfiguration {
/**
* 配置 WxCpService 实例
*/
@Bean
public WxCpService wxCpService() {
WxCpProperties.AppConfig appConfig = this.properties.getAppConfig();
@Bean("mobileWxCpService")
public WxCpService mobileWxCpService() {
List<WxCpProperties.AppConfig> appConfigs = this.properties.getAppConfigs();
WxCpProperties.AppConfig appConfig = appConfigs.get(0);
WxCpDefaultConfigImpl configStorage = new WxCpDefaultConfigImpl();
configStorage.setCorpId(this.properties.getCorpId());
configStorage.setAgentId(appConfig.getAgentId());
configStorage.setCorpSecret(appConfig.getSecret());
configStorage.setToken(appConfig.getToken());
configStorage.setAesKey(appConfig.getAesKey());
WxCpService service = new WxCpServiceImpl();
service.setWxCpConfigStorage(configStorage);
return service;
}
/**
* 配置 WxCpService 实例
*/
@Bean("pcWxCpService")
public WxCpService pcWxCpService() {
List<WxCpProperties.AppConfig> appConfigs = this.properties.getAppConfigs();
WxCpProperties.AppConfig appConfig = appConfigs.get(1);
WxCpDefaultConfigImpl configStorage = new WxCpDefaultConfigImpl();
configStorage.setCorpId(this.properties.getCorpId());
configStorage.setAgentId(appConfig.getAgentId());
......
......@@ -3,6 +3,8 @@ package com.tangguo.framework.wxcp;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import java.util.List;
/**
* 企业微信配置类
*
......@@ -21,12 +23,14 @@ public class WxCpProperties {
/**
* 多应用配置
*/
private AppConfig appConfig;
private List<AppConfig> appConfigs;
@Data
public static class AppConfig {
private String name;
/**
* 设置企业微信应用的AgentId
*/
......
......@@ -14,6 +14,8 @@ import com.tangguo.service.IBbsMomentCommentService;
import com.tangguo.service.IBbsMomentService;
import com.tangguo.service.IBbsMomentVoteOptionService;
import com.tangguo.service.IBbsMomentVoteService;
import io.swagger.annotations.ApiOperation;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
......@@ -216,4 +218,16 @@ public class MBbsMomentController {
return AjaxResult.success();
}
/**
* 置顶评论
*/
@ApiOperation("置顶评论")
@PostMapping("/comment/top")
public AjaxResult topComment(@RequestBody BbsCommentTopBO bo) {
ValidateOperations.generalValidate(bo);
this.momentService.userTopComment(bo);
return AjaxResult.success();
}
}
......@@ -6,9 +6,11 @@ import com.tangguo.common.core.domain.AjaxResult;
import com.tangguo.common.core.page.TableDataInfo;
import com.tangguo.common.enums.BusinessType;
import com.tangguo.domain.BbsMomentComment;
import com.tangguo.domain.bo.BbsCommentTopBO;
import com.tangguo.service.IBbsMomentCommentService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.checkerframework.checker.units.qual.A;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.*;
......@@ -79,4 +81,16 @@ public class BbsMomentCommentController extends BaseController {
return getDataTable(list);
}
/**
* 置顶评论
*/
@ApiOperation("置顶评论")
@PreAuthorize("@ss.hasPermi('moment:comment:top')")
@PostMapping("/top")
public AjaxResult topComment(@RequestBody BbsCommentTopBO bo) {
this.bbsMomentCommentService.topMomentComment(bo);
return AjaxResult.success();
}
}
......@@ -13,6 +13,7 @@ import lombok.NoArgsConstructor;
import lombok.ToString;
import lombok.experimental.Accessors;
import java.util.Date;
import java.util.List;
/**
......@@ -90,6 +91,14 @@ public class BbsMomentComment extends BaseEntity {
@ApiModelProperty("删除评论内容")
private String deleteComment;
/** 评论是否置顶:0 否、1 是 */
@ApiModelProperty("评论是否置顶:0 否、1 是")
private Integer isTop;
/** 评论置顶时间 */
@ApiModelProperty("评论置顶时间")
private Date topTime;
/**
* 动态内容
*/
......
package com.tangguo.domain.bo;
import lombok.Data;
import javax.validation.constraints.NotNull;
/**
*
*
* @author 谈笑
* @createTime 2025-11-05 16:12:43 星期三
*/
@Data
public class BbsCommentTopBO {
/**
* 动态Id
*/
@NotNull(message = "动态Id不能为空")
private Long momentId;
/**
* 评论Id
*/
@NotNull(message = "评论Id不能为空")
private Long commentId;
/**
* 置顶状态:0 取消置顶、1 置顶
*/
@NotNull(message = "置顶状态不能为空")
private Integer isTop;
}
......@@ -2,6 +2,7 @@ package com.tangguo.service;
import com.baomidou.mybatisplus.extension.service.IService;
import com.tangguo.domain.BbsMomentComment;
import com.tangguo.domain.bo.BbsCommentTopBO;
import com.tangguo.domain.vo.BbsCommentDetailVO;
import java.util.List;
......@@ -68,4 +69,12 @@ public interface IBbsMomentCommentService extends IService<BbsMomentComment> {
*/
void deleteMomentComments(Long momentId);
/**
* 置顶评论
*
* @param bo 请求参数
*/
void topMomentComment(BbsCommentTopBO bo);
}
......@@ -133,4 +133,12 @@ public interface IBbsMomentService extends IService<BbsMoment> {
*/
void userFeaturedComment(FeaturedCommentBO bo);
/**
* 置顶评论
*
* @param bo 评论
*/
void userTopComment(BbsCommentTopBO bo);
}
package com.tangguo.service.impl;
import cn.hutool.core.collection.CollUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.tangguo.common.exception.ServiceException;
import com.tangguo.domain.BbsMomentComment;
import com.tangguo.domain.bo.BbsCommentTopBO;
import com.tangguo.domain.vo.BbsCommentDetailVO;
import com.tangguo.mapper.BbsMomentCommentMapper;
import com.tangguo.service.IBbsMomentCommentService;
......@@ -11,6 +15,7 @@ import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Objects;
......@@ -110,4 +115,29 @@ public class BbsMomentCommentServiceImpl extends ServiceImpl<BbsMomentCommentMap
);
}
/**
* 置顶评论
*
* @param bo 请求参数
*/
@Override
public void topMomentComment(BbsCommentTopBO bo) {
BbsMomentComment dbComment = this.getById(bo.getCommentId());
if (Objects.isNull(dbComment)) {
throw new ServiceException("操作失败,未查询到当前评论数据。");
}
LambdaUpdateWrapper<BbsMomentComment> wrapper = new LambdaUpdateWrapper<>();
wrapper.eq(BbsMomentComment::getId, dbComment.getId());
if (bo.getIsTop() == 1) {
wrapper.set(BbsMomentComment::getIsTop, 1);
wrapper.set(BbsMomentComment::getTopTime, new Date());
} else {
wrapper.set(BbsMomentComment::getIsTop, 0);
wrapper.set(BbsMomentComment::getTopTime, null);
}
this.update(wrapper);
}
}
......@@ -467,6 +467,27 @@ public class BbsMomentServiceImpl extends ServiceImpl<BbsMomentMapper, BbsMoment
this.commentService.updateById(updComment);
}
/**
* 置顶评论
*
* @param bo 评论
*/
@Transactional(rollbackFor = Exception.class)
@Override
public void userTopComment(BbsCommentTopBO bo) {
BbsMoment dbMoment = this.getById(bo.getMomentId());
if (Objects.isNull(dbMoment)) {
throw new ServiceException("操作失败,未查询到当前动态数据。");
}
String userName = SecurityUtils.getUsername();
if (!dbMoment.getUserName().equals(userName)) {
throw new ServiceException("操作失败,没有对当前动态评论的操作权限。");
}
this.commentService.topMomentComment(bo);
}
/**
* 构建动态实体
......
......@@ -27,7 +27,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
</if>
</where>
ORDER BY
c.create_time DESC
c.is_top DESC, c.top_time DESC, c.create_time DESC
</select>
......@@ -41,7 +41,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
WHERE
status = 1 AND FIND_IN_SET(#{commentId}, r.ancestor_path) AND r.id != #{commentId}
ORDER BY
r.create_time
r.is_top DESC, r.top_time DESC, r.create_time
</select>
......@@ -53,7 +53,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
WHERE
status = 1 AND moment_id = #{momentId}
ORDER BY
create_time
is_top DESC, top_time DESC, create_time
</select>
......@@ -94,6 +94,8 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
OR m.is_enable_featured_comment = 0
OR (m.is_enable_featured_comment = 1 AND (c.user_name = #{userName} OR c.is_featured = 1))
)
ORDER BY
c.is_top DESC, c.top_time DESC, c.create_time
) AS c
WHERE rn &lt;= #{rows};
</select>
......@@ -126,7 +128,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
OR (m.is_enable_featured_comment = 1 AND (c.user_name = #{userName} OR c.is_featured = 1))
)
ORDER BY
c.create_time
c.is_top DESC, c.top_time DESC, c.create_time
</select>
</mapper>
......@@ -20,6 +20,12 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
<if test="moment.content != null and moment.content != ''">
m.content LIKE CONCAT('%', #{moment.content}, '%')
</if>
<if test="moment.userName != null and moment.userName != ''">
AND uv.user_name LIKE CONCAT('%', #{moment.userName}, '%')
</if>
<if test="moment.nickName != null and moment.nickName != ''">
AND uv.nick_name LIKE CONCAT('%', #{moment.nickName}, '%')
</if>
<if test="moment.topicNames != null and moment.topicNames != ''">
AND m.topic_names LIKE CONCAT('%', #{moment.topicNames}, '%')
</if>
......@@ -133,7 +139,9 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
) v ON v.moment_id = m.id
<where>
<if test="bo.content != null and bo.content != ''">
m.content LIKE CONCAT('%', #{bo.content}, '%')
(m.content LIKE CONCAT('%', #{bo.content}, '%')
OR uv.user_name LIKE CONCAT('%', #{bo.content}, '%')
OR uv.nick_name LIKE CONCAT('%', #{bo.content}, '%'))
</if>
<if test="bo.topicName != null and bo.topicName != ''">
AND m.topic_names LIKE CONCAT('%', #{bo.topicName}, '%')
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment